Browse Source

Minor - merge with 1.11.x

jmontoyaa 7 years ago
parent
commit
78e1642cb9
62 changed files with 2864 additions and 1741 deletions
  1. 6 4
      main/badge/issued.php
  2. 1 1
      main/forum/forumfunction.inc.php
  3. 1 1
      main/inc/introductionSection.inc.php
  4. 2 2
      main/inc/lib/AnnouncementEmail.php
  5. 25 18
      main/inc/lib/AnnouncementManager.php
  6. 49 2
      main/inc/lib/ScheduledAnnouncement.php
  7. 7 9
      main/inc/lib/TicketManager.php
  8. 0 1
      main/inc/lib/agenda.lib.php
  9. 87 33
      main/inc/lib/api.lib.php
  10. 1 1
      main/inc/lib/attendance.lib.php
  11. 2 6
      main/inc/lib/auth.lib.php
  12. 588 8
      main/inc/lib/career.lib.php
  13. 24 24
      main/inc/lib/certificate.lib.php
  14. 47 46
      main/inc/lib/course.lib.php
  15. 2 5
      main/inc/lib/course_category.lib.php
  16. 18 18
      main/inc/lib/course_home.lib.php
  17. 2 0
      main/inc/lib/database.lib.php
  18. 11 3
      main/inc/lib/display.lib.php
  19. 164 202
      main/inc/lib/document.lib.php
  20. 1 1
      main/inc/lib/events.lib.php
  21. 159 151
      main/inc/lib/exercise.lib.php
  22. 196 184
      main/inc/lib/exercise_show_functions.lib.php
  23. 3 5
      main/inc/lib/export.lib.inc.php
  24. 60 18
      main/inc/lib/extra_field.lib.php
  25. 30 12
      main/inc/lib/extra_field_value.lib.php
  26. 129 64
      main/inc/lib/fileUpload.lib.php
  27. 46 8
      main/inc/lib/glossary.lib.php
  28. 29 15
      main/inc/lib/image.lib.php
  29. 41 23
      main/inc/lib/import.lib.php
  30. 7 14
      main/inc/lib/internationalization.lib.php
  31. 6 1
      main/inc/lib/javascript/ckeditor/config_js.php
  32. 319 337
      main/inc/lib/javascript/ckeditor/plugins/video/dialogs/video.js
  33. 0 7
      main/inc/lib/javascript/hotspot/js/hotspot.js
  34. 2 2
      main/inc/lib/javascript/svgedit/extensions/imagelib/groups.php
  35. 2 2
      main/inc/lib/javascript/svgedit/extensions/imagelib/index.php
  36. 0 2
      main/inc/lib/login.lib.php
  37. 45 7
      main/inc/lib/message.lib.php
  38. 4 3
      main/inc/lib/model.lib.php
  39. 9 17
      main/inc/lib/notebook.lib.php
  40. 14 22
      main/inc/lib/pdf.lib.php
  41. 30 4
      main/inc/lib/pear/HTML/QuickForm/Rule/Compare.php
  42. 1 1
      main/inc/lib/pear/HTML/QuickForm/advmultiselect.php
  43. 1 1
      main/inc/lib/pear/HTML/QuickForm/file.php
  44. 1 1
      main/inc/lib/pear/HTML/QuickForm/select.php
  45. 44 25
      main/inc/lib/sessionmanager.lib.php
  46. 102 3
      main/inc/lib/skill.lib.php
  47. 32 2
      main/inc/lib/social.lib.php
  48. 6 2
      main/inc/lib/sortable_table.class.php
  49. 5 5
      main/inc/lib/svg-edit/extensions/imagelib/groups.php
  50. 5 5
      main/inc/lib/svg-edit/extensions/imagelib/index.php
  51. 77 35
      main/inc/lib/table_sort.class.php
  52. 109 61
      main/inc/lib/text.lib.php
  53. 11 7
      main/inc/lib/thematic.lib.php
  54. 12 13
      main/inc/lib/tracking.lib.php
  55. 19 11
      main/inc/lib/usergroup.lib.php
  56. 66 13
      main/inc/lib/usermanager.lib.php
  57. 65 73
      main/inc/lib/webservices/Rest.php
  58. 118 166
      main/lp/learnpath.class.php
  59. 0 23
      main/messages/outbox.php
  60. 17 7
      main/mySpace/myStudents.php
  61. 2 2
      main/mySpace/works_in_session_report.php
  62. 2 2
      main/upload/form.scorm.php

+ 6 - 4
main/badge/issued.php

@@ -13,8 +13,8 @@ use Chamilo\CoreBundle\Entity\SkillRelUserComment;
  */
 require_once __DIR__.'/../inc/global.inc.php';
 
-$issue = isset($_REQUEST['issue']) ? intval($_REQUEST['issue']) : 0;
-$userId = isset($_REQUEST['user']) ? intval($_REQUEST['user']) : 0;
+$issue = isset($_REQUEST['issue']) ? (int) $_REQUEST['issue'] : 0;
+$userId = isset($_REQUEST['user']) ? (int) $_REQUEST['user'] : 0;
 
 if (empty($issue)) {
     api_not_allowed(true);
@@ -22,8 +22,6 @@ if (empty($issue)) {
 
 $entityManager = Database::getManager();
 $skillIssue = $entityManager->find('ChamiloCoreBundle:SkillRelUser', $issue);
-$skillRepo = $entityManager->getRepository('ChamiloCoreBundle:Skill');
-$skillLevelRepo = $entityManager->getRepository('ChamiloSkillBundle:Level');
 
 if (!$skillIssue) {
     Display::addFlash(
@@ -36,6 +34,9 @@ if (!$skillIssue) {
     exit;
 }
 
+$skillRepo = $entityManager->getRepository('ChamiloCoreBundle:Skill');
+$skillLevelRepo = $entityManager->getRepository('ChamiloSkillBundle:Level');
+
 $user = $skillIssue->getUser();
 $skill = $skillIssue->getSkill();
 
@@ -160,6 +161,7 @@ if ($profile) {
         'profile' => $profileId,
     ]);
 
+    $profileLevels = [];
     foreach ($levels as $level) {
         $profileLevels[$level->getPosition()][$level->getId()] = $level->getName();
     }

+ 1 - 1
main/forum/forumfunction.inc.php

@@ -5496,7 +5496,7 @@ function get_notifications_of_user($user_id = 0, $force = false)
 
     if (!isset($_SESSION['forum_notification']) ||
         $_SESSION['forum_notification']['course'] != $course_id ||
-        $force = true
+        $force == true
     ) {
         $_SESSION['forum_notification']['course'] = $course_id;
 

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

@@ -54,7 +54,7 @@ if (!empty($courseId)) {
 }
 
 $config = [
-    'ToolbarSet' => 'IntroductionTool',
+    'ToolbarSet' => 'Basic',
     'Width' => '100%',
     'Height' => '300',
 ];

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

@@ -355,12 +355,12 @@ class AnnouncementEmail
     public function logMailSent()
     {
         $id = $this->announcement('id');
-        $course_id = $this->course('real_id');
+        $courseId = $this->course('real_id');
         $table = Database::get_course_table(TABLE_ANNOUNCEMENT);
         $sql = "UPDATE $table SET 
                 email_sent = 1
                 WHERE 
-                    c_id = $course_id AND 
+                    c_id = $courseId AND 
                     id = $id AND 
                     session_id = {$this->session_id} 
                 ";

+ 25 - 18
main/inc/lib/AnnouncementManager.php

@@ -410,9 +410,10 @@ class AnnouncementManager
             $html .= "<tr><th style='text-align:right'>$modify_icons</th></tr>";
         }
 
-        $toUser = $itemProperty->getToUser();
-        $toUserId = !empty($toUser) ? $toUser->getId() : 0;
-
+        //$toUser = $itemProperty->getToUser();
+        //$toUserId = !empty($toUser) ? $toUser->getId() : 0;
+        // The user id is always the current one.
+        $toUserId = api_get_user_id();
         $content = self::parse_content(
             $toUserId,
             $content,
@@ -768,7 +769,6 @@ class AnnouncementManager
         $courseId = api_get_course_int_id();
         $tbl_item_property = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $tbl_announcement = Database::get_course_table(TABLE_ANNOUNCEMENT);
-
         $id = intval($id);
 
         $params = [
@@ -824,14 +824,20 @@ class AnnouncementManager
             if (is_array($send_to['groups'])) {
                 foreach ($send_to['groups'] as $group) {
                     $groupInfo = GroupManager::get_group_properties($group);
-                    api_item_property_update(
-                        $courseInfo,
-                        TOOL_ANNOUNCEMENT,
-                        $id,
-                        'AnnouncementUpdated',
-                        api_get_user_id(),
-                        $groupInfo
-                    );
+                    if (empty($groupInfo)) {
+                        // Probably the group id and iid are different try checking the iid
+                        $groupInfo = GroupManager::get_group_properties($group, true);
+                    }
+                    if ($groupInfo) {
+                        api_item_property_update(
+                            $courseInfo,
+                            TOOL_ANNOUNCEMENT,
+                            $id,
+                            'AnnouncementUpdated',
+                            api_get_user_id(),
+                            $groupInfo
+                        );
+                    }
                 }
             }
 
@@ -1067,26 +1073,27 @@ class AnnouncementManager
     {
         $table = Database::get_course_table(TABLE_ITEM_PROPERTY);
         $tool = Database::escape_string($tool);
-        $id = intval($id);
+        $id = (int) $id;
         $courseId = api_get_course_int_id();
 
-        $sql = "SELECT * FROM $table
+        $sql = "SELECT to_user_id, to_group_id FROM $table
                 WHERE c_id = $courseId AND tool='$tool' AND ref = $id";
         $result = Database::query($sql);
         $to = [];
         while ($row = Database::fetch_array($result)) {
-            $to_group = $row['to_group_id'];
-            switch ($to_group) {
+            // This is the iid of c_group_info
+            $toGroup = $row['to_group_id'];
+            switch ($toGroup) {
                 // it was send to one specific user
                 case null:
                     $to[] = "USER:".$row['to_user_id'];
                     break;
                 // it was sent to everyone
                 case 0:
-                    return "everyone";
+                    return 'everyone';
                     break;
                 default:
-                    $to[] = "GROUP:".$row['to_group_id'];
+                    $to[] = "GROUP:".$toGroup;
             }
         }
 

+ 49 - 2
main/inc/lib/ScheduledAnnouncement.php

@@ -96,13 +96,14 @@ class ScheduledAnnouncement extends Model
     /**
      * Returns a Form validator Obj.
      *
+     * @param int    $id
      * @param string $url
      * @param string $action      add, edit
      * @param array  $sessionInfo
      *
      * @return FormValidator form validator obj
      */
-    public function returnSimpleForm($url, $action, $sessionInfo = [])
+    public function returnSimpleForm($id, $url, $action, $sessionInfo = [])
     {
         $form = new FormValidator(
             'announcement',
@@ -114,6 +115,12 @@ class ScheduledAnnouncement extends Model
         $form->addDateTimePicker('date', get_lang('Date'));
         $form->addText('subject', get_lang('Subject'));
         $form->addHtmlEditor('message', get_lang('Message'));
+
+        $extraField = new ExtraField('scheduled_announcement');
+        $extra = $extraField->addElements($form, $id);
+        $js = $extra['jquery_ready_content'];
+        $form->addHtml("<script> $(function() { $js }); </script> ");
+
         $this->setTagsInForm($form);
 
         $form->addCheckBox('sent', null, get_lang('MessageSent'));
@@ -232,6 +239,12 @@ class ScheduledAnnouncement extends Model
         $form->addHtml('</div>');
         $form->addText('subject', get_lang('Subject'));
         $form->addHtmlEditor('message', get_lang('Message'));
+
+        $extraField = new ExtraField('scheduled_announcement');
+        $extra = $extraField->addElements($form);
+        $js = $extra['jquery_ready_content'];
+        $form->addHtml("<script> $(function() { $js }); </script> ");
+
         $this->setTagsInForm($form);
 
         if ($action == 'edit') {
@@ -243,6 +256,37 @@ class ScheduledAnnouncement extends Model
         return $form;
     }
 
+    /**
+     * @param int $id
+     *
+     * @return string
+     */
+    public function getAttachmentToString($id)
+    {
+        $file = $this->getAttachment($id);
+        if (!empty($file) && !empty($file['value'])) {
+            //$file = api_get_uploaded_web_url('schedule_announcement', $id, basename($file['value']));
+            $url = api_get_path(WEB_UPLOAD_PATH).$file['value'];
+
+            return get_lang('Attachment').': '.Display::url(basename($file['value']), $url, ['target' => '_blank']);
+        }
+
+        return '';
+    }
+
+    /**
+     * @param int $id
+     *
+     * @return array
+     */
+    public function getAttachment($id)
+    {
+        $extraFieldValue = new ExtraFieldValue('scheduled_announcement');
+        $attachment = $extraFieldValue->get_values_by_handler_and_field_variable($id, 'attachment');
+
+        return $attachment;
+    }
+
     /**
      * @param int $urlId
      *
@@ -277,6 +321,8 @@ class ScheduledAnnouncement extends Model
                         continue;
                     }
 
+                    $attachments = $this->getAttachmentToString($result['id']);
+
                     self::update(['id' => $result['id'], 'sent' => 1]);
 
                     $subject = $result['subject'];
@@ -299,7 +345,7 @@ class ScheduledAnnouncement extends Model
                                 $progress = Tracking::get_avg_student_progress(
                                     $user['user_id'],
                                     $courseInfo['code'],
-                                    null,
+                                    [],
                                     $sessionId
                                 );
                             }
@@ -345,6 +391,7 @@ class ScheduledAnnouncement extends Model
                             ];
 
                             $message = str_replace(array_keys($tags), $tags, $message);
+                            $message .= $attachments;
 
                             MessageManager::send_message(
                                 $userInfo['user_id'],

+ 7 - 9
main/inc/lib/TicketManager.php

@@ -190,11 +190,11 @@ class TicketManager
      */
     public static function addUsersToCategory($categoryId, $users)
     {
-        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
         if (empty($users) || empty($categoryId)) {
             return false;
         }
 
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
         foreach ($users as $userId) {
             if (self::userIsAssignedToCategory($userId, $categoryId) == false) {
                 $params = [
@@ -704,7 +704,7 @@ class TicketManager
      * @param $ticketId
      * @param $message_id
      *
-     * @return array
+     * @return bool
      */
     public static function saveMessageAttachmentFile(
         $file_attach,
@@ -718,7 +718,6 @@ class TicketManager
             stripslashes($file_attach['name']),
             $file_attach['type']
         );
-        $file_name = $file_attach['name'];
         $table_support_message_attachments = Database::get_main_table(TABLE_TICKET_MESSAGE_ATTACHMENTS);
         if (!filter_extension($new_file_name)) {
             echo Display::return_message(
@@ -1113,7 +1112,6 @@ class TicketManager
     {
         $id = (int) $id;
         $em = Database::getManager();
-
         $item = $em->getRepository('ChamiloTicketBundle:MessageAttachment')->find($id);
         if ($item) {
             return $item;
@@ -1353,7 +1351,7 @@ class TicketManager
                     $onlyToUserId,
                     $titleEmail,
                     $messageEmail,
-                     0,
+                    0,
                     false,
                     false,
                     [],
@@ -1543,14 +1541,14 @@ class TicketManager
     }
 
     /**
-     * @throws \Doctrine\DBAL\DBALException
+     * Close old tickets.
      */
     public static function close_old_tickets()
     {
-        $table_support_tickets = Database::get_main_table(TABLE_TICKET_TICKET);
+        $table = Database::get_main_table(TABLE_TICKET_TICKET);
         $now = api_get_utc_datetime();
         $userId = api_get_user_id();
-        $sql = "UPDATE $table_support_tickets
+        $sql = "UPDATE $table
                 SET
                     status_id = '".self::STATUS_CLOSE."',
                     sys_lastedit_user_id ='$userId',
@@ -1667,7 +1665,7 @@ class TicketManager
                           OR user.username LIKE '%$keyword%')  ";
             }
         }
-        //Search advanced
+        // Search advanced
         if (isset($_GET['submit_advanced'])) {
             $keyword_category = Database::escape_string(
                 trim($_GET['keyword_category'])

+ 0 - 1
main/inc/lib/agenda.lib.php

@@ -1300,7 +1300,6 @@ class Agenda
                         }
                     }
                 }
-
                 break;
         }
 

+ 87 - 33
main/inc/lib/api.lib.php

@@ -149,6 +149,7 @@ define('TOOL_GRADEBOOK', 'gradebook');
 define('TOOL_NOTEBOOK', 'notebook');
 define('TOOL_ATTENDANCE', 'attendance');
 define('TOOL_COURSE_PROGRESS', 'course_progress');
+define('TOOL_PORTFOLIO', 'portfolio');
 
 // CONSTANTS defining Chamilo interface sections
 define('SECTION_CAMPUS', 'mycampus');
@@ -279,15 +280,17 @@ define('USERNAME_PURIFIER_SHALLOW', '/\s/');
 define('IS_WINDOWS_OS', api_is_windows_os());
 
 // Checks for installed optional php-extensions.
-define('INTL_INSTALLED', function_exists('intl_get_error_code')); // intl extension (from PECL), it is installed by default as of PHP 5.3.0
-define('ICONV_INSTALLED', function_exists('iconv')); // iconv extension, for PHP5 on Windows it is installed by default.
+// intl extension (from PECL), it is installed by default as of PHP 5.3.0.
+define('INTL_INSTALLED', function_exists('intl_get_error_code'));
+// iconv extension, for PHP5 on Windows it is installed by default.
+define('ICONV_INSTALLED', function_exists('iconv'));
 define('MBSTRING_INSTALLED', function_exists('mb_strlen')); // mbstring extension.
 
-// Patterns for processing paths.                                   // Examples:
+// Patterns for processing paths. Examples.
 define('REPEATED_SLASHES_PURIFIER', '/\/{2,}/'); // $path = preg_replace(REPEATED_SLASHES_PURIFIER, '/', $path);
 define('VALID_WEB_PATH', '/https?:\/\/[^\/]*(\/.*)?/i'); // $is_valid_path = preg_match(VALID_WEB_PATH, $path);
-define('VALID_WEB_SERVER_BASE', '/https?:\/\/[^\/]*/i'); // $new_path = preg_replace(VALID_WEB_SERVER_BASE, $new_base, $path);
-
+// $new_path = preg_replace(VALID_WEB_SERVER_BASE, $new_base, $path);
+define('VALID_WEB_SERVER_BASE', '/https?:\/\/[^\/]*/i');
 // Constants for api_get_path() and api_get_path_type(), etc. - registered path types.
 // basic (leaf elements)
 define('REL_CODE_PATH', 'REL_CODE_PATH');
@@ -2038,7 +2041,7 @@ function api_get_course_info($course_code = null, $strict = false)
  *
  * @return \Chamilo\CoreBundle\Entity\Course
  */
-function api_get_course_entity($courseId)
+function api_get_course_entity($courseId = 0)
 {
     if (empty($courseId)) {
         $courseId = api_get_course_int_id();
@@ -2047,6 +2050,20 @@ function api_get_course_entity($courseId)
     return CourseManager::getManager()->find($courseId);
 }
 
+/**
+ * @param int $id
+ *
+ * @return \Chamilo\CoreBundle\Entity\Session
+ */
+function api_get_session_entity($id = 0)
+{
+    if (empty($id)) {
+        $id = api_get_session_id();
+    }
+
+    return Database::getManager()->getRepository('ChamiloCoreBundle:Session')->find($id);
+}
+
 /**
  * Returns the current course info array.
 
@@ -2180,7 +2197,8 @@ function api_format_course_array($course_data)
             null,
             null,
             null,
-            true
+            true,
+            false
         );
     }
     $_course['course_image_large'] = $url_image;
@@ -2597,11 +2615,11 @@ function api_get_session_image($session_id, $status_id)
     if ((int) $status_id != 5) { //check whether is not a student
         if ($session_id > 0) {
             $session_img = "&nbsp;&nbsp;".Display::return_icon(
-                    'star.png',
-                    get_lang('SessionSpecificResource'),
-                    ['align' => 'absmiddle'],
-                    ICON_SIZE_SMALL
-                );
+                'star.png',
+                get_lang('SessionSpecificResource'),
+                ['align' => 'absmiddle'],
+                ICON_SIZE_SMALL
+            );
         }
     }
 
@@ -2835,6 +2853,11 @@ function api_get_settings_params($params)
     return $result;
 }
 
+/**
+ * @param array $params example: [id = ? => '1']
+ *
+ * @return array
+ */
 function api_get_settings_params_simple($params)
 {
     $table = Database::get_main_table(TABLE_MAIN_SETTINGS_CURRENT);
@@ -3072,7 +3095,7 @@ function api_is_coach($session_id = 0, $courseId = null, $check_student_view = t
 
     $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
     $session_rel_course_rel_user_table = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
-    $sessionIsCoach = null;
+    $sessionIsCoach = [];
 
     if (!empty($courseId)) {
         $sql = "SELECT DISTINCT s.id, name, access_start_date, access_end_date
@@ -4088,17 +4111,17 @@ function api_item_property_update(
 
     $to_group_id = 0;
     if (!empty($groupInfo) && isset($groupInfo['iid'])) {
-        $to_group_id = $groupInfo['iid'];
+        $to_group_id = (int) $groupInfo['iid'];
     }
 
     $em = Database::getManager();
 
     // Definition of variables.
     $tool = Database::escape_string($tool);
-    $item_id = intval($item_id);
+    $item_id = (int) $item_id;
     $lastEditTypeNoFilter = $last_edit_type;
     $last_edit_type = Database::escape_string($last_edit_type);
-    $user_id = intval($user_id);
+    $user_id = (int) $user_id;
 
     $startVisible = "NULL";
     if (!empty($start_visible)) {
@@ -4319,7 +4342,12 @@ function api_item_property_update(
             $user_id = api_get_anonymous_id();
             $objUser = api_get_user_entity($user_id);
         }
-        $objGroup = $em->find('ChamiloCourseBundle:CGroupInfo', intval($to_group_id));
+
+        $objGroup = null;
+        if (!empty($to_group_id)) {
+            $objGroup = $em->find('ChamiloCourseBundle:CGroupInfo', $to_group_id);
+        }
+
         $objToUser = api_get_user_entity($to_user_id);
         $objSession = $em->find('ChamiloCoreBundle:Session', intval($session_id));
 
@@ -4648,7 +4676,7 @@ function api_get_languages_combo($name = 'language')
  * @param  bool Hide form if only one language available (defaults to false = show the box anyway)
  * @param bool $showAsButton
  *
- * @return string Display the box directly
+ * @return null|string Display the box directly
  */
 function api_display_language_form($hide_if_no_choice = false, $showAsButton = false)
 {
@@ -6867,7 +6895,7 @@ function api_browser_support($format = '')
             return false;
         }
     } elseif ($format == 'pdf') {
-        //native pdf support
+        // native pdf support
         if ($current_browser == 'Chrome' && $current_majorver >= 6) {
             $result[$format] = true;
 
@@ -7103,6 +7131,9 @@ function api_get_asset($file)
 /**
  * Returns the <script> HTML tag.
  *
+ * @param string $file
+ * @param string $media
+ *
  * @return string
  */
 function api_get_css_asset($file, $media = 'screen')
@@ -7114,6 +7145,7 @@ function api_get_css_asset($file, $media = 'screen')
  * Returns the <link> HTML tag.
  *
  * @param string $file
+ * @param string $media
  */
 function api_get_css($file, $media = 'screen')
 {
@@ -7617,11 +7649,17 @@ function api_set_default_visibility(
     $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
     $userId = empty($userId) ? api_get_user_id() : $userId;
 
-    if (empty($group_id)) {
-        $group_id = api_get_group_id();
+    // if group is null force group_id = 0, this force is needed to create a LP folder with group = 0
+    if (is_null($group_id)) {
+        $group_id = 0;
+    } else {
+        $group_id = empty($group_id) ? api_get_group_id() : $group_id;
     }
 
-    $groupInfo = GroupManager::get_group_properties($group_id);
+    $groupInfo = [];
+    if (!empty($group_id)) {
+        $groupInfo = GroupManager::get_group_properties($group_id);
+    }
     $original_tool_id = $tool_id;
 
     switch ($tool_id) {
@@ -9034,16 +9072,31 @@ function api_upload_file($type, $file, $itemId, $cropParameters = '')
  *
  * @return bool
  */
-function api_get_uploaded_file($type, $itemId, $file)
+function api_get_uploaded_web_url($type, $itemId, $file)
+{
+    return api_get_uploaded_file($type, $itemId, $file, true);
+}
+
+/**
+ * @param string $type
+ * @param int    $itemId
+ * @param string $file
+ * @param bool   $getUrl
+ *
+ * @return bool
+ */
+function api_get_uploaded_file($type, $itemId, $file, $getUrl = false)
 {
     $itemId = (int) $itemId;
     $pathId = '/'.substr((string) $itemId, 0, 1).'/'.$itemId.'/';
     $path = api_get_path(SYS_UPLOAD_PATH).$type.$pathId;
-
     $file = basename($file);
-
     $file = $path.'/'.$file;
-    if (file_exists($file)) {
+    if (Security::check_abs_path($file, $path) && is_file($file) && file_exists($file)) {
+        if ($getUrl) {
+            return str_replace(api_get_path(SYS_UPLOAD_PATH), api_get_path(WEB_UPLOAD_PATH), $file);
+        }
+
         return $file;
     }
 
@@ -9074,8 +9127,9 @@ function api_download_uploaded_file($type, $itemId, $file, $title = '')
  */
 function api_remove_uploaded_file($type, $file)
 {
-    $path = api_get_path(SYS_UPLOAD_PATH).$type.'/'.$file;
-    if (file_exists($path)) {
+    $typePath = api_get_path(SYS_UPLOAD_PATH).$type;
+    $path = $typePath.'/'.$file;
+    if (Security::check_abs_path($path, $typePath) && file_exists($path) && is_file($path)) {
         unlink($path);
     }
 }
@@ -9106,18 +9160,18 @@ function api_float_val($number)
  * 3.141516 => 3.14
  * 3,141516 => 3,14
  *
- * @todo WIP
- *
- * @param string $number   number in iso code
+ * @param string $number            number in iso code
  * @param int    $decimals
+ * @param string $decimalSeparator
+ * @param string $thousandSeparator
  *
  * @return bool|string
  */
-function api_number_format($number, $decimals = 0)
+function api_number_format($number, $decimals = 0, $decimalSeparator = '.', $thousandSeparator = ',')
 {
     $number = api_float_val($number);
 
-    return number_format($number, $decimals);
+    return number_format($number, $decimals, $decimalSeparator, $thousandSeparator);
 }
 
 /**

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

@@ -43,7 +43,7 @@ class Attendance
      *
      * @see SortableTable#get_total_number_of_items()
      */
-    public static function get_number_of_attendances($active = -1)
+    public static function getNumberOfAttendances($active = -1)
     {
         $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
         $session_id = api_get_session_id();

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

@@ -686,7 +686,7 @@ class Auth
             $form->addElement('hidden', 'sec_token', Security::getTokenFromSession());
             $form->addElement('hidden', 'subscribe_user_with_password', $all_course_information['code']);
             $form->addElement('text', 'course_registration_code');
-            $form->addButton('submit', get_lang('SubmitRegistrationCode'));
+            $form->addButtonSave(get_lang('SubmitRegistrationCode'));
             $content = $form->returnForm();
 
             return ['message' => $message, 'content' => $content];
@@ -736,12 +736,8 @@ class Auth
             $sql .= "LIMIT {$limit['start']}, {$limit['length']} ";
         }
 
-        $ids = Database::store_result(
-            Database::query($sql)
-        );
-
+        $ids = Database::store_result(Database::query($sql));
         $sessions = [];
-
         foreach ($ids as $id) {
             $sessions[] = $em->find('ChamiloCoreBundle:Session', $id);
         }

+ 588 - 8
main/inc/lib/career.lib.php

@@ -312,6 +312,7 @@ class Career extends Model
         if (!($graph instanceof Graph)) {
             return '';
         }
+
         // Getting max column
         $maxColumn = 0;
         foreach ($graph->getVertices() as $vertex) {
@@ -328,7 +329,6 @@ class Career extends Model
             $groupData = explode(':', $group);
             $group = $groupData[0];
             $groupLabel = isset($groupData[1]) ? $groupData[1] : '';
-
             $subGroup = $vertex->getAttribute('SubGroup');
             $subGroupData = explode(':', $subGroup);
             $column = $vertex->getGroup();
@@ -349,7 +349,6 @@ class Career extends Model
         $connections = '';
         $groupDrawLine = [];
         $groupCourseList = [];
-
         // Read Connections column
         foreach ($list as $group => $subGroupList) {
             foreach ($subGroupList as $subGroupData) {
@@ -453,6 +452,583 @@ class Career extends Model
         return $graphHtml;
     }
 
+    /**
+     * @param Graph    $graph
+     * @param Template $tpl
+     *
+     * @return string
+     */
+    public static function renderDiagramByColumn($graph, $tpl)
+    {
+        if (!($graph instanceof Graph)) {
+            return '';
+        }
+
+        // Getting max column
+        $maxColumn = 0;
+        foreach ($graph->getVertices() as $vertex) {
+            $groupId = (int) $vertex->getGroup();
+            if ($groupId > $maxColumn) {
+                $maxColumn = $groupId;
+            }
+        }
+
+        $list = [];
+        $subGroups = [];
+        /** @var Vertex $vertex */
+        foreach ($graph->getVertices() as $vertex) {
+            $column = $vertex->getGroup();
+            $group = $vertex->getAttribute('Group');
+
+            $groupData = explode(':', $group);
+            $group = $groupData[0];
+            $groupLabel = isset($groupData[1]) ? $groupData[1] : '';
+
+            $subGroup = $vertex->getAttribute('SubGroup');
+            $subGroupData = explode(':', $subGroup);
+
+            $row = $vertex->getAttribute('Row');
+            $subGroupId = $subGroupData[0];
+            $subGroupLabel = isset($subGroupData[1]) ? $subGroupData[1] : '';
+
+            if (!empty($subGroupId) && !in_array($subGroupId, $subGroups)) {
+                //$subGroups[$subGroupId][] = $vertex->getId();
+                $subGroups[$subGroupId]['items'][] = $vertex->getId();
+                $subGroups[$subGroupId]['label'] = $subGroupLabel;
+            }
+
+            $list[$column]['rows'][$row]['items'][] = $vertex;
+            $list[$column]['rows'][$row]['label'] = $subGroupId;
+            $list[$column]['rows'][$row]['group'] = $group;
+            $list[$column]['rows'][$row]['group_label'] = $groupLabel;
+            $list[$column]['rows'][$row]['subgroup'] = $subGroup;
+            $list[$column]['rows'][$row]['subgroup_label'] = $subGroupLabel;
+            //$list[$column]['label'] = $subGroupLabel;
+            $list[$column]['label'] = $groupLabel;
+            $list[$column]['column'] = $column;
+        }
+
+        $connections = '';
+        $groupDrawLine = [];
+        $groupCourseList = [];
+        $simpleConnectionList = [];
+
+        // Read Connections column
+        foreach ($list as $column => $groupList) {
+            foreach ($groupList['rows'] as $subGroupList) {
+                /** @var Vertex $vertex */
+                foreach ($subGroupList['items'] as $vertex) {
+                    if ($vertex instanceof Vertex) {
+                        $rowId = $vertex->getId();
+                        $groupCourseList[$vertex->getAttribute('Column')][] = $vertex->getId();
+                        $connectionList = $vertex->getAttribute('Connections');
+                        if (empty($connectionList)) {
+                            continue;
+                        }
+                        $firstConnection = '';
+                        $secondConnection = '';
+                        $simpleFirstConnection = '';
+                        $simpleSecondConnection = '';
+
+                        $explode = explode('-', $connectionList);
+                        $pos = strpos($explode[0], 'SG');
+                        if ($pos === false) {
+                            $pos = strpos($explode[0], 'G');
+                            if (is_numeric($pos)) {
+                                // group_123 id
+                                $groupValueId = (int) str_replace(
+                                    'G',
+                                    '',
+                                    $explode[0]
+                                );
+                                $secondConnection = 'group_'.$groupValueId;
+                                $firstConnection = 'row_'.(int) $rowId;
+                                $groupDrawLine[$groupValueId] = true;
+
+                                $simpleSecondConnection = 'g'.$groupValueId;
+                                $simpleFirstConnection = 'v'.(int) $rowId;
+                            } else {
+                                // Course block (row_123 id)
+                                if (!empty($explode[0])) {
+                                    $firstConnection = 'row_'.(int) $explode[0];
+                                    $simpleFirstConnection = 'v'.(int) $explode[0];
+                                }
+                            }
+                        } else {
+                            // subgroup__123 id
+                            $firstConnection = 'subgroup_'.(int) str_replace('SG', '', $explode[0]);
+                            $simpleFirstConnection = 'sg'.(int) str_replace('SG', '', $explode[0]);
+                        }
+
+                        $pos = false;
+                        if (isset($explode[1])) {
+                            $pos = strpos($explode[1], 'SG');
+                        }
+                        if ($pos === false) {
+                            if (isset($explode[1])) {
+                                $pos = strpos($explode[1], 'G');
+                                $value = $explode[1];
+                            }
+                            if (is_numeric($pos)) {
+                                $groupValueId = (int) str_replace(
+                                    'G',
+                                    '',
+                                    $value
+                                );
+                                $secondConnection = 'group_'.$groupValueId;
+                                $simpleSecondConnection = 'g'.(int) $groupValueId;
+                                $groupDrawLine[$groupValueId] = true;
+                            } else {
+                                // Course block (row_123 id)
+                                if (!empty($explode[0]) && isset($explode[1])) {
+                                    $secondConnection = 'row_'.(int) $explode[1];
+                                    $simpleSecondConnection = 'v'.(int) $explode[1];
+                                }
+                            }
+                        } else {
+                            $secondConnection = 'subgroup_'.(int) str_replace('SG', '', $explode[1]);
+                            $simpleSecondConnection = 'sg'.(int) str_replace('SG', '', $explode[1]);
+                        }
+
+                        if (!empty($firstConnection) && !empty($firstConnection)) {
+                            $simpleConnectionList[] = [
+                                'from' => $simpleFirstConnection,
+                                'to' => $simpleSecondConnection,
+                            ];
+                            $connections .= self::createConnection(
+                                $firstConnection,
+                                $secondConnection,
+                                ['Left', 'Right']
+                            );
+                        }
+                    }
+                }
+            }
+        }
+
+        $graphHtml = '<div id ="career_grid">';
+        $groupsBetweenColumns = [];
+        foreach ($list as $column => $columnList) {
+            foreach ($columnList['rows'] as $subGroupList) {
+                $newGroup = $subGroupList['group'];
+                $label = $subGroupList['group_label'];
+                $newOrder[$newGroup]['items'][] = $subGroupList;
+                $newOrder[$newGroup]['label'] = $label;
+                $groupsBetweenColumns[$newGroup][] = $subGroupList;
+            }
+        }
+
+        // Creates graph
+        $graph = new stdClass();
+        $graph->blockWidth = 240;
+        $graph->blockHeight = 120;
+        $graph->xGap = 70;
+        $graph->yGap = 40;
+        $graph->xDiff = 70;
+        $graph->yDiff = 40;
+        $graph->groupXGap = 50;
+
+        foreach ($groupsBetweenColumns as $group => $items) {
+            self::parseColumnList($groupCourseList, $items, '', $graph, $simpleConnectionList);
+        }
+
+        $graphHtml .= '</div>';
+//        $graphHtml .= $connections;
+        $graphHtml .= '<style>
+            panel-title
+             #career_grid {
+                 display: grid;
+                 grid-gap: 40px;
+                 grid-template-columns: repeat(6, [col] auto ) ;
+                 grid-template-rows: repeat(2, [row] auto);
+                 background-color: #fff;
+                 color: #444;
+                 justify-items: stretch;
+                 align-items: start;
+                 align-content: start;
+                 justify-content: stretch;
+             }
+             .group_class {
+                 border: solid 2px;
+                 padding: 8px;
+             }
+             .panel-title {
+                font-size: 11px;
+             }
+             </style>
+             ';
+
+        // Create groups
+        if (!empty($graph->groupList)) {
+            $groupList = [];
+            $groupDiffX = 20;
+            $groupDiffY = 10;
+            $style = 'whiteSpace=wrap;rounded;strokeColor=red;fillColor=none;strokeWidth=2;align=left;verticalAlign=top;';
+            foreach ($graph->groupList as $id => $data) {
+                if (empty($id)) {
+                    continue;
+                }
+
+                $x = $data['min_x'] - $groupDiffX;
+                $y = $data['min_y'] - $groupDiffY;
+                $width = $data['max_width'] + ($groupDiffX * 2);
+                $height = $data['max_height'] + $groupDiffY * 2;
+                $label = '<h4>'.$data['label'].'</h4>';
+                $vertexData = "var g$id = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
+                $groupList[] = $vertexData;
+            }
+            $tpl->assign('group_list', $groupList);
+        }
+
+        // Create subgroups
+        $subGroupList = [];
+        $subGroupListData = [];
+        foreach ($subGroups as $subGroupId => $vertexData) {
+            $label = $vertexData['label'];
+            $vertexIdList = $vertexData['items'];
+            foreach ($vertexIdList as $rowId) {
+                $data = $graph->allData[$rowId];
+                $originalRow = $data['row'];
+                $column = $data['column'];
+                $x = $data['x'];
+                $y = $data['y'];
+                $width = $data['width'];
+                $height = $data['height'];
+
+                if (!isset($subGroupListData[$subGroupId])) {
+                    $subGroupListData[$subGroupId]['min_x'] = 1000;
+                    $subGroupListData[$subGroupId]['min_y'] = 1000;
+                    $subGroupListData[$subGroupId]['max_width'] = 0;
+                    $subGroupListData[$subGroupId]['max_height'] = 0;
+                    $subGroupListData[$subGroupId]['label'] = $label;
+                }
+
+                if ($x < $subGroupListData[$subGroupId]['min_x']) {
+                    $subGroupListData[$subGroupId]['min_x'] = $x;
+                }
+
+                if ($y < $subGroupListData[$subGroupId]['min_y']) {
+                    $subGroupListData[$subGroupId]['min_y'] = $y;
+                }
+
+                /*$originalRow--;
+                $column--;*/
+                $subGroupListData[$subGroupId]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $subGroupListData[$subGroupId]['min_x'];
+                $subGroupListData[$subGroupId]['max_height'] = ($originalRow + 1) * ($height + $graph->yGap) - $subGroupListData[$subGroupId]['min_y'];
+            }
+
+            $style = 'whiteSpace=wrap;rounded;dashed=1;strokeColor=blue;fillColor=none;strokeWidth=2;align=left;verticalAlign=bottom;';
+            $subGroupDiffX = 5;
+            foreach ($subGroupListData as $subGroupId => $data) {
+                $x = $data['min_x'] - $subGroupDiffX;
+                $y = $data['min_y'] - $subGroupDiffX;
+                $width = $data['max_width'] + $subGroupDiffX * 2;
+                $height = $data['max_height'] + $subGroupDiffX * 2;
+                $label = '<h4>'.$data['label'].'</h4>';
+                $vertexData = "var sg$subGroupId = graph.insertVertex(parent, null, '$label', $x, $y, $width, $height, '$style');";
+                $subGroupList[] = $vertexData;
+            }
+        }
+
+        // Create connections (arrows)
+        if (!empty($simpleConnectionList)) {
+            $connectionList = [];
+            //$style = 'endArrow=classic;html=1;strokeWidth=4;exitX=1;exitY=0.5;entryX=0;entryY=0.5;';
+            $style = '';
+            foreach ($simpleConnectionList as $connection) {
+                $from = $connection['from'];
+                $to = $connection['to'];
+                $vertexData = "var e1 = graph.insertEdge(parent, null, '', $from, $to, '$style')";
+                $connectionList[] = $vertexData;
+            }
+            $tpl->assign('connections', $connectionList);
+        }
+
+        $tpl->assign('subgroup_list', $subGroupList);
+        $tpl->assign('vertex_list', $graph->elementList);
+
+        $graphHtml .= '<div id="graphContainer"></div>';
+
+        return $graphHtml;
+    }
+
+    public static function parseColumnList($groupCourseList, $columnList, $width, &$graph, &$connections)
+    {
+        $graphHtml = '';
+        $oldGroup = null;
+        $newOrder = [];
+        foreach ($columnList as $key => $subGroupList) {
+            $newGroup = $subGroupList['group'];
+            $label = $subGroupList['group_label'];
+            $newOrder[$newGroup]['items'][] = $subGroupList;
+            $newOrder[$newGroup]['label'] = $label;
+        }
+
+        foreach ($newOrder as $newGroup => $data) {
+            $label = $data['label'];
+            $subGroupList = $data['items'];
+
+            if (!isset($graph->groupList[$newGroup])) {
+                $graph->groupList[$newGroup]['min_x'] = 1000;
+                $graph->groupList[$newGroup]['min_y'] = 1000;
+                $graph->groupList[$newGroup]['max_width'] = 0;
+                $graph->groupList[$newGroup]['max_height'] = 0;
+                $graph->groupList[$newGroup]['label'] = $label;
+            }
+
+            $maxColumn = 0;
+            $maxRow = 0;
+            $minColumn = 100;
+            $minRow = 100;
+            foreach ($subGroupList as $item) {
+                /** @var Vertex $vertex */
+                foreach ($item['items'] as $vertex) {
+                    $column = $vertex->getAttribute('Column');
+                    $realRow = $vertex->getAttribute('Row');
+
+                    if ($column > $maxColumn) {
+                        $maxColumn = $column;
+                    }
+                    if ($realRow > $maxRow) {
+                        $maxRow = $realRow;
+                    }
+
+                    if ($column < $minColumn) {
+                        $minColumn = $column;
+                    }
+                    if ($realRow < $minRow) {
+                        $minRow = $realRow;
+                    }
+                }
+            }
+
+            if (!empty($newGroup)) {
+                $graphHtml .= '<div 
+                    id ="group_'.$newGroup.'"
+                    class="group'.$newGroup.' group_class" 
+                    style="display:grid; 
+                        align-self: start;
+                        grid-gap: 10px;                                     
+                        justify-items: stretch;
+                        align-items: start;
+                        align-content: start;	
+                        justify-content: stretch;	
+                        grid-area:'.$minRow.'/'.$minColumn.'/'.$maxRow.'/'.$maxColumn.'">'; //style="display:grid"
+            }
+
+            $addRow = 0;
+            if (!empty($label)) {
+                $graphHtml .= "<div class='my_label' style='grid-area:$minRow/$minColumn/$maxRow/$maxColumn'>$label</div>";
+                $addRow = 1;
+            }
+
+            foreach ($subGroupList as $item) {
+                $graphHtml .= self::parseVertexList(
+                    $groupCourseList,
+                    $item['items'],
+                    $addRow,
+                    $graph,
+                    $newGroup,
+                    $connections
+                );
+            }
+
+            if (!empty($newGroup)) {
+                $graphHtml .= '</div >';
+            }
+        }
+
+        return $graphHtml;
+    }
+
+    public static function parseVertexList($groupCourseList, $vertexList, $addRow = 0, &$graph, $group, &$connections)
+    {
+        if (empty($vertexList)) {
+            return '';
+        }
+
+        $graphHtml = '';
+        /** @var Vertex $vertex */
+        foreach ($vertexList as $vertex) {
+            $column = $vertex->getAttribute('Column');
+            $realRow = $originalRow = $vertex->getAttribute('Row');
+            if ($addRow) {
+                $realRow = $realRow + $addRow;
+            }
+            $id = $vertex->getId();
+            $area = "$realRow/$column";
+            $graphHtml .= '<div 
+                id = "row_wrapper_'.$id.'"   
+                data= "'.$originalRow.'-'.$column.'"                            
+                style="
+                    align-self: start;
+                    justify-content: stretch; 
+                    grid-area:'.$area.'"
+            >';
+            $color = '';
+            if (!empty($vertex->getAttribute('DefinedColor'))) {
+                $color = $vertex->getAttribute('DefinedColor');
+            }
+            $content = '<div class="pull-left">'.$vertex->getAttribute('Notes').'</div>';
+            $content .= '<div class="pull-right">['.$id.']</div>';
+
+            $title = $vertex->getAttribute('graphviz.label');
+            if (!empty($vertex->getAttribute('LinkedElement'))) {
+                $title = Display::url($title, $vertex->getAttribute('LinkedElement'));
+            }
+
+            $originalRow--;
+            $column--;
+            //$title = "$originalRow / $column";
+            $graphHtml .= Display::panel(
+                $content,
+                $title,
+                null,
+                null,
+                null,
+                "row_$id",
+                $color
+            );
+
+            $panel = Display::panel(
+                $content,
+                $title,
+                null,
+                null,
+                null,
+                "row_$id",
+                $color
+            );
+
+            $x = $column * $graph->blockWidth + $graph->xDiff;
+            $y = $originalRow * $graph->blockHeight + $graph->yDiff;
+
+            $width = $graph->blockWidth - $graph->xGap;
+            $height = $graph->blockHeight - $graph->yGap;
+
+            $style = 'text;html=1;strokeColor=green;fillColor=#ffffff;overflow=fill;rounded=0;align=left;';
+
+            $panel = str_replace(["\n", "\r"], '', $panel);
+            //$panel = $title.' - '.$group.' row: '.$originalRow.' heigh: '.$height.' id: '.$id;
+            $vertexData = "var v$id = graph.insertVertex(parent, null, '".addslashes($panel)."', $x, $y, $width, $height, '$style');";
+
+            $graph->elementList[$id] = $vertexData;
+            $graph->allData[$id] = [
+                'x' => $x,
+                'y' => $y,
+                'width' => $width,
+                'height' => $height,
+                'row' => $originalRow,
+                'column' => $column,
+                'label' => $title,
+            ];
+
+            if ($x < $graph->groupList[$group]['min_x']) {
+                $graph->groupList[$group]['min_x'] = $x;
+            }
+
+            if ($y < $graph->groupList[$group]['min_y']) {
+                $graph->groupList[$group]['min_y'] = $y;
+            }
+
+            $graph->groupList[$group]['max_width'] = ($column + 1) * ($width + $graph->xGap) - $graph->groupList[$group]['min_x'];
+            $graph->groupList[$group]['max_height'] = ($originalRow + 1) * ($height + ($graph->yGap)) - $graph->groupList[$group]['min_y'];
+
+            $graphHtml .= '</div>';
+            $arrow = $vertex->getAttribute('DrawArrowFrom');
+            $found = false;
+            if (!empty($arrow)) {
+                $pos = strpos($arrow, 'SG');
+                if ($pos === false) {
+                    $pos = strpos($arrow, 'G');
+                    if (is_numeric($pos)) {
+                        $parts = explode('G', $arrow);
+                        if (empty($parts[0]) && count($parts) == 2) {
+                            $groupArrow = $parts[1];
+                            $graphHtml .= self::createConnection(
+                                "group_$groupArrow",
+                                "row_$id",
+                                ['Left', 'Right']
+                            );
+                            $found = true;
+                            $connections[] = [
+                              'from' => "g$groupArrow",
+                              'to' => "v$id",
+                            ];
+                        }
+                    }
+                } else {
+                    // Case is only one subgroup value example: SG1
+                    $parts = explode('SG', $arrow);
+                    if (empty($parts[0]) && count($parts) == 2) {
+                        $subGroupArrow = $parts[1];
+                        $graphHtml .= self::createConnection(
+                            "subgroup_$subGroupArrow",
+                            "row_$id",
+                            ['Left', 'Right']
+                        );
+                        $found = true;
+                        $connections[] = [
+                            'from' => "sg$subGroupArrow",
+                            'to' => "v$id",
+                        ];
+                    }
+                }
+
+                if ($found == false) {
+                    // case is connected to 2 subgroups: Example SG1-SG2
+                    $parts = explode('-', $arrow);
+                    if (count($parts) == 2 && !empty($parts[0]) && !empty($parts[1])) {
+                        $defaultArrow = ['Top', 'Bottom'];
+                        $firstPrefix = '';
+                        $firstId = '';
+                        $secondId = '';
+                        $secondPrefix = '';
+                        if (is_numeric($pos = strpos($parts[0], 'SG'))) {
+                            $firstPrefix = 'sg';
+                            $firstId = str_replace('SG', '', $parts[0]);
+                        }
+
+                        if (is_numeric($pos = strpos($parts[1], 'SG'))) {
+                            $secondPrefix = 'sg';
+                            $secondId = str_replace('SG', '', $parts[1]);
+                        }
+                        if (!empty($secondId) && !empty($firstId)) {
+                            $connections[] = [
+                                'from' => $firstPrefix.$firstId,
+                                'to' => $secondPrefix.$secondId,
+                                $defaultArrow,
+                            ];
+                            $found = true;
+                        }
+                    }
+                }
+
+                if ($found == false) {
+                    // case DrawArrowFrom is an integer
+                    $defaultArrow = ['Left', 'Right'];
+                    if (isset($groupCourseList[$column]) &&
+                        in_array($arrow, $groupCourseList[$column])
+                    ) {
+                        $defaultArrow = ['Top', 'Bottom'];
+                    }
+                    $graphHtml .= self::createConnection(
+                        "row_$arrow",
+                        "row_$id",
+                        $defaultArrow
+                    );
+
+                    $connections[] = [
+                        'from' => "v$arrow",
+                        'to' => "v$id",
+                    ];
+                }
+            }
+        }
+
+        return $graphHtml;
+    }
+
     /**
      * @param array  $groupCourseList list of groups and their courses
      * @param int    $group
@@ -480,8 +1056,10 @@ class Career extends Model
 
         $groupIdTag = "group_$group";
         $borderLine = $showGroupLine === true ? 'border-style:solid;' : '';
-        // padding:15px;
-        $graphHtml = '<div id="'.$groupIdTag.'" class="career_group" style=" '.$borderLine.' padding:15px; float:left; margin-left:'.$leftGroup.'; width:'.$widthGroup.'%">';
+
+        $graphHtml = '<div 
+            id="'.$groupIdTag.'" class="career_group" 
+            style=" '.$borderLine.' padding:15px; float:left; margin-left:'.$leftGroup.'; width:'.$widthGroup.'%">';
 
         if (!empty($groupLabel)) {
             $graphHtml .= '<h3>'.$groupLabel.'</h3>';
@@ -501,7 +1079,9 @@ class Career extends Model
             }
 
             // padding:15px;
-            $graphHtml .= '<div id="subgroup_'.$subGroup.'" class="career_subgroup" style="'.$line.' margin-bottom:20px; padding:15px; float:left; margin-left:0px; width:100%">';
+            $graphHtml .= '<div 
+                id="subgroup_'.$subGroup.'" class="career_subgroup" 
+                style="'.$line.' margin-bottom:20px; padding:15px; float:left; margin-left:0px; width:100%">';
             if (!empty($subGroupLabel)) {
                 $graphHtml .= '<h3>'.$subGroupLabel.'</h3>';
             }
@@ -515,7 +1095,9 @@ class Career extends Model
                 }
 
                 $widthColumn = 85 / count($columnList);
-                $graphHtml .= '<div id="col_'.$column.'" class="career_column" style="padding:15px;float:left; margin-left:'.$leftColumn.'; width:'.$widthColumn.'%">';
+                $graphHtml .= '<div 
+                    id="col_'.$column.'" class="career_column" 
+                    style="padding:15px;float:left; margin-left:'.$leftColumn.'; width:'.$widthColumn.'%">';
                 $maxRow = 0;
                 foreach ($rows as $row => $vertex) {
                     if ($row > $maxRow) {
@@ -613,8 +1195,6 @@ class Career extends Model
                                     $parts = explode('SG', $arrow);
                                     if (empty($parts[0]) && count($parts) == 2) {
                                         $subGroupArrow = $parts[1];
-                                        /*var_dump($subGroupArrow);
-                                        var_dump(array_keys($subGroupList));*/
                                         $graphHtml .= self::createConnection(
                                             "subgroup_$subGroupArrow",
                                             "row_$id",

+ 24 - 24
main/inc/lib/certificate.lib.php

@@ -40,16 +40,18 @@ class Certificate extends Model
     /**
      * Constructor.
      *
-     * @param int  $certificate_id   ID of the certificate
+     * @param int  $certificate_id        ID of the certificate
      * @param int  $userId
-     * @param bool $sendNotification send message to student
+     * @param bool $sendNotification      send message to student
+     * @param bool $updateCertificateData
      *
      * If no ID given, take user_id and try to generate one
      */
     public function __construct(
         $certificate_id = 0,
         $userId = 0,
-        $sendNotification = false
+        $sendNotification = false,
+        $updateCertificateData = true
     ) {
         $this->table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
         $this->user_id = !empty($userId) ? $userId : api_get_user_id();
@@ -119,7 +121,8 @@ class Certificate extends Model
                 self::updateUserCertificateInfo(
                     0,
                     $this->user_id,
-                    $path_certificate
+                    $path_certificate,
+                    $updateCertificateData
                 );
                 $this->certificate_data['path_certificate'] = $path_certificate;
 
@@ -453,22 +456,24 @@ class Certificate extends Model
     /**
      * Update user info about certificate.
      *
-     * @param int    $cat_id           category id
-     * @param int    $user_id          user id
-     * @param string $path_certificate the path name of the certificate
+     * @param int    $categoryId            category id
+     * @param int    $user_id               user id
+     * @param string $path_certificate      the path name of the certificate
+     * @param bool   $updateCertificateData
      */
     public function updateUserCertificateInfo(
-        $cat_id,
+        $categoryId,
         $user_id,
-        $path_certificate
+        $path_certificate,
+        $updateCertificateData = true
     ) {
-        $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
-        $now = api_get_utc_datetime();
-        if (!UserManager::is_user_certified($cat_id, $user_id)) {
+        if (!UserManager::is_user_certified($categoryId, $user_id) && $updateCertificateData) {
+            $table = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CERTIFICATE);
+            $now = api_get_utc_datetime();
             $sql = 'UPDATE '.$table.' SET 
                         path_certificate="'.Database::escape_string($path_certificate).'",
                         created_at = "'.$now.'"
-                    WHERE cat_id="'.intval($cat_id).'" AND user_id="'.intval($user_id).'" ';
+                    WHERE cat_id="'.intval($categoryId).'" AND user_id="'.intval($user_id).'" ';
             Database::query($sql);
         }
     }
@@ -677,7 +682,6 @@ class Certificate extends Model
             0,
             $this->user_id
         );
-
         if (empty($myCertificate)) {
             GradebookUtils::registerUserInfoAboutCertificate(
                 0,
@@ -691,7 +695,6 @@ class Certificate extends Model
 
         $extraFieldValue = new ExtraFieldValue('user');
         $value = $extraFieldValue->get_values_by_handler_and_field_variable($this->user_id, 'legal_accept');
-
         $termsValidationDate = '';
         if (isset($value) && !empty($value['value'])) {
             list($id, $id2, $termsValidationDate) = explode(':', $value['value']);
@@ -716,16 +719,9 @@ class Certificate extends Model
                     if (isset($gradebookCategories[0])) {
                         /** @var Category $category */
                         $category = $gradebookCategories[0];
-                        //  $categoryId = $category->get_id();
-                        // @todo how we check if user pass a gradebook?
-                        //$certificateInfo = GradebookUtils::get_certificate_by_user_id($categoryId, $this->user_id);
-
                         $result = Category::userFinishedCourse(
                             $this->user_id,
                             $category,
-                            null,
-                            $courseInfo['code'],
-                            $session['session_id'],
                             true
                         );
 
@@ -742,8 +738,12 @@ class Certificate extends Model
         }
 
         $skill = new Skill();
-        $skills = $skill->getStudentSkills($this->user_id);
-        $timeInSeconds = Tracking::get_time_spent_on_the_platform($this->user_id);
+        // Ofaj
+        $skills = $skill->getStudentSkills($this->user_id, 2);
+        $timeInSeconds = Tracking::get_time_spent_on_the_platform(
+            $this->user_id,
+            'ever'
+        );
         $time = api_time_to_hms($timeInSeconds);
 
         $tplContent = new Template(null, false, false, false, false, false);

+ 47 - 46
main/inc/lib/course.lib.php

@@ -2169,9 +2169,7 @@ class CourseManager
             return [];
         }
 
-        $group_list = [];
         $session_id != 0 ? $session_condition = ' WHERE g.session_id IN(1,'.intval($session_id).')' : $session_condition = ' WHERE g.session_id = 0';
-
         if ($in_get_empty_group == 0) {
             // get only groups that are not empty
             $sql = "SELECT DISTINCT g.id, g.iid, g.name
@@ -2187,14 +2185,15 @@ class CourseManager
                     $session_condition
                     AND c_id = $course_id";
         }
-        $result = Database::query($sql);
 
-        while ($group_data = Database::fetch_array($result)) {
-            $group_data['userNb'] = GroupManager::number_of_students($group_data['id'], $course_id);
-            $group_list[$group_data['id']] = $group_data;
+        $result = Database::query($sql);
+        $groupList = [];
+        while ($groupData = Database::fetch_array($result)) {
+            $groupData['userNb'] = GroupManager::number_of_students($groupData['id'], $course_id);
+            $groupList[$groupData['iid']] = $groupData;
         }
 
-        return $group_list;
+        return $groupList;
     }
 
     /**
@@ -3011,12 +3010,12 @@ class CourseManager
     /**
      * Creates a new extra field for a given course.
      *
-     * @param    string    Field's internal variable name
-     * @param    int        Field's type
-     * @param    string    Field's language var name
-     * @param string $default
+     * @param string $variable    Field's internal variable name
+     * @param int    $fieldType   Field's type
+     * @param string $displayText Field's language var name
+     * @param string $default     Optional. The default value
      *
-     * @return bool new extra field id
+     * @return int New extra field ID
      */
     public static function create_course_extra_field($variable, $fieldType, $displayText, $default = '')
     {
@@ -3084,11 +3083,11 @@ class CourseManager
     /**
      * Update an extra field value for a given course.
      *
-     * @param    int    Course ID
-     * @param    string    Field variable name
-     * @param    string    Field value
+     * @param string $course_code Course code
+     * @param string $variable    Field variable name
+     * @param string $value       Optional. Default field value
      *
-     * @return bool|null true if field updated, false otherwise
+     * @return bool|int An integer when register a new extra field. And boolean when update the extrafield
      */
     public static function update_course_extra_field_value($course_code, $variable, $value = '')
     {
@@ -5980,38 +5979,38 @@ class CourseManager
      */
     public static function getCourseGroups()
     {
-        $session_id = api_get_session_id();
-        if ($session_id != 0) {
-            $new_group_list = self::get_group_list_of_course(
+        $sessionId = api_get_session_id();
+        if ($sessionId != 0) {
+            $groupList = self::get_group_list_of_course(
                 api_get_course_id(),
-                $session_id,
+                $sessionId,
                 1
             );
         } else {
-            $new_group_list = self::get_group_list_of_course(
+            $groupList = self::get_group_list_of_course(
                 api_get_course_id(),
                 0,
                 1
             );
         }
 
-        return $new_group_list;
+        return $groupList;
     }
 
     /**
      * @param FormValidator $form
-     * @param array         $to_already_selected
+     * @param array         $alreadySelected
      *
      * @return HTML_QuickForm_element
      */
-    public static function addUserGroupMultiSelect(&$form, $to_already_selected)
+    public static function addUserGroupMultiSelect(&$form, $alreadySelected)
     {
         $userList = self::getCourseUsers(true);
-        $group_list = self::getCourseGroups();
+        $groupList = self::getCourseGroups();
         $array = self::buildSelectOptions(
-            $group_list,
+            $groupList,
             $userList,
-            $to_already_selected
+            $alreadySelected
         );
 
         $result = [];
@@ -6090,38 +6089,39 @@ class CourseManager
     /**
      * this function shows the form for sending a message to a specific group or user.
      *
-     * @param array $group_list
+     * @param array $groupList
      * @param array $userList
-     * @param array $to_already_selected
+     * @param array $alreadySelected
      *
      * @return array
      */
     public static function buildSelectOptions(
-        $group_list = [],
+        $groupList = [],
         $userList = [],
-        $to_already_selected = []
+        $alreadySelected = []
     ) {
-        if (empty($to_already_selected)) {
-            $to_already_selected = [];
+        if (empty($alreadySelected)) {
+            $alreadySelected = [];
         }
 
         $result = [];
         // adding the groups to the select form
-        if ($group_list) {
-            foreach ($group_list as $this_group) {
-                if (is_array($to_already_selected)) {
+        if ($groupList) {
+            foreach ($groupList as $thisGroup) {
+                $groupId = $thisGroup['iid'];
+                if (is_array($alreadySelected)) {
                     if (!in_array(
-                        "GROUP:".$this_group['id'],
-                        $to_already_selected
+                        "GROUP:".$groupId,
+                        $alreadySelected
                     )
-                    ) { // $to_already_selected is the array containing the groups (and users) that are already selected
-                        $user_label = ($this_group['userNb'] > 0) ? get_lang('Users') : get_lang('LowerCaseUser');
-                        $user_disabled = ($this_group['userNb'] > 0) ? "" : "disabled=disabled";
+                    ) { // $alreadySelected is the array containing the groups (and users) that are already selected
+                        $user_label = ($thisGroup['userNb'] > 0) ? get_lang('Users') : get_lang('LowerCaseUser');
+                        $user_disabled = ($thisGroup['userNb'] > 0) ? "" : "disabled=disabled";
                         $result[] = [
                             'disabled' => $user_disabled,
-                            'value' => "GROUP:".$this_group['id'],
+                            'value' => "GROUP:".$groupId,
                             // The space before "G" is needed in order to advmultiselect.php js puts groups first
-                            'content' => " G: ".$this_group['name']." - ".$this_group['userNb']." ".$user_label,
+                            'content' => " G: ".$thisGroup['name']." - ".$thisGroup['userNb']." ".$user_label,
                         ];
                     }
                 }
@@ -6131,12 +6131,13 @@ class CourseManager
         // adding the individual users to the select form
         if ($userList) {
             foreach ($userList as $user) {
-                if (is_array($to_already_selected)) {
+                if (is_array($alreadySelected)) {
                     if (!in_array(
                         "USER:".$user['user_id'],
-                        $to_already_selected
+                        $alreadySelected
                     )
-                    ) { // $to_already_selected is the array containing the users (and groups) that are already selected
+                    ) {
+                        // $alreadySelected is the array containing the users (and groups) that are already selected
                         $result[] = [
                             'value' => "USER:".$user['user_id'],
                             'content' => api_get_person_name($user['firstname'], $user['lastname']),

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

@@ -177,12 +177,9 @@ class CourseCategory
         }
         // Now we're at the top, get back down to update each child
         //$children_count = courseCategoryChildrenCount($categoryId);
+        $sql = "UPDATE $table SET children_count = (children_count - ".abs($delta).") WHERE code = '$categoryId'";
         if ($delta >= 0) {
-            $sql = "UPDATE $table SET children_count = (children_count + $delta)
-                WHERE code = '$categoryId'";
-        } else {
-            $sql = "UPDATE $table SET children_count = (children_count - ".abs($delta).")
-                WHERE code = '$categoryId'";
+            $sql = "UPDATE $table SET children_count = (children_count + $delta) WHERE code = '$categoryId'";
         }
         Database::query($sql);
     }

+ 18 - 18
main/inc/lib/course_home.lib.php

@@ -251,9 +251,7 @@ class CourseHome
         $html = '';
         $web_code_path = api_get_path(WEB_CODE_PATH);
         $course_tool_table = Database::get_course_table(TABLE_TOOL_LIST);
-
         $course_id = api_get_course_int_id();
-
         switch ($course_tool_category) {
             case TOOL_PUBLIC:
                 $condition_display_tools = ' WHERE c_id = '.$course_id.' AND visibility = 1 ';
@@ -571,13 +569,18 @@ class CourseHome
         }
 
         $allowEditionInSession = api_get_configuration_value('allow_edit_tool_visibility_in_session');
-
-        $toolWithSessionValue = [];
-        foreach ($tools as $row) {
-            if (!empty($row['session_id'])) {
-                $toolWithSessionValue[$row['name']] = $row;
+        // If exists same tool (by name) from session in base course then avoid it. Allow them pass in other cases
+        $tools = array_filter($tools, function (array $toolToFilter) use ($sessionId, $tools) {
+            if (!empty($toolToFilter['session_id'])) {
+                foreach ($tools as $originalTool) {
+                    if ($toolToFilter['name'] == $originalTool['name'] && empty($originalTool['session_id'])) {
+                        return false;
+                    }
+                }
             }
-        }
+
+            return true;
+        });
 
         foreach ($tools as $temp_row) {
             $add = false;
@@ -589,13 +592,6 @@ class CourseHome
                 $add = true;
             }
 
-            if (isset($toolWithSessionValue[$temp_row['name']])) {
-                if (!empty($temp_row['session_id'])) {
-                    continue;
-                }
-                //$temp_row = $toolWithSessionValue[$temp_row['name']];
-            }
-
             if ($allowEditionInSession && !empty($sessionId)) {
                 // Checking if exist row in session
                 $criteria = [
@@ -1240,14 +1236,18 @@ class CourseHome
                 }
 
                 $navigation_items[$row['id']] = $row;
-                if (stripos($row['link'], 'http://') === false && stripos($row['link'], 'https://') === false) {
+                if (stripos($row['link'], 'http://') === false &&
+                    stripos($row['link'], 'https://') === false
+                ) {
                     $navigation_items[$row['id']]['link'] = api_get_path(WEB_CODE_PATH);
 
                     if ($row['category'] == 'plugin') {
                         $plugin = new AppPlugin();
                         $pluginInfo = $plugin->getPluginInfo($row['name']);
-                        $navigation_items[$row['id']]['link'] = api_get_path(WEB_PLUGIN_PATH);
-                        $navigation_items[$row['id']]['name'] = $pluginInfo['title'];
+                        if (isset($pluginInfo['title'])) {
+                            $navigation_items[$row['id']]['link'] = api_get_path(WEB_PLUGIN_PATH);
+                            $navigation_items[$row['id']]['name'] = $pluginInfo['title'];
+                        }
                     } else {
                         $navigation_items[$row['id']]['name'] = self::translate_tool_name($row);
                     }

+ 2 - 0
main/inc/lib/database.lib.php

@@ -1,10 +1,12 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Doctrine\Common\Annotations\AnnotationRegistry;
 use Doctrine\DBAL\Connection;
 use Doctrine\DBAL\Driver\Statement;
 use Doctrine\DBAL\Types\Type;
 use Doctrine\ORM\EntityManager;
+use Symfony\Component\Debug\ExceptionHandler;
 
 /**
  * Class Database.

+ 11 - 3
main/inc/lib/display.lib.php

@@ -850,7 +850,6 @@ class Display
         // When moving this to production, the return_icon() calls should
         // ask for the SVG version directly
         $svgIcons = api_get_setting('icons_mode_svg');
-
         if ($svgIcons == 'true' && $return_only_path == false) {
             $svgImage = substr($image, 0, -3).'svg';
             if (is_file($code_path.$theme.'svg/'.$svgImage)) {
@@ -1044,6 +1043,15 @@ class Display
 
     /**
      * Displays an HTML select tag.
+     *
+     * @param string $name
+     * @param array  $values
+     * @param int    $default
+     * @param array  $extra_attributes
+     * @param bool   $show_blank_item
+     * @param null   $blank_item_text
+     *
+     * @return string
      */
     public static function select(
         $name,
@@ -1051,7 +1059,7 @@ class Display
         $default = -1,
         $extra_attributes = [],
         $show_blank_item = true,
-        $blank_item_text = null
+        $blank_item_text = ''
     ) {
         $html = '';
         $extra = '';
@@ -2478,7 +2486,7 @@ class Display
         $style = !in_array($type, $typeList) ? 'default' : $type;
 
         if (!empty($id)) {
-            $id = " id = $id ";
+            $id = " id='$id'";
         }
 
         return '

+ 164 - 202
main/inc/lib/document.lib.php

@@ -296,12 +296,15 @@ class DocumentManager
         if (!is_file($full_file_name)) {
             return false;
         }
-        $filename = ($name == '') ? basename($full_file_name) : api_replace_dangerous_char($name);
+        $filename = $name == '' ? basename($full_file_name) : api_replace_dangerous_char($name);
         $len = filesize($full_file_name);
         // Fixing error when file name contains a ","
         $filename = str_replace(',', '', $filename);
         $sendFileHeaders = api_get_configuration_value('enable_x_sendfile_headers');
 
+        // Allows chrome to make videos and audios seekable
+        header('Accept-Ranges: bytes');
+
         if ($forced) {
             // Force the browser to save the file instead of opening it
             if (isset($sendFileHeaders) &&
@@ -325,7 +328,8 @@ class DocumentManager
             header('Content-Transfer-Encoding: binary');
 
             if (function_exists('ob_end_clean')) {
-                // Use ob_end_clean() to avoid weird buffering situations where file is sent broken/incomplete for download
+                // Use ob_end_clean() to avoid weird buffering situations
+                // where file is sent broken/incomplete for download
                 ob_end_clean();
             }
 
@@ -334,8 +338,7 @@ class DocumentManager
 
             return true;
         } else {
-            //no forced download, just let the browser decide what to do according to the mimetype
-            $content_type = self::file_get_mime_type($filename);
+            // no forced download, just let the browser decide what to do according to the mimetype
             $lpFixedEncoding = api_get_configuration_value('lp_fixed_encoding');
 
             // Commented to let courses content to be cached in order to improve performance:
@@ -345,24 +348,25 @@ 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');
-            switch ($content_type) {
+            $contentType = self::file_get_mime_type($filename);
+            switch ($contentType) {
                 case 'text/html':
                     if (isset($lpFixedEncoding) && $lpFixedEncoding === 'true') {
-                        $content_type .= '; charset=UTF-8';
+                        $contentType .= '; charset=UTF-8';
                     } else {
                         $encoding = @api_detect_encoding_html(file_get_contents($full_file_name));
                         if (!empty($encoding)) {
-                            $content_type .= '; charset='.$encoding;
+                            $contentType .= '; charset='.$encoding;
                         }
                     }
                     break;
                 case 'text/plain':
                     if (isset($lpFixedEncoding) && $lpFixedEncoding === 'true') {
-                        $content_type .= '; charset=UTF-8';
+                        $contentType .= '; charset=UTF-8';
                     } else {
                         $encoding = @api_detect_encoding(strip_tags(file_get_contents($full_file_name)));
                         if (!empty($encoding)) {
-                            $content_type .= '; charset='.$encoding;
+                            $contentType .= '; charset='.$encoding;
                         }
                     }
                     break;
@@ -371,7 +375,7 @@ class DocumentManager
                     header('Content-type: application/octet-stream');
                     break;
             }
-            header('Content-type: '.$content_type);
+            header('Content-type: '.$contentType);
             header('Content-Length: '.$len);
             $user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
             if (strpos($user_agent, 'msie')) {
@@ -390,7 +394,8 @@ class DocumentManager
                 echo $content;
             } else {
                 if (function_exists('ob_end_clean')) {
-                    // Use ob_end_clean() to avoid weird buffering situations where file is sent broken/incomplete for download
+                    // Use ob_end_clean() to avoid weird buffering situations
+                    // where file is sent broken/incomplete for download
                     ob_end_clean();
                 }
 
@@ -429,39 +434,39 @@ class DocumentManager
     /**
      * Fetches all document data for the given user/group.
      *
-     * @param array  $_course
+     * @param array  $courseInfo
      * @param string $path
-     * @param int    $to_group_id       iid
-     * @param int    $to_user_id
-     * @param bool   $can_see_invisible
+     * @param int    $toGroupId       iid
+     * @param int    $toUserId
+     * @param bool   $canSeeInvisible
      * @param bool   $search
      * @param int    $sessionId
      *
      * @return array with all document data
      */
-    public static function get_all_document_data(
-        $_course,
+    public static function getAllDocumentData(
+        $courseInfo,
         $path = '/',
-        $to_group_id = 0,
-        $to_user_id = null,
-        $can_see_invisible = false,
+        $toGroupId = 0,
+        $toUserId = null,
+        $canSeeInvisible = false,
         $search = false,
         $sessionId = 0
     ) {
-        $TABLE_ITEMPROPERTY = Database::get_course_table(TABLE_ITEM_PROPERTY);
-        $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
+        $tblItemProperty = Database::get_course_table(TABLE_ITEM_PROPERTY);
+        $tblDocument = Database::get_course_table(TABLE_DOCUMENT);
 
         $userGroupFilter = '';
-        if (!is_null($to_user_id)) {
-            $to_user_id = intval($to_user_id);
-            $userGroupFilter = "last.to_user_id = $to_user_id";
-            if (empty($to_user_id)) {
+        if (!is_null($toUserId)) {
+            $toUserId = intval($toUserId);
+            $userGroupFilter = "last.to_user_id = $toUserId";
+            if (empty($toUserId)) {
                 $userGroupFilter = " (last.to_user_id = 0 OR last.to_user_id IS NULL) ";
             }
         } else {
-            $to_group_id = intval($to_group_id);
-            $userGroupFilter = "last.to_group_id = $to_group_id";
-            if (empty($to_group_id)) {
+            $toGroupId = intval($toGroupId);
+            $userGroupFilter = "last.to_group_id = $toGroupId";
+            if (empty($toGroupId)) {
                 $userGroupFilter = "( last.to_group_id = 0 OR last.to_group_id IS NULL) ";
             }
         }
@@ -470,23 +475,23 @@ class DocumentManager
         $originalPath = $path;
         $path = str_replace('_', '\_', $path);
 
-        $visibility_bit = ' <> 2';
+        $visibilityBit = ' <> 2';
 
         // The given path will not end with a slash, unless it's the root '/'
         // so no root -> add slash
-        $added_slash = $path == '/' ? '' : '/';
+        $addedSlash = $path == '/' ? '' : '/';
 
         // Condition for the session
         $sessionId = $sessionId ?: api_get_session_id();
-        $condition_session = " AND (last.session_id = '$sessionId' OR (last.session_id = '0' OR last.session_id IS NULL) )";
-        $condition_session .= self::getSessionFolderFilters($originalPath, $sessionId);
+        $conditionSession = " AND (last.session_id = '$sessionId' OR (last.session_id = '0' OR last.session_id IS NULL) )";
+        $conditionSession .= self::getSessionFolderFilters($originalPath, $sessionId);
 
         $sharedCondition = null;
         if ($originalPath == '/shared_folder') {
-            $students = CourseManager::get_user_list_from_course_code($_course['code'], $sessionId);
+            $students = CourseManager::get_user_list_from_course_code($courseInfo['code'], $sessionId);
             if (!empty($students)) {
                 $conditionList = [];
-                foreach ($students as $studentId => $studentInfo) {
+                foreach ($students as $studentInfo) {
                     $conditionList[] = '/shared_folder/sf_user_'.$studentInfo['user_id'];
                 }
                 $sharedCondition .= ' AND docs.path IN ("'.implode('","', $conditionList).'")';
@@ -506,136 +511,106 @@ class DocumentManager
                     last.lastedit_date,
                     last.visibility,
                     last.insert_user_id
-                FROM $TABLE_ITEMPROPERTY AS last
-                INNER JOIN $TABLE_DOCUMENT AS docs
+                FROM $tblItemProperty AS last
+                INNER JOIN $tblDocument AS docs
                 ON (
                     docs.id = last.ref AND
                     docs.c_id = last.c_id
                 )
                 WHERE                                
                     last.tool = '".TOOL_DOCUMENT."' AND 
-                    docs.c_id = {$_course['real_id']} AND
-                    last.c_id = {$_course['real_id']} AND
-                    docs.path LIKE '".Database::escape_string($path.$added_slash.'%')."' AND
-                    docs.path NOT LIKE '".Database::escape_string($path.$added_slash.'%/%')."' AND
+                    docs.c_id = {$courseInfo['real_id']} AND
+                    last.c_id = {$courseInfo['real_id']} AND
+                    docs.path LIKE '".Database::escape_string($path.$addedSlash.'%')."' AND
+                    docs.path NOT LIKE '".Database::escape_string($path.$addedSlash.'%/%')."' AND
                     docs.path NOT LIKE '%_DELETED_%' AND
                     $userGroupFilter AND
-                    last.visibility $visibility_bit
-                    $condition_session
+                    last.visibility $visibilityBit
+                    $conditionSession
                     $sharedCondition
+                ORDER BY last.iid DESC, last.session_id DESC
                 ";
         $result = Database::query($sql);
 
-        $doc_list = [];
-        $document_data = [];
-        $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
+        $documentData = [];
+        $isAllowedToEdit = api_is_allowed_to_edit(null, true);
         $isCoach = api_is_coach();
         if ($result !== false && Database::num_rows($result) != 0) {
+            $rows = [];
+
+            $hideInvisibleDocuments = api_get_configuration_value('hide_invisible_course_documents_in_sessions');
+
             while ($row = Database::fetch_array($result, 'ASSOC')) {
-                if ($isCoach) {
-                    // Looking for course items that are invisible to hide it in the session
-                    if (in_array($row['id'], array_keys($doc_list))) {
-                        if ($doc_list[$row['id']]['item_property_session_id'] == 0 &&
-                            $doc_list[$row['id']]['session_id'] == 0
-                        ) {
-                            if ($doc_list[$row['id']]['visibility'] == 0) {
-                                unset($document_data[$row['id']]);
-                                continue;
-                            }
-                        }
-                    }
-                    $doc_list[$row['id']] = $row;
+                if (isset($rows[$row['id']])) {
+                    continue;
                 }
 
-                if (!$isCoach && !$is_allowed_to_edit) {
-                    $doc_list[] = $row;
+                // If we are in session and hide_invisible_course_documents_in_sessions is enabled
+                // Then we avoid the documents that have visibility in session but that they come from a base course
+                if ($hideInvisibleDocuments && $sessionId) {
+                    if ($row['item_property_session_id'] == $sessionId && empty($row['session_id'])) {
+                        continue;
+                    }
                 }
 
+                $rows[$row['id']] = $row;
+            }
+
+            // If we are in session and hide_invisible_course_documents_in_sessions is enabled
+            // Or if we are students
+            // Then don't list the invisible or deleted documents
+            if (($sessionId && $hideInvisibleDocuments) || (!$isCoach && !$isAllowedToEdit)) {
+                $rows = array_filter($rows, function ($row) {
+                    if (in_array($row['visibility'], ['0', '2'])) {
+                        return false;
+                    }
+
+                    return true;
+                });
+            }
+
+            foreach ($rows as $row) {
                 if ($row['filetype'] == 'file' &&
                     pathinfo($row['path'], PATHINFO_EXTENSION) == 'html'
                 ) {
                     // Templates management
-                    $table_template = Database::get_main_table(TABLE_MAIN_TEMPLATES);
-                    $sql = "SELECT id FROM $table_template
+                    $tblTemplate = Database::get_main_table(TABLE_MAIN_TEMPLATES);
+                    $sql = "SELECT id FROM $tblTemplate
                             WHERE
-                                course_code = '".$_course['code']."' AND
+                                course_code = '".$courseInfo['code']."' AND
                                 user_id = '".api_get_user_id()."' AND
                                 ref_doc = '".$row['id']."'";
-                    $template_result = Database::query($sql);
-                    $row['is_template'] = (Database::num_rows($template_result) > 0) ? 1 : 0;
+                    $templateResult = Database::query($sql);
+                    $row['is_template'] = (Database::num_rows($templateResult) > 0) ? 1 : 0;
                 }
                 $row['basename'] = basename($row['path']);
                 // Just filling $document_data.
-                $document_data[$row['id']] = $row;
+                $documentData[$row['id']] = $row;
             }
 
             // Only for the student we filter the results see BT#1652
-            if (!$isCoach && !$is_allowed_to_edit) {
-                $ids_to_remove = [];
-                $my_repeat_ids = $temp = [];
-
-                // Selecting repeated ids
-                foreach ($doc_list as $row) {
-                    if (in_array($row['id'], array_keys($temp))) {
-                        $my_repeat_ids[] = $row['id'];
-                    }
-                    $temp[$row['id']] = $row;
-                }
-
-                //@todo use the self::is_visible function
-                // Checking visibility in a session
-                foreach ($my_repeat_ids as $id) {
-                    foreach ($doc_list as $row) {
-                        if ($id == $row['id']) {
-                            if ($row['visibility'] == 0 && $row['item_property_session_id'] == 0) {
-                                $delete_repeated[$id] = true;
-                            }
-                            if ($row['visibility'] == 0 && $row['item_property_session_id'] != 0) {
-                                $delete_repeated[$id] = true;
-                            }
-                        }
-                    }
-                }
-
-                foreach ($doc_list as $key => $row) {
-                    if (in_array($row['visibility'], ['0', '2']) &&
-                        !in_array($row['id'], $my_repeat_ids)
-                    ) {
-                        $ids_to_remove[] = $row['id'];
-                        unset($doc_list[$key]);
-                    }
-                }
-
-                foreach ($document_data as $row) {
-                    if (in_array($row['id'], $ids_to_remove)) {
-                        unset($document_data[$row['id']]);
-                    }
-                    if (isset($delete_repeated[$row['id']]) && $delete_repeated[$row['id']]) {
-                        unset($document_data[$row['id']]);
-                    }
-                }
-
+            if (!$isCoach && !$isAllowedToEdit) {
                 // Checking parents visibility.
-                $final_document_data = [];
-                foreach ($document_data as $row) {
-                    $is_visible = self::check_visibility_tree(
+                $finalDocumentData = [];
+                foreach ($documentData as $row) {
+                    $isVisible = self::check_visibility_tree(
                         $row['id'],
-                        $_course['code'],
+                        $courseInfo['code'],
                         $sessionId,
                         api_get_user_id(),
-                        $to_group_id
+                        $toGroupId
                     );
-                    if ($is_visible) {
-                        $final_document_data[$row['id']] = $row;
+                    if ($isVisible) {
+                        $finalDocumentData[$row['id']] = $row;
                     }
                 }
             } else {
-                $final_document_data = $document_data;
+                $finalDocumentData = $documentData;
             }
 
-            return $final_document_data;
+            return $finalDocumentData;
         } else {
-            return false;
+            return [];
         }
     }
 
@@ -871,7 +846,7 @@ class DocumentManager
         $_course,
         $user_id,
         $file = null,
-        $document_id = '',
+        $document_id = 0,
         $to_delete = false,
         $sessionId = null,
         $documentId = null
@@ -1059,16 +1034,14 @@ class DocumentManager
     ) {
         $TABLE_DOCUMENT = Database::get_course_table(TABLE_DOCUMENT);
 
+        $groupId = intval($groupId);
         if (empty($groupId)) {
             $groupId = api_get_group_id();
-        } else {
-            $groupId = intval($groupId);
         }
 
+        $sessionId = intval($sessionId);
         if (empty($sessionId)) {
             $sessionId = api_get_session_id();
-        } else {
-            $sessionId = intval($sessionId);
         }
 
         $course_id = $_course['real_id'];
@@ -1466,7 +1439,6 @@ class DocumentManager
         $course_code = Database::escape_string($course_code);
         $user_id = intval($user_id);
         $document_id = intval($document_id);
-
         $sql = 'SELECT id FROM '.$table_template.'
                 WHERE
                     course_code="'.$course_code.'" AND
@@ -1507,7 +1479,8 @@ class DocumentManager
         $propTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
 
         $course_id = $course['real_id'];
-        //note the extra / at the end of doc_path to match every path in the document table that is part of the document path
+        // note the extra / at the end of doc_path to match every path in
+        // the document table that is part of the document path
 
         $session_id = intval($session_id);
         $condition = "AND d.session_id IN  ('$session_id', '0') ";
@@ -1716,7 +1689,7 @@ class DocumentManager
      * @param int    $document_id
      * @param int    $session_id
      */
-    public static function attach_gradebook_certificate($courseCode, $document_id, $session_id = 0)
+    public static function attach_gradebook_certificate($course_id, $document_id, $session_id = 0)
     {
         $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
         $session_id = intval($session_id);
@@ -1731,10 +1704,8 @@ class DocumentManager
         } else {
             $sql_session = '';
         }
-        $courseInfo = api_get_course_info($courseCode);
-
         $sql = 'UPDATE '.$tbl_category.' SET document_id="'.intval($document_id).'"
-                WHERE c_id ="'.$courseInfo['real_id'].'" '.$sql_session;
+                WHERE course_code="'.Database::escape_string($course_id).'" '.$sql_session;
         Database::query($sql);
     }
 
@@ -1748,7 +1719,7 @@ class DocumentManager
      *
      * @return int The default certificate id
      */
-    public static function get_default_certificate_id($courseCode, $session_id = 0)
+    public static function get_default_certificate_id($course_id, $session_id = 0)
     {
         $tbl_category = Database::get_main_table(TABLE_MAIN_GRADEBOOK_CATEGORY);
         $session_id = intval($session_id);
@@ -1763,9 +1734,8 @@ class DocumentManager
         } else {
             $sql_session = '';
         }
-        $courseInfo = api_get_course_info($courseCode);
         $sql = 'SELECT document_id FROM '.$tbl_category.'
-                WHERE c_id ="'.$courseInfo['real_id'].'" '.$sql_session;
+                WHERE course_code="'.Database::escape_string($course_id).'" '.$sql_session;
 
         $rs = Database::query($sql);
         $num = Database::num_rows($rs);
@@ -1785,7 +1755,7 @@ class DocumentManager
      * @param int    $sessionId
      * @param bool   $is_preview
      *
-     * @return string The html content of the certificate
+     * @return array
      */
     public static function replace_user_info_into_html(
         $user_id,
@@ -2005,7 +1975,6 @@ class DocumentManager
             $course_dir = $courseInfo['path']."/document/";
             $sys_course_path = api_get_path(SYS_COURSE_PATH);
             $base_work_dir = $sys_course_path.$course_dir;
-            $base_work_dir_test = $base_work_dir.'certificates';
             $dir_name = '/certificates';
             $post_dir_name = get_lang('CertificatesFiles');
             $visibility_command = 'invisible';
@@ -2157,8 +2126,12 @@ class DocumentManager
                     case 'shtml':
                     case 'css':
                         $file_content = file_get_contents($abs_path);
-                        //get an array of attributes from the HTML source
-                        $attributes = self::parse_HTML_attributes($file_content, $wanted_attributes, $explode_attributes);
+                        // get an array of attributes from the HTML source
+                        $attributes = self::parse_HTML_attributes(
+                            $file_content,
+                            $wanted_attributes,
+                            $explode_attributes
+                        );
                         break;
                     default:
                         break;
@@ -2476,13 +2449,13 @@ class DocumentManager
     /**
      * Parses the HTML attributes given as string.
      *
-     * @param    string  HTML attribute string
-     * @param	 array	 List of attributes that we want to get back
-     * @param    array
+     * @param string HTML attribute string
+     * @param array List of attributes that we want to get back
+     * @param array
      *
      * @return array An associative array of attributes
      *
-     * @author 	 Based on a function from the HTML_Common2 PEAR module     *
+     * @author Based on a function from the HTML_Common2 PEAR module     *
      */
     public static function parse_HTML_attributes($attrString, $wanted = [], $explode_variables = [])
     {
@@ -2649,7 +2622,11 @@ class DocumentManager
                                     $perm = api_get_permissions_for_new_directories();
                                     $result = @mkdir($filepath_dir, $perm, true);
                                     if ($result) {
-                                        $filepath_to_add = str_replace([$dest_course_path, 'document'], '', $filepath_dir);
+                                        $filepath_to_add = str_replace(
+                                            [$dest_course_path, 'document'],
+                                            '',
+                                            $filepath_dir
+                                        );
 
                                         //Add to item properties to the new folder
                                         $doc_id = add_document(
@@ -2676,7 +2653,11 @@ class DocumentManager
                                 if (!file_exists($destination_filepath)) {
                                     $result = @copy($origin_filepath, $destination_filepath);
                                     if ($result) {
-                                        $filepath_to_add = str_replace([$dest_course_path, 'document'], '', $destination_filepath);
+                                        $filepath_to_add = str_replace(
+                                            [$dest_course_path, 'document'],
+                                            '',
+                                            $destination_filepath
+                                        );
                                         $size = filesize($destination_filepath);
 
                                         // Add to item properties to the file
@@ -2704,12 +2685,13 @@ class DocumentManager
 
                             // Replace origin course path by destination course path.
                             if (strpos($content_html, $real_orig_url) !== false) {
-                                $url_course_path = str_replace($orig_course_info_path.'/'.$document_file, '', $real_orig_path);
-
-                                //$destination_url = $url_course_path . $destination_course_directory . '/' . $document_file . $dest_url_query;
+                                $url_course_path = str_replace(
+                                    $orig_course_info_path.'/'.$document_file,
+                                    '',
+                                    $real_orig_path
+                                );
                                 // See BT#7780
                                 $destination_url = $dest_course_path_rel.$document_file.$dest_url_query;
-
                                 // If the course code doesn't exist in the path? what we do? Nothing! see BT#1985
                                 if (strpos($real_orig_path, $origin_course_code) === false) {
                                     $url_course_path = $real_orig_path;
@@ -2788,7 +2770,7 @@ class DocumentManager
      * @param string $title
      * @param string $comment
      * @param int    $unzip                   unzip or not the file
-     * @param string $if_exists               overwrite, rename or warn (default)
+     * @param string $ifExists                overwrite, rename or warn (default)
      * @param bool   $index_document          index document (search xapian module)
      * @param bool   $show_output             print html messages
      * @param string $fileKey
@@ -2799,10 +2781,10 @@ class DocumentManager
     public static function upload_document(
         $files,
         $path,
-        $title = null,
-        $comment = null,
+        $title = '',
+        $comment = '',
         $unzip = 0,
-        $if_exists = null,
+        $ifExists = '',
         $index_document = false,
         $show_output = false,
         $fileKey = 'file',
@@ -2815,9 +2797,8 @@ class DocumentManager
         $base_work_dir = $sys_course_path.$course_dir;
 
         if (isset($files[$fileKey])) {
-            $upload_ok = process_uploaded_file($files[$fileKey], $show_output);
-
-            if ($upload_ok) {
+            $uploadOk = process_uploaded_file($files[$fileKey], $show_output);
+            if ($uploadOk) {
                 $new_path = handle_uploaded_document(
                     $course_info,
                     $files[$fileKey],
@@ -2827,7 +2808,7 @@ class DocumentManager
                     api_get_group_id(),
                     null,
                     $unzip,
-                    $if_exists,
+                    $ifExists,
                     $show_output,
                     false,
                     null,
@@ -2846,8 +2827,8 @@ class DocumentManager
                     }
 
                     return [
-                        'title' => $path,
-                        'url' => $path,
+                        'title' => $files[$fileKey]['name'],
+                        'url' => '#',
                     ];
                 }
 
@@ -2889,7 +2870,7 @@ class DocumentManager
                             null,
                             $_POST['language'],
                             $_REQUEST,
-                            $if_exists
+                            $ifExists
                         );
                     }
 
@@ -3011,12 +2992,12 @@ class DocumentManager
             $course_id = api_get_course_int_id();
         }
 
-        $group_condition = null;
+        $group_condition = '';
         if ($group_id) {
             $group_condition = " AND props.to_group_id='".$group_id."' ";
         }
 
-        $session_condition = null;
+        $session_condition = '';
         if ($session_id) {
             $session_condition = " AND props.session_id='".$session_id."' ";
         }
@@ -3155,7 +3136,7 @@ class DocumentManager
                         </div>
                     </div>
                 </div>';
-        //<div id="jplayer_inspector_'.$i.'"></div>
+
         return $html;
     }
 
@@ -3166,7 +3147,6 @@ class DocumentManager
      */
     public static function generate_video_preview($document_data = [])
     {
-        //<button class="jp-video-play-icon" role="button" tabindex="0">play</button>
         $html = '
         <div id="jp_container_1" class="jp-video center-block" role="application" aria-label="media player">
             <div class="jp-type-single">
@@ -4048,12 +4028,7 @@ class DocumentManager
         $formatTypesList = [];
         $formatTypes = ['text', 'spreadsheet', 'presentation', 'drawing'];
         foreach ($formatTypes as $formatType) {
-            if (
-            in_array(
-                $extension,
-                self::getJodconverterExtensionList($mode, $formatType)
-            )
-            ) {
+            if (in_array($extension, self::getJodconverterExtensionList($mode, $formatType))) {
                 $formatTypesList[] = $formatType;
             }
         }
@@ -5422,11 +5397,11 @@ class DocumentManager
     /**
      * Creates the row of edit icons for a file/folder.
      *
-     * @param string $curdirpath current path (cfr open folder)
-     * @param string $type       (file/folder)
-     * @param string $path       dbase path of file/folder
-     * @param int    $visibility (1/0)
-     * @param int    $id         dbase id of the document
+     * @param array $document_data
+     * @param int   $id
+     * @param bool  $is_template
+     * @param int   $is_read_only
+     * @param int   $visibility    (1/0)
      *
      * @return string html img tags with hyperlinks
      */
@@ -5565,7 +5540,7 @@ class DocumentManager
         $form = new FormValidator('move_to', 'post', api_get_self().'?'.api_get_cidreq());
 
         // Form title
-        $form->addElement('hidden', 'move_file', $move_file);
+        $form->addHidden('move_file', $move_file);
 
         $options = [];
 
@@ -5734,16 +5709,16 @@ class DocumentManager
      * Checks whether the user is into any user shared folder.
      *
      * @param string $path
-     * @param int    $current_session_id
+     * @param int    $sessionId
      *
      * @return bool Return true when user is in any user shared folder
      */
-    public static function is_any_user_shared_folder($path, $current_session_id)
+    public static function is_any_user_shared_folder($path, $sessionId)
     {
         $clean_path = Security::remove_XSS($path);
         if (strpos($clean_path, 'shared_folder/sf_user_')) {
             return true;
-        } elseif (strpos($clean_path, 'shared_folder_session_'.$current_session_id.'/sf_user_')) {
+        } elseif (strpos($clean_path, 'shared_folder_session_'.$sessionId.'/sf_user_')) {
             return true;
         } else {
             return false;
@@ -5830,17 +5805,17 @@ class DocumentManager
      *
      * @param int    $user_id
      * @param string $path
-     * @param int    $current_session_id
+     * @param int    $sessionId
      *
      * @return bool Return true when user is in his user shared folder or into a subfolder
      */
-    public static function is_my_shared_folder($user_id, $path, $current_session_id)
+    public static function is_my_shared_folder($user_id, $path, $sessionId)
     {
         $clean_path = Security::remove_XSS($path).'/';
         //for security does not remove the last slash
         $main_user_shared_folder = '/shared_folder\/sf_user_'.$user_id.'\//';
         //for security does not remove the last slash
-        $main_user_shared_folder_session = '/shared_folder_session_'.$current_session_id.'\/sf_user_'.$user_id.'\//';
+        $main_user_shared_folder_session = '/shared_folder_session_'.$sessionId.'\/sf_user_'.$user_id.'\//';
 
         if (preg_match($main_user_shared_folder, $clean_path)) {
             return true;
@@ -5885,7 +5860,7 @@ class DocumentManager
 
         /*
           //TODO: make a admin switch to strict mode
-          1. global default $allowed_extensions only: 'htm', 'html', 'xhtml', 'gif', 'jpg', 'jpeg', 'png', 'bmp', 'txt', 'log'
+          1. global default $allowed_extensions
           if (in_array($file_extension, $allowed_extensions)) { // Assignment + a logical check.
           return true;
           }
@@ -5893,7 +5868,8 @@ class DocumentManager
           3. check plugins: quicktime, mediaplayer, vlc, acrobat, flash, java
          */
 
-        if (!($result = in_array($file_extension, $allowed_extensions))) { // Assignment + a logical check.
+        if (!($result = in_array($file_extension, $allowed_extensions))) {
+            // Assignment + a logical check.
             return false;
         }
 
@@ -6185,7 +6161,9 @@ class DocumentManager
                 $new_path = Database::escape_string($new_path);
                 $query = "UPDATE $dbTable SET
                             path = CONCAT('".$new_path."', SUBSTRING(path, LENGTH('".$old_path."')+1) )
-                          WHERE c_id = $course_id AND (path LIKE BINARY '".$old_path."' OR path LIKE BINARY '".$old_path."/%')";
+                          WHERE 
+                                c_id = $course_id AND 
+                                (path LIKE BINARY '".$old_path."' OR path LIKE BINARY '".$old_path."/%')";
                 Database::query($query);
                 break;
         }
@@ -6556,7 +6534,6 @@ class DocumentManager
         }
 
         $return .= '<div class="item_data" style="margin-left:'.($num * 5).'px;margin-right:5px;">';
-
         if ($add_move_button) {
             $return .= '<a class="moved" href="#">';
             $return .= Display::return_icon('move_everywhere.png', get_lang('Move'), [], ICON_SIZE_TINY);
@@ -6604,22 +6581,7 @@ class DocumentManager
             return null;
         }
 
-        //trad some titles
-        /*
-        if ($key == 'images') {
-            $key = get_lang('Images');
-        } elseif ($key == 'gallery') {
-            $key = get_lang('Gallery');
-        } elseif ($key == 'flash') {
-            $key = get_lang('Flash');
-        } elseif ($key == 'audio') {
-            $key = get_lang('Audio');
-        } elseif ($key == 'video') {
-            $key = get_lang('Video');
-        }*/
-
         $onclick = '';
-
         // if in LP, hidden folder are displayed in grey
         $folder_class_hidden = '';
         if ($lp_id) {

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

@@ -82,7 +82,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);

+ 159 - 151
main/inc/lib/exercise.lib.php

@@ -39,13 +39,13 @@ class ExerciseLib
         $freeze = false,
         $user_choice = [],
         $show_comment = false,
-        $show_answers = false
+        $show_answers = false,
+        $show_icon = false
     ) {
         $course_id = empty($exercise->course_id) ? api_get_course_int_id() : $exercise->course_id;
         $course = api_get_course_info_by_id($course_id);
         // Change false to true in the following line to enable answer hinting
         $debug_mark_answer = $show_answers;
-
         // Reads question information
         if (!$objQuestionTmp = Question::read($questionId)) {
             // Question not found
@@ -70,7 +70,7 @@ class ExerciseLib
                     if ($exercise->display_category_name) {
                         TestCategory::displayCategoryAndTitle($objQuestionTmp->id);
                     }
-                    $titleToDisplay = null;
+                    $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
                     if ($answerType == READING_COMPREHENSION) {
                         // In READING_COMPREHENSION, the title of the question
                         // contains the question itself, which can only be
@@ -79,8 +79,6 @@ class ExerciseLib
                             $current_item.'. '.get_lang('ReadingComprehension'),
                             ['class' => 'question_title']
                         );
-                    } else {
-                        $titleToDisplay = $objQuestionTmp->getTitleToDisplay($current_item);
                     }
                     echo $titleToDisplay;
                 }
@@ -92,9 +90,7 @@ class ExerciseLib
                 }
             }
 
-            if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION]) &&
-                $freeze
-            ) {
+            if (in_array($answerType, [FREE_ANSWER, ORAL_EXPRESSION]) && $freeze) {
                 return '';
             }
 
@@ -191,7 +187,6 @@ class ExerciseLib
                 if (api_get_setting('enable_record_audio') == 'true') {
                     //@todo pass this as a parameter
                     global $exercise_stat_info, $exerciseId;
-
                     if (!empty($exercise_stat_info)) {
                         $objQuestionTmp->initFile(
                             api_get_session_id(),
@@ -290,9 +285,7 @@ class ExerciseLib
 
             $hidingClass = '';
             if ($answerType == READING_COMPREHENSION) {
-                $objQuestionTmp->processText(
-                    $objQuestionTmp->selectDescription()
-                );
+                $objQuestionTmp->processText($objQuestionTmp->selectDescription());
                 $hidingClass = 'hide-reading-answers';
                 $s .= Display::div(
                     $objQuestionTmp->selectTitle(),
@@ -337,15 +330,17 @@ class ExerciseLib
                         if ($answerType == UNIQUE_ANSWER_IMAGE) {
                             if ($show_comment) {
                                 if (empty($comment)) {
-                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
-                                        .'class="exercise-unique-answer-image" style="text-align: center">';
+                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" 
+                                            class="exercise-unique-answer-image" style="text-align: center">';
                                 } else {
-                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" '
-                                        .'class="exercise-unique-answer-image col-xs-6 col-sm-12" style="text-align: center">';
+                                    $s .= '<div id="answer'.$questionId.$numAnswer.'" 
+                                            class="exercise-unique-answer-image col-xs-6 col-sm-12" 
+                                            style="text-align: center">';
                                 }
                             } else {
-                                $s .= '<div id="answer'.$questionId.$numAnswer.'" '
-                                    .'class="exercise-unique-answer-image col-xs-6 col-md-3" style="text-align: center">';
+                                $s .= '<div id="answer'.$questionId.$numAnswer.'" 
+                                        class="exercise-unique-answer-image col-xs-6 col-md-3" 
+                                        style="text-align: center">';
                             }
                         }
 
@@ -585,10 +580,8 @@ class ExerciseLib
                         // or filled to display the answer in the Question preview of the exercise/admin.php page
                         $displayForStudent = true;
                         $listAnswerInfo = FillBlanks::getAnswerInfo($answer);
-
                         // Correct answers
                         $correctAnswerList = $listAnswerInfo['words'];
-
                         // Student's answer
                         $studentAnswerList = [];
                         if (isset($user_choice[0]['answer'])) {
@@ -599,8 +592,8 @@ class ExerciseLib
                             $studentAnswerList = $arrayStudentAnswer['student_answer'];
                         }
 
-                        // If the question must be shown with the answer (in page exercise/admin.php) for teacher preview
-                        // set the student-answer to the correct answer
+                        // If the question must be shown with the answer (in page exercise/admin.php)
+                        // for teacher preview set the student-answer to the correct answer
                         if ($debug_mark_answer) {
                             $studentAnswerList = $correctAnswerList;
                             $displayForStudent = false;
@@ -675,13 +668,11 @@ class ExerciseLib
                             $rowLastAttempt = Database::fetch_array($rsLastAttempt);
                             $answer = $rowLastAttempt['answer'];
                             if (empty($answer)) {
-                                $_SESSION['calculatedAnswerId'][$questionId] = mt_rand(
-                                    1,
-                                    $nbrAnswers
-                                );
-                                $answer = $objAnswerTmp->selectAnswer(
-                                    $_SESSION['calculatedAnswerId'][$questionId]
-                                );
+                                $randomValue = mt_rand(1, $nbrAnswers);
+                                $answer = $objAnswerTmp->selectAnswer($randomValue);
+                                $calculatedAnswer = Session::read('calculatedAnswerId');
+                                $calculatedAnswer[$questionId] = $randomValue;
+                                Session::write('calculatedAnswerId', $calculatedAnswer);
                             }
                         }
 
@@ -693,7 +684,8 @@ class ExerciseLib
                             $correctAnswerList
                         );
 
-                        // get student answer to display it if student go back to previous calculated answer question in a test
+                        // get student answer to display it if student go back to
+                        // previous calculated answer question in a test
                         if (isset($user_choice[0]['answer'])) {
                             api_preg_match_all(
                                 '/\[[^]]+\]/',
@@ -703,9 +695,7 @@ class ExerciseLib
                             $studentAnswerListTobecleaned = $studentAnswerList[0];
                             $studentAnswerList = [];
 
-                            for ($i = 0; $i < count(
-                                $studentAnswerListTobecleaned
-                            ); $i++) {
+                            for ($i = 0; $i < count($studentAnswerListTobecleaned); $i++) {
                                 $answerCorrected = $studentAnswerListTobecleaned[$i];
                                 $answerCorrected = api_preg_replace(
                                     '| / <font color="green"><b>.*$|',
@@ -732,7 +722,8 @@ class ExerciseLib
                             }
                         }
 
-                        // If display preview of answer in test view for exemple, set the student answer to the correct answers
+                        // If display preview of answer in test view for exaemple,
+                        // set the student answer to the correct answers
                         if ($debug_mark_answer) {
                             // contain the rights answers surronded with brackets
                             $studentAnswerList = $correctAnswerList[0];
@@ -751,7 +742,7 @@ class ExerciseLib
                             ' '.$answer.' '
                         );
                         if (!empty($correctAnswerList) && !empty($studentAnswerList)) {
-                            $answer = "";
+                            $answer = '';
                             $i = 0;
                             foreach ($studentAnswerList as $studentItem) {
                                 // remove surronding brackets
@@ -816,7 +807,9 @@ class ExerciseLib
                             // Id of select is # question + # of option
                             $s .= '<td width="10%" valign="top" align="center">
                                 <div class="select-matching">
-                                <select id="choice_id_'.$current_item.'_'.$lines_count.'" name="choice['.$questionId.']['.$numAnswer.']">';
+                                <select 
+                                    id="choice_id_'.$current_item.'_'.$lines_count.'" 
+                                    name="choice['.$questionId.']['.$numAnswer.']">';
 
                             // fills the list-box
                             foreach ($select_items as $key => $val) {
@@ -840,7 +833,12 @@ class ExerciseLib
                             $s .= '</select></div></td><td width="5%" class="separate">&nbsp;</td>';
                             $s .= '<td width="40%" valign="top" >';
                             if (isset($select_items[$lines_count])) {
-                                $s .= '<div class="text-right"><p class="indent">'.$select_items[$lines_count]['letter'].'.&nbsp; '.$select_items[$lines_count]['answer'].'</p></div>';
+                                $s .= '<div class="text-right">
+                                        <p class="indent">'.
+                                            $select_items[$lines_count]['letter'].'.&nbsp; '.
+                                            $select_items[$lines_count]['answer'].'
+                                        </p>
+                                        </div>';
                             } else {
                                 $s .= '&nbsp;';
                             }
@@ -855,7 +853,8 @@ class ExerciseLib
                                     $s .= '<tr>
                                       <td colspan="2"></td>
                                       <td valign="top">';
-                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.$select_items[$lines_count]['answer'];
+                                    $s .= '<b>'.$select_items[$lines_count]['letter'].'.</b> '.
+                                                $select_items[$lines_count]['answer'];
                                     $s .= "</td>
                                 </tr>";
                                     $lines_count++;
@@ -962,7 +961,8 @@ class ExerciseLib
                             $s .= <<<HTML
                             <tr>
                                 <td width="45%">
-                                    <div id="window_{$windowId}" class="window window_left_question window{$questionId}_question">
+                                    <div id="window_{$windowId}" 
+                                        class="window window_left_question window{$questionId}_question">
                                         <strong>$lines_count.</strong> 
                                         $answer
                                     </div>
@@ -1034,7 +1034,8 @@ HTML;
                             if (isset($select_items[$lines_count])) {
                                 $s .= <<<HTML
                                 <div id="window_{$windowId}_answer" class="window window_right_question">
-                                    <strong>{$select_items[$lines_count]['letter']}.</strong> {$select_items[$lines_count]['answer']}
+                                    <strong>{$select_items[$lines_count]['letter']}.</strong> 
+                                    {$select_items[$lines_count]['answer']}
                                 </div>
 HTML;
                             } else {
@@ -1142,7 +1143,6 @@ HTML;
                     }
                 }
             }
-            $questionName = $objQuestionTmp->selectTitle();
             $questionDescription = $objQuestionTmp->selectDescription();
 
             // Get the answers, make a list
@@ -1314,12 +1314,16 @@ HOTSPOT;
                                         <div class="btn-group" data-toggle="buttons">
                                             <label class="btn btn-default active"
                                                 aria-label="'.get_lang('AddAnnotationPath').'">
-                                                <input type="radio" value="0" name="'.$questionId.'-options" autocomplete="off" checked>
+                                                <input 
+                                                    type="radio" value="0" 
+                                                    name="'.$questionId.'-options" autocomplete="off" checked>
                                                 <span class="fa fa-pencil" aria-hidden="true"></span>
                                             </label>
                                             <label class="btn btn-default"
                                                 aria-label="'.get_lang('AddAnnotationText').'">
-                                                <input type="radio" value="1" name="'.$questionId.'-options" autocomplete="off">
+                                                <input 
+                                                    type="radio" value="1" 
+                                                    name="'.$questionId.'-options" autocomplete="off">
                                                 <span class="fa fa-font fa-fw" aria-hidden="true"></span>
                                             </label>
                                         </div>
@@ -1365,6 +1369,11 @@ HOTSPOT;
 
             $res_fb_type = Database::query($sql);
             $result = Database::fetch_array($res_fb_type, 'ASSOC');
+            $result['duration_formatted'] = '';
+            if (!empty($result['exe_duration'])) {
+                $time = api_format_time($result['exe_duration'], 'js');
+                $result['duration_formatted'] = $time;
+            }
         }
 
         return $result;
@@ -1673,6 +1682,7 @@ HOTSPOT;
      * @param bool   $showSessionField
      * @param bool   $showExerciseCategories
      * @param array  $userExtraFieldsToAdd
+     * @param bool   $useCommaAsDecimalPoint
      *
      * @return array
      */
@@ -1687,7 +1697,8 @@ HOTSPOT;
         $courseCode = null,
         $showSessionField = false,
         $showExerciseCategories = false,
-        $userExtraFieldsToAdd = []
+        $userExtraFieldsToAdd = [],
+        $useCommaAsDecimalPoint = false
     ) {
         //@todo replace all this globals
         global $documentPath, $filter;
@@ -1700,7 +1711,12 @@ HOTSPOT;
         }
 
         $course_id = $courseInfo['real_id'];
-        $is_allowedToEdit = api_is_allowed_to_edit(null, true) || api_is_allowed_to_edit(true) || api_is_drh() || api_is_student_boss();
+        $is_allowedToEdit =
+            api_is_allowed_to_edit(null, true) ||
+            api_is_allowed_to_edit(true) ||
+            api_is_drh() ||
+            api_is_student_boss() ||
+            api_is_session_admin();
         $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
         $TBL_EXERCICES = Database::get_course_table(TABLE_QUIZ_TEST);
         $TBL_GROUP_REL_USER = Database::get_course_table(TABLE_GROUP_USER);
@@ -1927,9 +1943,7 @@ HOTSPOT;
             return $rowx[0];
         }
 
-        $teacher_list = CourseManager::get_teacher_list_from_course_code(
-            $courseCode
-        );
+        $teacher_list = CourseManager::get_teacher_list_from_course_code($courseCode);
         $teacher_id_list = [];
         if (!empty($teacher_list)) {
             foreach ($teacher_list as $teacher) {
@@ -1937,6 +1951,15 @@ HOTSPOT;
             }
         }
 
+        $scoreDisplay = new ScoreDisplay();
+        $decimalSeparator = '.';
+        $thousandSeparator = ',';
+
+        if ($useCommaAsDecimalPoint) {
+            $decimalSeparator = ',';
+            $thousandSeparator = '';
+        }
+
         $listInfo = [];
         // Simple exercises
         if (empty($hotpotatoe_where)) {
@@ -1954,7 +1977,6 @@ HOTSPOT;
             while ($rowx = Database::fetch_array($resx, 'ASSOC')) {
                 $results[] = $rowx;
             }
-
             $group_list = GroupManager::get_group_list(null, $courseInfo);
             $clean_group_list = [];
             if (!empty($group_list)) {
@@ -1967,7 +1989,7 @@ HOTSPOT;
             $lp_list = $lp_list_obj->get_flat_list();
             $oldIds = array_column($lp_list, 'lp_old_id', 'iid');
 
-            if (is_array($results)) {
+            if (!empty($results)) {
                 $users_array_id = [];
                 $from_gradebook = false;
                 if (isset($_GET['gradebook']) && $_GET['gradebook'] == 'view') {
@@ -1975,11 +1997,7 @@ HOTSPOT;
                 }
                 $sizeof = count($results);
                 $user_list_id = [];
-                $locked = api_resource_is_locked_by_gradebook(
-                    $exercise_id,
-                    LINK_EXERCISE
-                );
-
+                $locked = api_resource_is_locked_by_gradebook($exercise_id, LINK_EXERCISE);
                 $timeNow = strtotime(api_get_utc_datetime());
                 // Looping results
                 for ($i = 0; $i < $sizeof; $i++) {
@@ -2061,10 +2079,9 @@ HOTSPOT;
                     // we filter the results if we have the permission to
                     $result_disabled = 0;
                     if (isset($results[$i]['results_disabled'])) {
-                        $result_disabled = intval(
-                            $results[$i]['results_disabled']
-                        );
+                        $result_disabled = (int) $results[$i]['results_disabled'];
                     }
+
                     if ($result_disabled == 0) {
                         $my_res = $results[$i]['exe_result'];
                         $my_total = $results[$i]['exe_weighting'];
@@ -2076,9 +2093,19 @@ HOTSPOT;
                             $my_res = 0;
                         }
 
-                        $score = self::show_score($my_res, $my_total);
+                        $score = self::show_score(
+                            $my_res,
+                            $my_total,
+                            true,
+                            true,
+                            false,
+                            false,
+                            $decimalSeparator,
+                            $thousandSeparator
+                        );
 
                         $actions = '<div class="pull-right">';
+
                         if ($is_allowedToEdit) {
                             if (isset($teacher_id_list)) {
                                 if (in_array(
@@ -2163,7 +2190,7 @@ HOTSPOT;
                             }
 
                             // Admin can always delete the attempt
-                            if (($locked == false || api_is_platform_admin()) && !api_is_student_boss()) {
+                            if (($locked == false || (api_is_platform_admin()) && !api_is_student_boss())) {
                                 $ip = Tracking::get_ip_from_user_event(
                                     $results[$i]['exe_user_id'],
                                     api_get_utc_datetime(),
@@ -2208,6 +2235,9 @@ HOTSPOT;
                                 if (api_is_drh() && !api_is_platform_admin()) {
                                     $delete_link = null;
                                 }
+                                if (api_is_session_admin()) {
+                                    $delete_link = '';
+                                }
                                 if ($revised == 3) {
                                     $delete_link = null;
                                 }
@@ -2295,7 +2325,9 @@ HOTSPOT;
                                                 $category_was_added_for_this_test = true;
                                             }
 
-                                            if (isset($objQuestionTmp->category_list) && !empty($objQuestionTmp->category_list)) {
+                                            if (isset($objQuestionTmp->category_list) &&
+                                                !empty($objQuestionTmp->category_list)
+                                            ) {
                                                 foreach ($objQuestionTmp->category_list as $category_id) {
                                                     $category_list[$category_id]['score'] += $my_total_score;
                                                     $category_list[$category_id]['total'] += $my_total_weight;
@@ -2321,7 +2353,17 @@ HOTSPOT;
                             }
 
                             foreach ($category_list as $categoryId => $result) {
-                                $scoreToDisplay = self::show_score($result['score'], $result['total']);
+                                $scoreToDisplay = self::show_score(
+                                    $result['score'],
+                                    $result['total'],
+                                    true,
+                                    true,
+                                    false,
+                                    false,
+                                    $decimalSeparator,
+                                    $thousandSeparator
+                                );
+
                                 $results[$i]['category_'.$categoryId] = $scoreToDisplay;
                                 $results[$i]['category_'.$categoryId.'_score_percentage'] = self::show_score(
                                     $result['score'],
@@ -2329,7 +2371,9 @@ HOTSPOT;
                                     true,
                                     true,
                                     true, // $show_only_percentage = false
-                                    true // hide % sign
+                                    true, // hide % sign
+                                    $decimalSeparator,
+                                    $thousandSeparator
                                 );
                                 $results[$i]['category_'.$categoryId.'_only_score'] = $result['score'];
                                 $results[$i]['category_'.$categoryId.'_total'] = $result['total'];
@@ -2344,10 +2388,23 @@ HOTSPOT;
                                 true,
                                 true,
                                 true,
-                                true
+                                true,
+                                $decimalSeparator,
+                                $thousandSeparator
+                            );
+
+                            $results[$i]['only_score'] = $scoreDisplay->format_score(
+                                $my_res,
+                                false,
+                                $decimalSeparator,
+                                $thousandSeparator
+                            );
+                            $results[$i]['total'] = $scoreDisplay->format_score(
+                                $my_total,
+                                false,
+                                $decimalSeparator,
+                                $thousandSeparator
                             );
-                            $results[$i]['only_score'] = $my_res;
-                            $results[$i]['total'] = $my_total;
                             $results[$i]['lp'] = $lp_name;
                             $results[$i]['actions'] = $actions;
                             $listInfo[] = $results[$i];
@@ -2421,12 +2478,14 @@ HOTSPOT;
      * Converts the score with the exercise_max_note and exercise_min_score
      * the platform settings + formats the results using the float_format function.
      *
-     * @param float $score
-     * @param float $weight
-     * @param bool  $show_percentage       show percentage or not
-     * @param bool  $use_platform_settings use or not the platform settings
-     * @param bool  $show_only_percentage
-     * @param bool  $hidePercetangeSign    hide "%" sign
+     * @param float  $score
+     * @param float  $weight
+     * @param bool   $show_percentage       show percentage or not
+     * @param bool   $use_platform_settings use or not the platform settings
+     * @param bool   $show_only_percentage
+     * @param bool   $hidePercentageSign    hide "%" sign
+     * @param string $decimalSeparator
+     * @param string $thousandSeparator
      *
      * @return string an html with the score modified
      */
@@ -2436,7 +2495,9 @@ HOTSPOT;
         $show_percentage = true,
         $use_platform_settings = true,
         $show_only_percentage = false,
-        $hidePercetangeSign = false
+        $hidePercentageSign = false,
+        $decimalSeparator = '.',
+        $thousandSeparator = ','
     ) {
         if (is_null($score) && is_null($weight)) {
             return '-';
@@ -2458,14 +2519,13 @@ HOTSPOT;
         $percentage = (100 * $score) / ($weight != 0 ? $weight : 1);
 
         // Formats values
-        $percentage = float_format($percentage, 1);
-        $score = float_format($score, 1);
-        $weight = float_format($weight, 1);
+        $percentage = float_format($percentage, 1, $decimalSeparator, $thousandSeparator);
+        $score = float_format($score, 1, $decimalSeparator, $thousandSeparator);
+        $weight = float_format($weight, 1, $decimalSeparator, $thousandSeparator);
 
-        $html = '';
         if ($show_percentage) {
             $percentageSign = '%';
-            if ($hidePercetangeSign) {
+            if ($hidePercentageSign) {
                 $percentageSign = '';
             }
             $html = $percentage."$percentageSign  ($score / $weight)";
@@ -2495,9 +2555,7 @@ HOTSPOT;
      */
     public static function getModelStyle($model, $percentage)
     {
-        //$modelWithStyle = get_lang($model['name']);
         $modelWithStyle = '<span class="'.$model['css_class'].'"> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>';
-        //$modelWithStyle .= $percentage;
 
         return $modelWithStyle;
     }
@@ -3257,10 +3315,7 @@ EOT;
         $avg_score = 0;
         if (!empty($user_results)) {
             foreach ($user_results as $result) {
-                if (!empty($result['exe_weighting']) && intval(
-                        $result['exe_weighting']
-                    ) != 0
-                ) {
+                if (!empty($result['exe_weighting']) && intval($result['exe_weighting']) != 0) {
                     $score = $result['exe_result'] / $result['exe_weighting'];
                     $avg_score += $score;
                 }
@@ -3456,7 +3511,7 @@ EOT;
      * @param int $question_id
      * @param int $exercise_id
      *
-     * @return int
+     * @return array
      */
     public static function getNumberStudentsFillBlanksAnswerCount(
         $question_id,
@@ -3480,7 +3535,6 @@ EOT;
         );
 
         $arrayCount = [];
-
         foreach ($listFillTheBlankResult as $resultCount) {
             foreach ($resultCount as $index => $count) {
                 //this is only for declare the array index per answer
@@ -3697,7 +3751,6 @@ EOT;
         $answer_id = intval($answer_id);
         $exercise_id = intval($exercise_id);
         $courseId = api_get_course_int_id($course_code);
-        $course_code = Database::escape_string($course_code);
         $session_id = intval($session_id);
 
         switch ($question_type) {
@@ -3944,43 +3997,6 @@ EOT;
         return $return;
     }
 
-    /**
-     * @param string $in_name     is the name and the id of the <select>
-     * @param string $in_default  default value for option
-     * @param string $in_onchange
-     *
-     * @return string the html code of the <select>
-     */
-    public static function displayGroupMenu($in_name, $in_default, $in_onchange = "")
-    {
-        // check the default value of option
-        $tabSelected = [$in_default => " selected='selected' "];
-        $res = "";
-        $res .= "<select name='$in_name' id='$in_name' onchange='".$in_onchange."' >";
-        $res .= "<option value='-1'".$tabSelected["-1"].">-- ".get_lang(
-                'AllGroups'
-            )." --</option>";
-        $res .= "<option value='0'".$tabSelected["0"].">- ".get_lang(
-                'NotInAGroup'
-            )." -</option>";
-        $tabGroups = GroupManager::get_group_list();
-        $currentCatId = 0;
-        for ($i = 0; $i < count($tabGroups); $i++) {
-            $tabCategory = GroupManager::get_category_from_group(
-                $tabGroups[$i]['iid']
-            );
-            if ($tabCategory["id"] != $currentCatId) {
-                $res .= "<option value='-1' disabled='disabled'>".$tabCategory["title"]."</option>";
-                $currentCatId = $tabCategory["id"];
-            }
-            $res .= "<option ".$tabSelected[$tabGroups[$i]["id"]]."style='margin-left:40px' value='".$tabGroups[$i]["id"]."'>".
-                    $tabGroups[$i]["name"]."</option>";
-        }
-        $res .= "</select>";
-
-        return $res;
-    }
-
     /**
      * @param int $exe_id
      */
@@ -4107,14 +4123,9 @@ EOT;
                 $user_info = api_get_user_info($exercise_stat_info['exe_user_id']);
                 if ($user_info) {
                     // Shows exercise header
-                    echo $objExercise->show_exercise_result_header(
+                    echo $objExercise->showExerciseResultHeader(
                         $user_info,
-                        api_convert_and_format_date(
-                            $exercise_stat_info['start_date'],
-                            DATE_TIME_FORMAT_LONG
-                        ),
-                        $exercise_stat_info['duration'],
-                        $exercise_stat_info['user_ip']
+                        $exercise_stat_info
                     );
                 }
             }
@@ -4186,6 +4197,7 @@ EOT;
                     'question' => $result['open_question'],
                     'answer' => $result['open_answer'],
                     'answer_type' => $result['answer_type'],
+                    'generated_oral_file' => $result['generated_oral_file'],
                 ];
 
                 $my_total_score = $result['score'];
@@ -4295,11 +4307,18 @@ EOT;
                 if ($show_results) {
                     $question_content .= '</div>';
                 }
-                if (!$show_only_score) {
+                if ($objExercise->showExpectedChoice()) {
                     $exercise_content .= Display::div(
                         Display::panel($question_content),
                         ['class' => 'question-panel']
                     );
+                } else {
+                    if (!$show_only_score) {
+                        $exercise_content .= Display::div(
+                            Display::panel($question_content),
+                            ['class' => 'question-panel']
+                        );
+                    }
                 }
             } // end foreach() block that loops over all questions
         }
@@ -4330,7 +4349,8 @@ EOT;
         }
 
         if ($show_all_but_expected_answer) {
-            $exercise_content .= "<div class='normal-message'>".get_lang('ExerciseWithFeedbackWithoutCorrectionComment')."</div>";
+            $exercise_content .= "<div class='normal-message'>".
+                get_lang('ExerciseWithFeedbackWithoutCorrectionComment')."</div>";
         }
 
         // Remove audio auto play from questions on results page - refs BT#7939
@@ -4341,6 +4361,12 @@ EOT;
         );
 
         echo $total_score_text;
+
+        // Ofaj change BT#11784
+        if (!empty($objExercise->description)) {
+            echo Display::div($objExercise->description, ['class' => 'exercise_description']);
+        }
+
         echo $exercise_content;
 
         if (!$show_only_score) {
@@ -4392,24 +4418,6 @@ EOT;
         }
     }
 
-    /**
-     * @param string $class
-     * @param string $scoreLabel
-     * @param string $result
-     *
-     * @return string
-     */
-    public static function getQuestionRibbon($class, $scoreLabel, $result)
-    {
-        return '<div class="ribbon">
-                    <div class="rib rib-'.$class.'">
-                        <h3>'.$scoreLabel.'</h3>
-                    </div>
-                    <h4>'.get_lang('Score').': '.$result.'</h4>
-                </div>'
-        ;
-    }
-
     /**
      * @param Exercise $objExercise
      * @param float    $score

+ 196 - 184
main/inc/lib/exercise_show_functions.lib.php

@@ -45,71 +45,84 @@ class ExerciseShowFunctions
             $resultsDisabled,
             $showTotalScoreAndUserChoices
         );
-        if (strpos($originalStudentAnswer, 'font color') !== false) {
+        // ofaj
+        /*if (strpos($originalStudentAnswer, 'font color') !== false) {
             $answerHTML = $originalStudentAnswer;
-        }
-
+        }*/
         if (empty($id)) {
             echo '<tr><td>';
             echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
             echo '</td></tr>';
         } else {
-            ?>
-            <tr>
-                <td>
-                    <?php echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY); ?>
-                </td>
-
-                <?php
-                if (!api_is_allowed_to_edit(null, true) && $feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                    ?>
-                    <td>
-                        <?php
-                        $comm = Event::get_comments($id, $questionId); ?>
-                    </td>
-                <?php
-                } ?>
-            </tr>
-        <?php
+            echo '<tr><td>';
+            echo Security::remove_XSS($answerHTML, COURSEMANAGERLOWSECURITY);
+            echo '</td>';
+            if (!api_is_allowed_to_edit(null, true) && $feedbackType != EXERCISE_FEEDBACK_TYPE_EXAM) {
+                echo '<td>';
+                $comm = Event::get_comments($id, $questionId);
+                echo '</td>';
+            }
+            echo '</tr>';
         }
     }
 
     /**
      * Shows the answer to a calculated question, as HTML.
      *
+     *  @param Exercise $exercise
      * @param string    Answer text
      * @param int       Exercise ID
      * @param int       Question ID
      */
     public static function display_calculated_answer(
+        $exercise,
         $feedback_type,
         $answer,
         $id,
         $questionId,
         $results_disabled,
-        $showTotalScoreAndUserChoices
+        $showTotalScoreAndUserChoices,
+        $expectedChoice = '',
+        $choice = '',
+        $status = ''
     ) {
-        if (empty($id)) {
-            echo '<tr><td>'.Security::remove_XSS($answer).'</td></tr>';
+        if ($exercise->showExpectedChoice()) {
+            if (empty($id)) {
+                echo '<tr><td>'.Security::remove_XSS($answer).'</td>';
+                echo '<td>'.Security::remove_XSS($choice).'</td>';
+                echo '<td>'.Security::remove_XSS($expectedChoice).'</td>';
+                echo '<td>'.Security::remove_XSS($status).'</td>';
+                echo '</tr>';
+            } else {
+                echo '<tr><td>';
+                echo Security::remove_XSS($answer);
+                echo '</td><td>';
+                echo Security::remove_XSS($choice);
+                echo '</td><td>';
+                echo Security::remove_XSS($expectedChoice);
+                echo '</td><td>';
+                echo Security::remove_XSS($status);
+                echo '</td>';
+                if (!api_is_allowed_to_edit(null, true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+                    echo '<td>';
+                    $comm = Event::get_comments($id, $questionId);
+                    echo '</td>';
+                }
+                echo '</tr>';
+            }
         } else {
-            ?>
-            <tr>
-                <td>
-                    <?php
-                    echo Security::remove_XSS($answer); ?>
-                </td>
-
-            <?php
-            if (!api_is_allowed_to_edit(null, true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                ?>
-                <td>
-                    <?php
-                    $comm = Event::get_comments($id, $questionId); ?>
-                </td>
-            <?php
-            } ?>
-            </tr>
-        <?php
+            if (empty($id)) {
+                echo '<tr><td>'.Security::remove_XSS($answer).'</td></tr>';
+            } else {
+                echo '<tr><td>';
+                echo Security::remove_XSS($answer);
+                if (!api_is_allowed_to_edit(null, true) && $feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+                    echo '<td>';
+                    $comm = Event::get_comments($id, $questionId);
+                    echo '</td>';
+                }
+                echo '</tr>';
+            }
         }
     }
 
@@ -228,10 +241,9 @@ class ExerciseShowFunctions
         }
 
         if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+            $hide_expected_answer = true;
             if ($showTotalScoreAndUserChoices) {
                 $hide_expected_answer = false;
-            } else {
-                $hide_expected_answer = true;
             }
         }
 
@@ -250,57 +262,54 @@ class ExerciseShowFunctions
             "#ED2024",
             "#3B3B3B",
             "#F7BDE2",
-        ]; ?>
-        <table class="data_table">
-        <tr>
-            <td class="text-center" width="5%">
-                <span class="fa fa-square fa-fw fa-2x" aria-hidden="true" style="color: <?php echo $hotspot_colors[$orderColor]; ?>"></span>
-            </td>
-            <td class="text-left" width="25%">
-                <?php echo "$answerId - $answer"; ?>
-            </td>
-            <td class="text-left" width="10%">
-                <?php
-                if (!$hide_expected_answer) {
-                    $my_choice = $studentChoice ? get_lang('Correct') : get_lang('Fault');
-                    echo $my_choice;
-                } ?>
-            </td>
-            <?php if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                    ?>
-            <td class="text-left" width="60%">
-                <?php
-                if ($studentChoice) {
-                    echo '<span style="font-weight: bold; color: #008000;">'.nl2br($answerComment).'</span>';
-                } ?>
-            </td>
-            <?php
-                } else {
-                    ?>
-                <td class="text-left" width="60%">&nbsp;</td>
-            <?php
-                } ?>
-        </tr>
-        <?php
+        ];
+        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 '</td>';
+        echo '<td class="text-left" width="25%">';
+        echo "$answerId - $answer";
+        echo '</td>';
+        echo '<td class="text-left" width="10%">';
+        if (!$hide_expected_answer) {
+            $status = Display::label(get_lang('Incorrect'), 'danger');
+            if ($studentChoice) {
+                $status = Display::label(get_lang('Correct'), 'success');
+            }
+            echo $status;
+        }
+        echo '</td>';
+        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            echo '<td class="text-left" width="60%">';
+            if ($studentChoice) {
+                echo '<span style="font-weight: bold; color: #008000;">'.nl2br($answerComment).'</span>';
+            }
+            echo '</td>';
+        } else {
+            echo '<td class="text-left" width="60%">&nbsp;</td>';
+        }
+        echo '</tr>';
     }
 
     /**
      * Display the answers to a multiple choice question.
      *
-     * @param int    $feedback_type                Feedback type
-     * @param int    $answerType                   Answer type
-     * @param int    $studentChoice                Student choice
-     * @param string $answer                       Textual answer
-     * @param string $answerComment                Comment on answer
-     * @param string $answerCorrect                Correct answer comment
-     * @param int    $id                           Exercise ID
-     * @param int    $questionId                   Question ID
-     * @param bool   $ans                          Whether to show the answer comment or not
-     * @param bool   $resultsDisabled
-     * @param bool   $showTotalScoreAndUserChoices
-     * @param bool   $export
+     * @param Exercise $exercise
+     * @param int      $feedback_type                Feedback type
+     * @param int      $answerType                   Answer type
+     * @param int      $studentChoice                Student choice
+     * @param string   $answer                       Textual answer
+     * @param string   $answerComment                Comment on answer
+     * @param string   $answerCorrect                Correct answer comment
+     * @param int      $id                           Exercise ID
+     * @param int      $questionId                   Question ID
+     * @param bool     $ans                          Whether to show the answer comment or not
+     * @param bool     $resultsDisabled
+     * @param bool     $showTotalScoreAndUserChoices
+     * @param bool     $export
      */
     public static function display_unique_or_multiple_answer(
+        $exercise,
         $feedback_type,
         $answerType,
         $studentChoice,
@@ -332,10 +341,9 @@ class ExerciseShowFunctions
         }
 
         if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+            $hide_expected_answer = true;
             if ($showTotalScoreAndUserChoices) {
                 $hide_expected_answer = false;
-            } else {
-                $hide_expected_answer = true;
             }
         }
 
@@ -344,56 +352,59 @@ class ExerciseShowFunctions
         $icon .= '.png';
         $iconAnswer = in_array($answerType, [UNIQUE_ANSWER, UNIQUE_ANSWER_NO_OPTION]) ? 'radio' : 'checkbox';
         $iconAnswer .= $answerCorrect ? '_on' : '_off';
-        $iconAnswer .= '.png'; ?>
-        <tr>
-        <td width="5%">
-            <?php echo Display::return_icon($icon, null, null, ICON_SIZE_TINY); ?>
-        </td>
-        <td width="5%">
-            <?php if (!$hide_expected_answer) {
+        $iconAnswer .= '.png';
+
+        echo '<tr>';
+        echo '<td width="5%">';
+        echo Display::return_icon($icon, null, null, ICON_SIZE_TINY);
+        echo '</td><td width="5%">';
+        if (!$hide_expected_answer) {
             echo Display::return_icon($iconAnswer, null, null, ICON_SIZE_TINY);
         } else {
             echo "-";
-        } ?>
-        </td>
-        <td width="40%">
-            <?php
-            echo $answer; ?>
-        </td>
+        }
+        echo '</td><td width="40%">';
+        echo $answer;
+        echo '</td>';
+
+        if ($exercise->showExpectedChoice()) {
+            $status = Display::label(get_lang('Incorrect'), 'danger');
+            if ($studentChoice) {
+                if ($answerCorrect) {
+                    $status = Display::label(get_lang('Correct'), 'success');
+                }
+            }
+            echo '<td width="20%">';
+            echo $status;
+            echo '</td>';
+        }
 
-        <?php if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-                ?>
-        <td width="20%">
-            <?php
+        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            echo '<td width="20%">';
             if ($studentChoice) {
+                $color = 'black';
                 if ($answerCorrect) {
                     $color = 'green';
-                } else {
-                    $color = 'black';
                 }
                 if ($hide_expected_answer) {
                     $color = '';
                 }
-                echo '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
-            } ?>
-        </td>
-            <?php
+                echo '<span style="font-weight: bold; color: '.$color.';">'.strip_tags($answerComment).'</span>';
+            }
+            echo '</td>';
             if ($ans == 1) {
                 $comm = Event::get_comments($id, $questionId);
-            } ?>
-         <?php
-            } else {
-                ?>
-            <td>&nbsp;</td>
-        <?php
-            } ?>
-        </tr>
-        <?php
+            }
+        } else {
+            echo '<td>&nbsp;</td>';
+        }
+        echo '</tr>';
     }
 
     /**
      * Display the answers to a multiple choice question.
      *
+     * @param Exercise $exercise
      * @param int Answer type
      * @param int Student choice
      * @param string  Textual answer
@@ -404,6 +415,7 @@ class ExerciseShowFunctions
      * @param bool Whether to show the answer comment or not
      */
     public static function display_multiple_answer_true_false(
+        $exercise,
         $feedback_type,
         $answerType,
         $studentChoice,
@@ -417,35 +429,27 @@ class ExerciseShowFunctions
         $showTotalScoreAndUserChoices
     ) {
         $hide_expected_answer = false;
-
         if ($feedback_type == 0 && ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ONLY)) {
             $hide_expected_answer = true;
         }
 
         if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+            $hide_expected_answer = true;
             if ($showTotalScoreAndUserChoices) {
                 $hide_expected_answer = false;
-            } else {
-                $hide_expected_answer = true;
             }
-        } ?>
-        <tr>
-        <td width="5%">
-        <?php
+        }
+        echo '<tr><td width="5%">';
         $course_id = api_get_course_int_id();
         $new_options = Question::readQuestionOption($questionId, $course_id);
-
-        //Your choice
+        // Your choice
         if (isset($new_options[$studentChoice])) {
             echo get_lang($new_options[$studentChoice]['name']);
         } else {
             echo '-';
-        } ?>
-        </td>
-        <td width="5%">
-        <?php
-
-        //Expected choice
+        }
+        echo '</td><td width="5%">';
+        // Expected choice
         if (!$hide_expected_answer) {
             if (isset($new_options[$answerCorrect])) {
                 echo get_lang($new_options[$answerCorrect]['name']);
@@ -454,16 +458,23 @@ class ExerciseShowFunctions
             }
         } else {
             echo '-';
-        } ?>
-        </td>
-        <td width="40%">
-            <?php echo $answer; ?>
-        </td>
-
-        <?php if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-            ?>
-        <td width="20%">
-            <?php
+        }
+        echo '</td><td width="40%">';
+        echo $answer;
+        echo '</td>';
+        if ($exercise->showExpectedChoice()) {
+            $status = Display::label(get_lang('Incorrect'), 'danger');
+            if (isset($new_options[$studentChoice])) {
+                if ($studentChoice == $answerCorrect) {
+                    $status = Display::label(get_lang('Correct'), 'success');
+                }
+            }
+            echo '<td width="20%">';
+            echo $status;
+            echo '</td>';
+        }
+        if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
+            echo '<td width="20%">';
             $color = "black";
             if (isset($new_options[$studentChoice])) {
                 if ($studentChoice == $answerCorrect) {
@@ -473,27 +484,22 @@ class ExerciseShowFunctions
                 if ($hide_expected_answer) {
                     $color = '';
                 }
-
                 echo '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
-            } ?>
-        </td>
-            <?php
+            }
+            echo '</td>';
             if ($ans == 1) {
                 $comm = Event::get_comments($id, $questionId);
-            } ?>
-         <?php
+            }
         } else {
-            ?>
-            <td>&nbsp;</td>
-        <?php
-        } ?>
-        </tr>
-        <?php
+            echo '<td>&nbsp;</td>';
+        }
+        echo '</tr>';
     }
 
     /**
      * Display the answers to a multiple choice question.
      *
+     * @param Exercise $exercise
      * @param int Answer type
      * @param int Student choice
      * @param string  Textual answer
@@ -504,6 +510,7 @@ class ExerciseShowFunctions
      * @param bool Whether to show the answer comment or not
      */
     public static function display_multiple_answer_combination_true_false(
+        $exercise,
         $feedback_type,
         $answerType,
         $studentChoice,
@@ -522,26 +529,22 @@ class ExerciseShowFunctions
         }
 
         if ($resultsDisabled == RESULT_DISABLE_SHOW_SCORE_ATTEMPT_SHOW_ANSWERS_LAST_ATTEMPT) {
+            $hide_expected_answer = true;
             if ($showTotalScoreAndUserChoices) {
                 $hide_expected_answer = false;
-            } else {
-                $hide_expected_answer = true;
             }
-        } ?>
-        <tr>
-        <td width="5%">
-        <?php
-        //Your choice
+        }
+
+        echo '<tr><td width="5%">';
+        // Your choice
         $question = new MultipleAnswerCombinationTrueFalse();
         if (isset($question->options[$studentChoice])) {
             echo $question->options[$studentChoice];
         } else {
             echo $question->options[2];
-        } ?>
-        </td>
-        <td width="5%">
-        <?php
-        //Expected choice
+        }
+        echo '</td><td width="5%">';
+        // Expected choice
         if (!$hide_expected_answer) {
             if (isset($question->options[$answerCorrect])) {
                 echo $question->options[$answerCorrect];
@@ -550,18 +553,28 @@ class ExerciseShowFunctions
             }
         } else {
             echo '-';
-        } ?>
-        </td>
-        <td width="40%">
-            <?php
-            //my answer
-            echo $answer; ?>
-        </td>
-        <?php
+        }
+        echo '</td>';
+        echo '<td width="40%">';
+        // my answer
+        echo $answer;
+        echo '</td>';
+
+        if ($exercise->showExpectedChoice()) {
+            $status = '';
+            if (isset($studentChoice)) {
+                $status = Display::label(get_lang('Incorrect'), 'danger');
+                if ($studentChoice == $answerCorrect) {
+                    $status = Display::label(get_lang('Correct'), 'success');
+                }
+            }
+            echo '<td width="20%">';
+            echo $status;
+            echo '</td>';
+        }
+
         if ($feedback_type != EXERCISE_FEEDBACK_TYPE_EXAM) {
-            ?>
-        <td width="20%">
-            <?php
+            echo '<td width="20%">';
             //@todo replace this harcoded value
             if ($studentChoice) {
                 $color = "black";
@@ -572,9 +585,8 @@ class ExerciseShowFunctions
                     $color = '';
                 }
                 echo '<span style="font-weight: bold; color: '.$color.';">'.nl2br($answerComment).'</span>';
-            } ?>
-        </td>
-            <?php
+            }
+            echo '</td>';
             if ($ans == 1) {
                 $comm = Event::get_comments($id, $questionId);
             }

+ 3 - 5
main/inc/lib/export.lib.inc.php

@@ -43,10 +43,9 @@ class Export
         }
 
         $filePath = api_get_path(SYS_ARCHIVE_PATH).uniqid('').'.csv';
-
-        $writer = new CsvWriter();
-        $writer->setStream(fopen($filePath, 'w'));
-
+        $stream = fopen($filePath, 'w');
+        $writer = new CsvWriter(';', '"', $stream, true);
+        $writer->prepare();
         foreach ($data as $item) {
             if (empty($item)) {
                 $writer->writeItem([]);
@@ -78,7 +77,6 @@ class Export
         $file = new \SplFileObject($filePath, 'w');
         $writer = new ExcelWriter($file);
         $writer->prepare();
-
         foreach ($data as $row) {
             $writer->writeItem($row);
         }

+ 60 - 18
main/inc/lib/extra_field.lib.php

@@ -140,6 +140,9 @@ class ExtraField extends Model
             case 'survey':
                 $this->extraFieldType = EntityExtraField::SURVEY_FIELD_TYPE;
                 break;
+            case 'scheduled_announcement':
+                $this->extraFieldType = EntityExtraField::SCHEDULED_ANNOUNCEMENT;
+                break;
         }
 
         $this->pageUrl = 'extra_fields.php?type='.$this->type;
@@ -160,7 +163,7 @@ class ExtraField extends Model
      */
     public static function getValidExtraFieldTypes()
     {
-        return [
+        $result = [
             'user',
             'course',
             'session',
@@ -174,6 +177,12 @@ class ExtraField extends Model
             'user_certificate',
             'survey',
         ];
+
+        if (api_get_configuration_value('allow_scheduled_announcements')) {
+            $result[] = 'scheduled_announcement';
+        }
+
+        return $result;
     }
 
     /**
@@ -893,19 +902,22 @@ class ExtraField extends Model
     public function save($params, $show_query = false)
     {
         $fieldInfo = self::get_handler_field_info_by_field_variable($params['variable']);
-        $params = self::clean_parameters($params);
+        $params = $this->clean_parameters($params);
         $params['extra_field_type'] = $this->extraFieldType;
 
         if ($fieldInfo) {
             return $fieldInfo['id'];
         } else {
             $id = parent::save($params, $show_query);
-            if ($id) {
-                $session_field_option = new ExtraFieldOption($this->type);
-                $params['field_id'] = $id;
-                $session_field_option->save($params);
+
+            if (!$id) {
+                return 0;
             }
 
+            $session_field_option = new ExtraFieldOption($this->type);
+            $params['field_id'] = $id;
+            $session_field_option->save($params);
+
             return $id;
         }
     }
@@ -915,7 +927,7 @@ class ExtraField extends Model
      */
     public function update($params, $showQuery = false)
     {
-        $params = self::clean_parameters($params);
+        $params = $this->clean_parameters($params);
         if (isset($params['id'])) {
             $field_option = new ExtraFieldOption($this->type);
             $params['field_id'] = $params['id'];
@@ -925,7 +937,7 @@ class ExtraField extends Model
             $field_option->save($params, $showQuery);
         }
 
-        parent::update($params, $showQuery);
+        return parent::update($params, $showQuery);
     }
 
     /**
@@ -1150,8 +1162,8 @@ class ExtraField extends Model
                         break;
                     case self::FIELD_TYPE_SELECT_MULTIPLE:
                         $options = [];
-                        foreach ($field_details['options'] as $option_id => $option_details) {
-                            $options[$option_details['option_value']] = $option_details['display_text'];
+                        foreach ($field_details['options'] as $optionDetails) {
+                            $options[$optionDetails['option_value']] = $optionDetails['display_text'];
                         }
                         $form->addElement(
                             'select',
@@ -1446,12 +1458,12 @@ class ExtraField extends Model
                         $form->applyFilter('extra_'.$field_details['variable'], 'stripslashes');
                         $form->applyFilter('extra_'.$field_details['variable'], 'trim');
 
-                        $allowed_picture_types = ['jpg', 'jpeg', 'png', 'gif'];
+                        $allowedPictureTypes = ['jpg', 'jpeg', 'png', 'gif'];
                         $form->addRule(
                             'extra_'.$field_details['variable'],
-                            get_lang('OnlyImagesAllowed').' ('.implode(',', $allowed_picture_types).')',
+                            get_lang('OnlyImagesAllowed').' ('.implode(',', $allowedPictureTypes).')',
                             'filetype',
-                            $allowed_picture_types
+                            $allowedPictureTypes
                         );
 
                         if ($freezeElement) {
@@ -1484,14 +1496,45 @@ class ExtraField extends Model
                             array_key_exists($fieldVariable, $extraData)
                         ) {
                             if (file_exists(api_get_path(SYS_UPLOAD_PATH).$extraData[$fieldVariable])) {
-                                $fieldTexts[] = Display::url(
-                                    api_get_path(WEB_UPLOAD_PATH).$extraData[$fieldVariable],
+                                $linkToDelete = '';
+                                $divItemId = $field_details['variable'];
+                                if (api_is_platform_admin()) {
+                                    $url = api_get_path(WEB_AJAX_PATH).'extra_field.ajax.php?type='.$this->type;
+                                    $url .= '&a=delete_file&field_id='.$field_details['id'].'&item_id='.$itemId;
+
+                                    $deleteId = $field_details['variable'].'_delete';
+                                    $form->addHtml("
+                                        <script>
+                                            $(document).ready(function() {                                      
+                                                $('#".$deleteId."').on('click', function() {
+                                                    $.ajax({			
+                                                        type: 'GET',
+                                                        url: '".$url."',			
+                                                        success: function(result) {		    
+                                                            if (result == 1) {
+                                                                $('#".$divItemId."').html('".get_lang('Deleted')."');
+                                                            }			    
+                                                        }
+                                                    });
+                                                });
+                                            });
+                                        </script>
+                                    ");
+
+                                    $linkToDelete = '&nbsp;'.Display::url(
+                                        Display::return_icon('delete.png', get_lang('Delete')),
+                                        'javascript:void(0)',
+                                        ['id' => $deleteId]
+                                    );
+                                }
+                                $fieldTexts[] = '<div id="'.$divItemId.'">'.Display::url(
+                                    basename($extraData[$fieldVariable]),
                                     api_get_path(WEB_UPLOAD_PATH).$extraData[$fieldVariable],
                                     [
                                         'title' => $field_details['display_text'],
                                         'target' => '_blank',
                                     ]
-                                );
+                                ).$linkToDelete.'</div>';
                             }
                         }
 
@@ -1614,7 +1657,7 @@ class ExtraField extends Model
 
                                     $('#map_extra_{$field_details['variable']}')
                                         .html('<div class=\"alert alert-info\">"
-                                            .get_lang('YouNeedToActivateTheGoogleMapsPluginInAdminPlatformToSeeTheMap')
+                                            .addslashes(get_lang('YouNeedToActivateTheGoogleMapsPluginInAdminPlatformToSeeTheMap'))
                                             ."</div>');
                                 });
 
@@ -1635,7 +1678,6 @@ class ExtraField extends Model
                                         var geoOptions = {
                                             enableHighAccuracy: true
                                         };
-
                                         navigator.geolocation.getCurrentPosition(geoPosition, geoError, geoOptions);
                                     }
                                 }

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

@@ -104,12 +104,14 @@ class ExtraFieldValue extends Model
         }
 
         $type = $this->getExtraField()->getExtraFieldType();
+
         $extraField = new ExtraField($this->type);
         $extraFields = $extraField->get_all(null, 'option_order');
 
         // Parse params.
         foreach ($extraFields as $fieldDetails) {
             $field_variable = $fieldDetails['variable'];
+
             // if the field is not visible to the user in the end, we need to apply special rules
             if ($fieldDetails['visible_to_self'] != 1) {
                 //only admins should be able to add those values
@@ -125,10 +127,9 @@ class ExtraFieldValue extends Model
                 continue;
             }
 
+            $value = '';
             if (isset($params['extra_'.$field_variable])) {
                 $value = $params['extra_'.$field_variable];
-            } else {
-                $value = '';
             }
             $extraFieldInfo = $this->getExtraField()->get_handler_field_info_by_field_variable($field_variable);
 
@@ -214,7 +215,6 @@ class ExtraFieldValue extends Model
                         $fieldRelTag->setFieldId($extraFieldInfo['id']);
                         $fieldRelTag->setItemId($params['item_id']);
                         $fieldRelTag->setTagId($tag->getId());
-
                         $em->persist($fieldRelTag);
                     }
 
@@ -260,7 +260,7 @@ class ExtraFieldValue extends Model
                             'value' => $fileDirStored.$fileName,
                             'comment' => $comment,
                         ];
-                        self::save($newParams);
+                        $this->save($newParams);
                     }
                     break;
                 case ExtraField::FIELD_TYPE_FILE:
@@ -281,6 +281,10 @@ class ExtraFieldValue extends Model
                             $fileDir = api_get_path(SYS_UPLOAD_PATH).'work/';
                             $fileDirStored = "work/";
                             break;
+                        case 'scheduled_announcement':
+                            $fileDir = api_get_path(SYS_UPLOAD_PATH).'scheduled_announcement/';
+                            $fileDirStored = 'scheduled_announcement/';
+                            break;
                     }
 
                     $cleanedName = api_replace_dangerous_char($value['name']);
@@ -302,7 +306,7 @@ class ExtraFieldValue extends Model
                             $new_params['comment'] = $comment;
                         }
 
-                        self::save($new_params);
+                        $this->save($new_params);
                     }
                     break;
                 case ExtraField::FIELD_TYPE_CHECKBOX:
@@ -320,7 +324,7 @@ class ExtraFieldValue extends Model
                         'comment' => $comment,
                     ];
 
-                    self::save($newParams);
+                    $this->save($newParams);
 
                     break;
                 default:
@@ -330,7 +334,7 @@ class ExtraFieldValue extends Model
                         'value' => $value,
                         'comment' => $comment,
                     ];
-                    self::save($newParams, $showQuery);
+                    $this->save($newParams, $showQuery);
             }
         }
     }
@@ -401,12 +405,11 @@ class ExtraFieldValue extends Model
                 case ExtraField::FIELD_TYPE_DOUBLE_SELECT:
                 case ExtraField::FIELD_TYPE_SELECT_WITH_TEXT_FIELD:
                     if (is_array($value)) {
+                        $value_to_insert = null;
                         if (isset($value['extra_'.$extraFieldInfo['variable']]) &&
                             isset($value['extra_'.$extraFieldInfo['variable'].'_second'])
                         ) {
                             $value_to_insert = $value['extra_'.$extraFieldInfo['variable']].'::'.$value['extra_'.$extraFieldInfo['variable'].'_second'];
-                        } else {
-                            $value_to_insert = null;
                         }
                     }
                     break;
@@ -994,20 +997,35 @@ class ExtraFieldValue extends Model
      * @param int $itemId
      * @param int $fieldId
      * @param int $fieldValue
+     *
+     * @return bool
      */
     public function deleteValuesByHandlerAndFieldAndValue($itemId, $fieldId, $fieldValue)
     {
         $itemId = intval($itemId);
         $fieldId = intval($fieldId);
-        $fieldValue = Database::escape_string($fieldValue);
 
-        $sql = "DELETE FROM {$this->table}
+        $fieldData = $this->getExtraField()->get($fieldId);
+        if ($fieldData) {
+            $fieldValue = Database::escape_string($fieldValue);
+
+            $sql = "DELETE FROM {$this->table}
                 WHERE
                     item_id = '$itemId' AND
                     field_id = '$fieldId' AND
                     value = '$fieldValue'
                 ";
-        Database::query($sql);
+            Database::query($sql);
+
+            // Delete file from uploads
+            if ($fieldData['field_type'] == ExtraField::FIELD_TYPE_FILE) {
+                api_remove_uploaded_file($this->type, basename($fieldValue));
+            }
+
+            return true;
+        }
+
+        return false;
     }
 
     /**

+ 129 - 64
main/inc/lib/fileUpload.lib.php

@@ -276,7 +276,8 @@ function handle_uploaded_document(
             $sessionId,
             $groupId,
             $output,
-            $onlyUploadFile
+            $onlyUploadFile,
+            $whatIfFileExists
         );
     } elseif ($unzip == 1 && !preg_match('/.zip$/', strtolower($uploadedFile['name']))) {
         // We can only unzip ZIP files (no gz, tar,...)
@@ -371,27 +372,6 @@ function handle_uploaded_document(
                 $sessionId
             );
 
-            $documentList = DocumentManager::getDocumentByPathInCourse(
-                $courseInfo,
-                $filePath //$filePath
-            );
-
-            // This means that the path already exists in this course.
-            if (!empty($documentList) && $whatIfFileExists != 'overwrite') {
-                //$found = false;
-                // Checking if we are talking about the same course + session
-                /*foreach ($documentList as $document) {
-                    if ($document['session_id'] == $sessionId) {
-                        $found = true;
-                        break;
-                    }
-                }*/
-
-                //if ($found == false) {
-                //$whatIfFileExists = 'rename';
-                //}
-            }
-
             // What to do if the target file exists
             switch ($whatIfFileExists) {
                 // Overwrite the file if it exists
@@ -481,11 +461,13 @@ function handle_uploaded_document(
 
                             // If the file is in a folder, we need to update all parent folders
                             item_property_update_on_folder($courseInfo, $uploadPath, $userId);
+
                             // Display success message with extra info to user
                             if ($output) {
                                 Display::addFlash(
                                     Display::return_message(
-                                        get_lang('UplUploadSucceeded').'<br /> '.$documentTitle.' '.get_lang('UplFileOverwritten'),
+                                        get_lang('UplUploadSucceeded').'<br /> '.
+                                        $documentTitle.' '.get_lang('UplFileOverwritten'),
                                         'confirmation',
                                         false
                                     )
@@ -622,7 +604,8 @@ function handle_uploaded_document(
                         if ($output) {
                             Display::addFlash(
                                 Display::return_message(
-                                    get_lang('UplUploadSucceeded').'<br />'.get_lang('UplFileSavedAs').' '.$documentTitle,
+                                    get_lang('UplUploadSucceeded').'<br />'.
+                                    get_lang('UplFileSavedAs').' '.$documentTitle,
                                     'success',
                                     false
                                 )
@@ -644,12 +627,27 @@ function handle_uploaded_document(
                         return false;
                     }
                     break;
+                case 'nothing':
+                    $fileExists = file_exists($fullPath);
+                    if ($fileExists) {
+                        if ($output) {
+                            Display::addFlash(
+                                Display::return_message(
+                                    $uploadPath.$cleanName.' '.get_lang('UplAlreadyExists'),
+                                    'warning',
+                                    false
+                                )
+                            );
+                        }
+                        break;
+                    }
+                    // no break
                 default:
                     // Only save the file if it doesn't exist or warn user if it does exist
                     if (file_exists($fullPath) && $docId) {
                         if ($output) {
                             Display::addFlash(
-                                Display::return_message($cleanName.' '.get_lang('UplAlreadyExists'), 'error', false)
+                                Display::return_message($cleanName.' '.get_lang('UplAlreadyExists'), 'warning', false)
                             );
                         }
                     } else {
@@ -1104,16 +1102,17 @@ function unzip_uploaded_file($uploaded_file, $upload_path, $base_work_dir, $max_
  *
  * @param array  $courseInfo
  * @param array  $userInfo
- * @param array  $uploaded_file  - follows the $_FILES Structure
- * @param string $uploadPath     - destination of the upload.
- *                               This path is to append to $base_work_dir
- * @param string $base_work_dir  - base working directory of the module
- * @param int    $maxFilledSpace - amount of bytes to not exceed in the base
- *                               working directory
+ * @param array  $uploaded_file    - follows the $_FILES Structure
+ * @param string $uploadPath       - destination of the upload.
+ *                                 This path is to append to $base_work_dir
+ * @param string $base_work_dir    - base working directory of the module
+ * @param int    $maxFilledSpace   - amount of bytes to not exceed in the base
+ *                                 working directory
  * @param int    $sessionId
- * @param int    $groupId        group.id
- * @param bool   $output         Optional. If no output not wanted on success, set to false.
+ * @param int    $groupId          group.id
+ * @param bool   $output           Optional. If no output not wanted on success, set to false.
  * @param bool   $onlyUploadFile
+ * @param string $whatIfFileExists (only works if $onlyUploadFile is false)
  *
  * @return bool true if it succeeds false otherwise
  */
@@ -1127,7 +1126,8 @@ function unzip_uploaded_document(
     $sessionId = 0,
     $groupId = 0,
     $output = true,
-    $onlyUploadFile = false
+    $onlyUploadFile = false,
+    $whatIfFileExists = 'overwrite'
 ) {
     $zip = new PclZip($uploaded_file['tmp_name']);
 
@@ -1148,7 +1148,7 @@ function unzip_uploaded_document(
     $destinationDir = api_get_path(SYS_ARCHIVE_PATH).$folder;
     mkdir($destinationDir, api_get_permissions_for_new_directories(), true);
 
-    /*	Uncompress zip file*/
+    // Uncompress zip file
     // We extract using a callback function that "cleans" the path
     $zip->extract(
         PCLZIP_OPT_PATH,
@@ -1168,7 +1168,8 @@ function unzip_uploaded_document(
             $sessionId,
             $groupId,
             $output,
-            ['path' => $uploadPath]
+            ['path' => $uploadPath],
+            $whatIfFileExists
         );
     } else {
         // Copy result
@@ -1245,8 +1246,8 @@ function clean_up_path($path)
  * The list of extensions accepted/rejected can be found from
  * api_get_setting('upload_extensions_exclude') and api_get_setting('upload_extensions_include').
  *
- * @param	string 	filename passed by reference. The filename will be modified
- * if filter rules say so! (you can include path but the filename should look like 'abc.html')
+ * @param string $filename passed by reference. The filename will be modified
+ *                         if filter rules say so! (you can include path but the filename should look like 'abc.html')
  *
  * @return int 0 to skip file, 1 to keep file
  */
@@ -1312,8 +1313,8 @@ function filter_extension(&$filename)
  * @param int    $readonly
  * @param bool   $saveVisibility
  * @param int    $group_id       group.id
- * @param int    $session_id     Session ID, if any
- * @param int    $userId         creator id
+ * @param int    $sessionId      Session ID, if any
+ * @param int    $userId         creator user id
  *
  * @return int id if inserted document
  */
@@ -1326,14 +1327,14 @@ function add_document(
     $comment = null,
     $readonly = 0,
     $saveVisibility = true,
-    $group_id = null,
-    $session_id = 0,
+    $group_id = 0,
+    $sessionId = 0,
     $userId = 0
 ) {
-    $session_id = empty($session_id) ? api_get_session_id() : $session_id;
+    $sessionId = empty($sessionId) ? api_get_session_id() : $sessionId;
     $userId = empty($userId) ? api_get_user_id() : $userId;
 
-    $readonly = intval($readonly);
+    $readonly = (int) $readonly;
     $c_id = $courseInfo['real_id'];
     $params = [
         'c_id' => $c_id,
@@ -1343,7 +1344,7 @@ function add_document(
         'title' => $title,
         'comment' => $comment,
         'readonly' => $readonly,
-        'session_id' => $session_id,
+        'session_id' => $sessionId,
     ];
     $table = Database::get_course_table(TABLE_DOCUMENT);
     $documentId = Database::insert($table, $params);
@@ -1357,11 +1358,34 @@ function add_document(
                 TOOL_DOCUMENT,
                 $group_id,
                 $courseInfo,
-                $session_id,
+                $sessionId,
                 $userId
             );
         }
 
+        $allowNotification = api_get_configuration_value('send_notification_when_document_added');
+        if ($allowNotification) {
+            $courseTitle = $courseInfo['title'];
+            if (!empty($sessionId)) {
+                $sessionInfo = api_get_session_info($sessionId);
+                $courseTitle .= " ( ".$sessionInfo['name'].") ";
+            }
+
+            $url = api_get_path(WEB_CODE_PATH).
+                'document/showinframes.php?cidReq='.$courseInfo['code'].'&id_session='.$sessionId.'&id='.$documentId;
+            $link = Display::url(basename($title), $url, ['target' => '_blank']);
+            $userInfo = api_get_user_info($userId);
+
+            $message = sprintf(
+                get_lang('DocumentXHasBeenAddedToDocumentInYourCourseXByUserX'),
+                $link,
+                $courseTitle,
+                $userInfo['complete_name']
+            );
+            $subject = sprintf(get_lang('NewDocumentAddedToCourseX'), $courseTitle);
+            MessageManager::sendMessageToAllUsersInCourse($subject, $message, $courseInfo, $sessionId);
+        }
+
         return $documentId;
     } else {
         return false;
@@ -1898,13 +1922,15 @@ function build_missing_files_form($missing_files, $upload_path, $file_name)
  *
  * @param array  $courseInfo
  * @param array  $userInfo
- * @param string $base_work_dir
- * @param string $folderPath
+ * @param string $base_work_dir    course document dir
+ * @param string $folderPath       folder to read
  * @param int    $sessionId
- * @param int    $groupId       group.id
+ * @param int    $groupId          group.id
  * @param bool   $output
  * @param array  $parent
- * @param string $uploadPath
+ * @param string $whatIfFileExists
+ *
+ * @return bool
  */
 function add_all_documents_in_folder_to_database(
     $courseInfo,
@@ -1914,7 +1940,8 @@ function add_all_documents_in_folder_to_database(
     $sessionId = 0,
     $groupId = 0,
     $output = false,
-    $parent = []
+    $parent = [],
+    $whatIfFileExists = 'overwrite'
 ) {
     if (empty($userInfo) || empty($courseInfo)) {
         return false;
@@ -1932,12 +1959,11 @@ function add_all_documents_in_folder_to_database(
                 continue;
             }
 
-            $parentPath = null;
-
+            $parentPath = '';
             if (!empty($parent) && isset($parent['path'])) {
                 $parentPath = $parent['path'];
                 if ($parentPath == '/') {
-                    $parentPath = null;
+                    $parentPath = '';
                 }
             }
 
@@ -1954,14 +1980,52 @@ function add_all_documents_in_folder_to_database(
                 );
 
                 if ($folderExists === true) {
-                    $documentId = DocumentManager::get_document_id($courseInfo, $completePath, $sessionId);
-                    if ($documentId) {
-                        $newFolderData = DocumentManager::get_document_data_by_id(
-                            $documentId,
-                            $courseInfo['code'],
-                            false,
-                            $sessionId
-                        );
+                    switch ($whatIfFileExists) {
+                        case 'overwrite':
+                            $documentId = DocumentManager::get_document_id($courseInfo, $completePath, $sessionId);
+                            if ($documentId) {
+                                $newFolderData = DocumentManager::get_document_data_by_id(
+                                    $documentId,
+                                    $courseInfo['code'],
+                                    false,
+                                    $sessionId
+                                );
+                            }
+                            break;
+                        case 'rename':
+                            $newFolderData = create_unexisting_directory(
+                                $courseInfo,
+                                $userId,
+                                $sessionId,
+                                $groupId,
+                                null,
+                                $base_work_dir,
+                                $completePath,
+                                null,
+                                null,
+                                true
+                            );
+                            break;
+                        case 'nothing':
+                            if ($output) {
+                                $documentId = DocumentManager::get_document_id($courseInfo, $completePath, $sessionId);
+                                if ($documentId) {
+                                    $folderData = DocumentManager::get_document_data_by_id(
+                                        $documentId,
+                                        $courseInfo['code'],
+                                        false,
+                                        $sessionId
+                                    );
+                                    Display::addFlash(
+                                        Display::return_message(
+                                            $folderData['path'].' '.get_lang('UplAlreadyExists'),
+                                            'warning'
+                                        )
+                                    );
+                                }
+                            }
+                            continue 2;
+                            break;
                     }
                 } else {
                     $newFolderData = create_unexisting_directory(
@@ -1987,7 +2051,8 @@ function add_all_documents_in_folder_to_database(
                     $sessionId,
                     $groupId,
                     $output,
-                    $newFolderData
+                    $newFolderData,
+                    $whatIfFileExists
                 );
             } else {
                 // Rename
@@ -2009,7 +2074,7 @@ function add_all_documents_in_folder_to_database(
                     $groupId,
                     null,
                     0,
-                    'overwrite',
+                    $whatIfFileExists,
                     $output,
                     false,
                     null,

+ 46 - 8
main/inc/lib/glossary.lib.php

@@ -400,19 +400,14 @@ class GlossaryManager
                 Display::return_icon('new_glossary_term.png', get_lang('TermAddNew'), '', ICON_SIZE_MEDIUM).'</a>';
         }
 
-        if (!api_is_anonymous()) {
-            $actionsLeft .= '<a href="index.php?'.api_get_cidreq().'&action=export">'.
-                Display::return_icon('export_csv.png', get_lang('ExportGlossaryAsCSV'), '', ICON_SIZE_MEDIUM).'</a>';
-        }
-
         if (api_is_allowed_to_edit(null, true)) {
             $actionsLeft .= '<a href="index.php?'.api_get_cidreq().'&action=import">'.
-                Display::return_icon('import_csv.png', get_lang('ImportGlossary'), '', ICON_SIZE_MEDIUM).'</a>';
+                Display::return_icon('import.png', get_lang('ImportGlossary'), '', ICON_SIZE_MEDIUM).'</a>';
         }
 
         if (!api_is_anonymous()) {
-            $actionsLeft .= '<a href="index.php?'.api_get_cidreq().'&action=export_to_pdf">'.
-                Display::return_icon('pdf.png', get_lang('ExportToPDF'), '', ICON_SIZE_MEDIUM).'</a>';
+            $actionsLeft .= '<a id="export_opener" href="'.api_get_self().'?'.api_get_cidreq().'&action=export">'.
+                Display::return_icon('save.png', get_lang('Export'), '', ICON_SIZE_MEDIUM).'</a>';
         }
 
         if (($view == 'table') || (!isset($view))) {
@@ -753,6 +748,7 @@ class GlossaryManager
         $template = new Template('', false, false, false, true, false, false);
         $layout = $template->get_template('glossary/export_pdf.tpl');
         $template->assign('items', $data);
+
         $html = $template->fetch($layout);
         $courseCode = api_get_course_id();
         $pdf = new PDF();
@@ -804,4 +800,46 @@ class GlossaryManager
 
         return false;
     }
+
+    /**
+     * @param string $format
+     */
+    public static function exportToFormat($format)
+    {
+        if ($format == 'pdf') {
+            self::export_to_pdf();
+
+            return;
+        }
+
+        $data = GlossaryManager::get_glossary_data(
+            0,
+            GlossaryManager::get_number_glossary_terms(api_get_session_id()),
+            0,
+            'ASC'
+        );
+        usort($data, 'sorter');
+        $list = [];
+        $list[] = ['term', 'definition'];
+        $allowStrip = api_get_configuration_value('allow_remove_tags_in_glossary_export');
+        foreach ($data as $line) {
+            $definition = $line[1];
+            if ($allowStrip) {
+                // htmlspecialchars_decode replace &#39 to '
+                // strip_tags remove HTML tags
+                $definition = htmlspecialchars_decode(strip_tags($definition), ENT_QUOTES);
+            }
+            $list[] = [$line[0], $definition];
+        }
+        $filename = 'glossary_course_'.api_get_course_id();
+
+        switch ($format) {
+            case 'csv':
+                Export::arrayToCsv($list, $filename);
+                break;
+            case 'xls':
+                Export::arrayToXls($list, $filename);
+                break;
+        }
+    }
 }

+ 29 - 15
main/inc/lib/image.lib.php

@@ -65,27 +65,37 @@ class Image
         }
     }
 
+    /**
+     * @param $cropParameters
+     *
+     * @return bool
+     */
     public function crop($cropParameters)
     {
         $image_size = $this->get_image_size($this->image_wrapper->path);
         $src_width = $image_size['width'];
         $src_height = $image_size['height'];
-        $cropParameters = explode(",", $cropParameters);
-        $x = intval($cropParameters[0]);
-        $y = intval($cropParameters[1]);
-        $width = intval($cropParameters[2]);
-        $height = intval($cropParameters[3]);
-
-        $image = $this->image_wrapper->crop(
-            $x,
-            $y,
-            $width,
-            $height,
-            $src_width,
-            $src_height
-        );
+        $cropParameters = explode(',', $cropParameters);
+
+        if (isset($cropParameters[0]) && isset($cropParameters[1])) {
+            $x = intval($cropParameters[0]);
+            $y = intval($cropParameters[1]);
+            $width = intval($cropParameters[2]);
+            $height = intval($cropParameters[3]);
+
+            $image = $this->image_wrapper->crop(
+                $x,
+                $y,
+                $width,
+                $height,
+                $src_width,
+                $src_height
+            );
+
+            return $image;
+        }
 
-        return $image;
+        return false;
     }
 
     /**
@@ -278,6 +288,8 @@ class ImagickWrapper extends ImageWrapper
      * @param int $height     the height of the crop
      * @param int $src_width  the source width of the original image
      * @param int $src_height the source height of the original image
+     *
+     * @return bool
      */
     public function crop($x, $y, $width, $height, $src_width, $src_height)
     {
@@ -287,6 +299,8 @@ class ImagickWrapper extends ImageWrapper
         $this->image->cropimage($width, $height, $x, $y);
         $this->width = $width;
         $this->height = $height;
+
+        return true;
     }
 
     public function send_image(

+ 41 - 23
main/inc/lib/import.lib.php

@@ -1,9 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
-use Ddeboer\DataImport\Reader\CsvReader;
-use Ddeboer\DataImport\Workflow;
-use Ddeboer\DataImport\Writer\ArrayWriter;
+use Ddeboer\DataImport\Reader\ExcelReader;
+use League\Csv\Reader;
 
 /**
  * Class Import
@@ -18,22 +17,11 @@ class Import
      * @param string $path
      * @param bool   $setFirstRowAsHeader
      *
-     * @return CsvReader
+     * @return array
      */
     public static function csv_reader($path, $setFirstRowAsHeader = true)
     {
-        if (empty($path)) {
-            return false;
-        }
-
-        $file = new \SplFileObject($path);
-        $csvReader = new CsvReader($file, ';');
-
-        if ($setFirstRowAsHeader) {
-            $csvReader->setHeaderRowNumber(0);
-        }
-
-        return $csvReader;
+        return self::csvToArray($path);
     }
 
     /**
@@ -57,14 +45,44 @@ class Import
      */
     public static function csvToArray($filename)
     {
-        $csvReader = self::csv_reader($filename);
-        $resultArray = [];
-        if ($csvReader) {
-            $workflow = new Workflow\StepAggregator($csvReader);
-            $writer = new ArrayWriter($resultArray);
-            $workflow->addWriter($writer)->process();
+        if (empty($filename)) {
+            return [];
+        }
+
+        $reader = Reader::createFromPath($filename, 'r');
+        if ($reader) {
+            $reader->setDelimiter(';');
+            $reader->stripBom(true);
+            /*$contents = $reader->__toString();
+            if (!Utf8::isUtf8($contents)) {
+                // If file is not in utf8 try converting to ISO-8859-15
+                if ($reader->getStreamFilterMode() == 1) {
+                    $reader->appendStreamFilter('convert.iconv.ISO-8859-15/UTF-8');
+                }
+            }*/
+
+            $iterator = $reader->fetchAssoc(0);
+
+            return iterator_to_array($iterator);
+        }
+
+        return [];
+    }
+
+    /**
+     * @param string $filename
+     *
+     * @return array
+     */
+    public static function xlsToArray($filename)
+    {
+        if (empty($filename)) {
+            return [];
         }
 
-        return $resultArray;
+        $file = new \SplFileObject($filename);
+        $reader = new ExcelReader($file, 0);
+
+        return $reader;
     }
 }

+ 7 - 14
main/inc/lib/internationalization.lib.php

@@ -347,8 +347,8 @@ function api_get_timezone()
  * Returns the given date as a DATETIME in UTC timezone.
  * This function should be used before entering any date in the DB.
  *
- * @param mixed $time                        The date to be converted (can be a string supported by date() or a timestamp)
- * @param bool  $return_null_if_invalid_date if the date is not correct return null instead of the current date
+ * @param mixed $time                    date to be converted (can be a string supported by date() or a timestamp)
+ * @param bool  $returnNullIfInvalidDate if the date is not correct return null instead of the current date
  * @param bool  $returnObj
  *
  * @return string The DATETIME in UTC to be inserted in the DB,
@@ -359,16 +359,15 @@ function api_get_timezone()
  */
 function api_get_utc_datetime(
     $time = null,
-    $return_null_if_invalid_date = false,
+    $returnNullIfInvalidDate = false,
     $returnObj = false
 ) {
-    $to_timezone = 'UTC';
     if (is_null($time) || empty($time) || $time === '0000-00-00 00:00:00') {
-        if ($return_null_if_invalid_date) {
+        if ($returnNullIfInvalidDate) {
             return null;
         }
         if ($returnObj) {
-            return $date = new DateTime(gmdate('Y-m-d H:i:s'));
+            return $date = new DateTime(gmdate('Y-m-d H:i:s'), new DateTimeZone('UTC'));
         }
 
         return gmdate('Y-m-d H:i:s');
@@ -376,15 +375,14 @@ function api_get_utc_datetime(
 
     // If time is a timestamp, return directly in utc
     if (is_numeric($time)) {
-        $time = intval($time);
+        $time = (int) $time;
 
         return gmdate('Y-m-d H:i:s', $time);
     }
-
     try {
         $fromTimezone = api_get_timezone();
         $date = new DateTime($time, new DateTimezone($fromTimezone));
-        $date->setTimezone(new DateTimeZone($to_timezone));
+        $date->setTimezone(new DateTimeZone('UTC'));
         if ($returnObj) {
             return $date;
         } else {
@@ -945,11 +943,6 @@ function api_is_western_name_order($format = null, $language = null)
  */
 function api_sort_by_first_name($language = null)
 {
-    $userNameSortBy = api_get_setting('user_name_sort_by');
-    if (!empty($userNameSortBy) && in_array($userNameSortBy, ['firstname', 'lastname'])) {
-        return $userNameSortBy == 'firstname' ? true : false;
-    }
-
     static $sort_by_first_name = [];
 
     if (empty($language)) {

+ 6 - 1
main/inc/lib/javascript/ckeditor/config_js.php

@@ -12,7 +12,12 @@ if (api_get_setting('more_buttons_maximized_mode') === 'true') {
 $template = new Template();
 $template->setCSSEditor();
 $template->assign('moreButtonsInMaximizedMode', $moreButtonsInMaximizedMode);
-$template->assign('course_condition', api_get_cidreq());
+$courseId = api_get_course_int_id();
+$courseCondition = '';
+if (!empty($courseId)) {
+    $courseCondition = api_get_cidreq();
+}
+$template->assign('course_condition', $courseCondition);
 
 header('Content-type: application/x-javascript');
 $template->display($template->get_template('javascript/editor/ckeditor/config_js.tpl'));

+ 319 - 337
main/inc/lib/javascript/ckeditor/plugins/video/dialogs/video.js

@@ -1,372 +1,354 @@
 CKEDITOR.dialog.add( 'video', function ( editor )
 {
-	var lang = editor.lang.video;
+    var lang = editor.lang.video;
 
-	function commitValue( videoNode, extraStyles )
-	{
-		var value=this.getValue();
+    function commitValue( videoNode, extraStyles )
+    {
+        var value=this.getValue();
 
-		if ( !value && this.id=='id' )
-			value = generateId();
+        if ( !value && this.id=='id' )
+            value = generateId();
 
         if (value == '') {
             return;
         }
 
-		videoNode.setAttribute( this.id, value);
+        videoNode.setAttribute( this.id, value);
 
-		if ( !value )
-			return;
-		switch( this.id )
-		{
-			case 'poster':
-				extraStyles.backgroundImage = 'url(' + value + ')';
-				break;
-			case 'width':
-				extraStyles.width = value + 'px';
-				break;
-			case 'height':
-				extraStyles.height = value + 'px';
-				break;
-		}
-	}
+        if ( !value )
+            return;
+        switch( this.id )
+        {
+            case 'poster':
+                extraStyles.backgroundImage = 'url(' + value + ')';
+                break;
+            case 'width':
+                extraStyles.width = value + 'px';
+                break;
+            case 'height':
+                extraStyles.height = value + 'px';
+                break;
+        }
+    }
 
-	function commitSrc( videoNode, extraStyles, videos )
-	{
-		var match = this.id.match(/(\w+)(\d)/),
-			id = match[1],
-			number = parseInt(match[2], 10);
+    function commitSrc( videoNode, extraStyles, videos )
+    {
+        var match = this.id.match(/(\w+)(\d)/),
+            id = match[1],
+            number = parseInt(match[2], 10);
 
-		var video = videos[number] || (videos[number]={});
-		video[id] = this.getValue();
-	}
+        var video = videos[number] || (videos[number]={});
+        video[id] = this.getValue();
+    }
 
-	function loadValue( videoNode )
-	{
-		if ( videoNode )
-			this.setValue( videoNode.getAttribute( this.id ) );
-		else
-		{
-			if ( this.id == 'id')
-				this.setValue( generateId() );
-		}
-	}
+    function loadValue( videoNode )
+    {
+        if ( videoNode )
+            this.setValue( videoNode.getAttribute( this.id ) );
+        else
+        {
+            if ( this.id == 'id')
+                this.setValue( generateId() );
+        }
+    }
 
-	function loadSrc( videoNode, videos )
-	{
-		var match = this.id.match(/(\w+)(\d)/),
-			id = match[1],
-			number = parseInt(match[2], 10);
+    function loadSrc( videoNode, videos )
+    {
+        var match = this.id.match(/(\w+)(\d)/),
+            id = match[1],
+            number = parseInt(match[2], 10);
 
-		var video = videos[number];
-		if (!video)
-			return;
-		this.setValue( video[ id ] );
-	}
+        var video = videos[number];
+        if (!video)
+            return;
+        this.setValue( video[ id ] );
+    }
 
-	function generateId()
-	{
-		var now = new Date();
-		return 'video' + now.getFullYear() + now.getMonth() + now.getDate() + now.getHours() + now.getMinutes() + now.getSeconds();
-	}
+    function generateId()
+    {
+        var now = new Date();
+        return 'video' + now.getFullYear() + now.getMonth() + now.getDate() + now.getHours() + now.getMinutes() + now.getSeconds();
+    }
 
-	// To automatically get the dimensions of the poster image
-	var onImgLoadEvent = function()
-	{
-		// Image is ready.
-		var preview = this.previewImage;
-		preview.removeListener( 'load', onImgLoadEvent );
-		preview.removeListener( 'error', onImgLoadErrorEvent );
-		preview.removeListener( 'abort', onImgLoadErrorEvent );
+    // To automatically get the dimensions of the poster image
+    var onImgLoadEvent = function()
+    {
+        // Image is ready.
+        var preview = this.previewImage;
+        preview.removeListener( 'load', onImgLoadEvent );
+        preview.removeListener( 'error', onImgLoadErrorEvent );
+        preview.removeListener( 'abort', onImgLoadErrorEvent );
 
-		this.setValueOf( 'info', 'width', preview.$.width );
-		this.setValueOf( 'info', 'height', preview.$.height );
-	};
+        this.setValueOf( 'info', 'width', preview.$.width );
+        this.setValueOf( 'info', 'height', preview.$.height );
+    };
 
-	var onImgLoadErrorEvent = function()
-	{
-		// Error. Image is not loaded.
-		var preview = this.previewImage;
-		preview.removeListener( 'load', onImgLoadEvent );
-		preview.removeListener( 'error', onImgLoadErrorEvent );
-		preview.removeListener( 'abort', onImgLoadErrorEvent );
-	};
+    var onImgLoadErrorEvent = function()
+    {
+        // Error. Image is not loaded.
+        var preview = this.previewImage;
+        preview.removeListener( 'load', onImgLoadEvent );
+        preview.removeListener( 'error', onImgLoadErrorEvent );
+        preview.removeListener( 'abort', onImgLoadErrorEvent );
+    };
 
-	return {
-		title : lang.dialogTitle,
-		minWidth : 400,
-		minHeight : 200,
+    return {
+        title : lang.dialogTitle,
+        minWidth : 400,
+        minHeight : 200,
 
-		onShow : function()
-		{
-			// Clear previously saved elements.
-			this.fakeImage = this.videoNode = null;
-			// To get dimensions of poster image
-			this.previewImage = editor.document.createElement( 'img' );
+        onShow : function()
+        {
+            // Clear previously saved elements.
+            this.fakeImage = this.videoNode = null;
+            // To get dimensions of poster image
+            this.previewImage = editor.document.createElement( 'img' );
 
-			var fakeImage = this.getSelectedElement();
-			if ( fakeImage && fakeImage.data( 'cke-real-element-type' ) && fakeImage.data( 'cke-real-element-type' ) == 'video' )
-			{
-				this.fakeImage = fakeImage;
+            var fakeImage = this.getSelectedElement();
+            if ( fakeImage && fakeImage.data( 'cke-real-element-type' ) && fakeImage.data( 'cke-real-element-type' ) == 'video' )
+            {
+                this.fakeImage = fakeImage;
 
-				var videoNode = editor.restoreRealElement( fakeImage ),
-					videos = [],
-					sourceList = videoNode.getElementsByTag( 'source', '' );
-				if (sourceList.count()==0)
-					sourceList = videoNode.getElementsByTag( 'source', 'cke' );
+                var videoNode = editor.restoreRealElement( fakeImage ),
+                    videos = [],
+                    sourceList = videoNode.getElementsByTag( 'source', '' );
+                if (sourceList.count()==0)
+                    sourceList = videoNode.getElementsByTag( 'source', 'cke' );
 
-				for ( var i = 0, length = sourceList.count() ; i < length ; i++ )
-				{
-					var item = sourceList.getItem( i );
-					videos.push( {src : item.getAttribute( 'src' ), type: item.getAttribute( 'type' )} );
-				}
+                for ( var i = 0, length = sourceList.count() ; i < length ; i++ )
+                {
+                    var item = sourceList.getItem( i );
+                    videos.push( {src : item.getAttribute( 'src' ), type: item.getAttribute( 'type' )} );
+                }
 
-				this.videoNode = videoNode;
+                this.videoNode = videoNode;
 
-				this.setupContent( videoNode, videos );
-			}
-			else
-				this.setupContent( null, [] );
-		},
+                this.setupContent( videoNode, videos );
+            }
+            else
+                this.setupContent( null, [] );
+        },
 
-		onOk : function()
-		{
-			// If there's no selected element create one. Otherwise, reuse it
-			var videoNode = null;
-			if ( !this.fakeImage )
-			{
-				videoNode = CKEDITOR.dom.element.createFromHtml( '<cke:video></cke:video>', editor.document );
-				videoNode.setAttributes(
-					{
-						controls : 'controls'
-					} );
-			}
-			else
-			{
-				videoNode = this.videoNode;
-			}
+        onOk : function()
+        {
+            // If there's no selected element create one. Otherwise, reuse it
+            var videoNode = null;
+            if ( !this.fakeImage )
+            {
+                videoNode = CKEDITOR.dom.element.createFromHtml( '<cke:video></cke:video>', editor.document );
+                videoNode.setAttributes(
+                    {
+                        controls : 'controls'
+                    } );
+            }
+            else
+            {
+                videoNode = this.videoNode;
+            }
 
-			var extraStyles = {}, videos = [];
-			this.commitContent( videoNode, extraStyles, videos );
+            var extraStyles = {}, videos = [];
+            this.commitContent( videoNode, extraStyles, videos );
 
-			var innerHtml = '', links = '',
-				link = lang.linkTemplate || '',
-				fallbackTemplate = lang.fallbackTemplate || '';
-			for(var i=0; i<videos.length; i++)
-			{
-				var video = videos[i];
-				if ( !video || !video.src ) { continue; }
-				//local copy of video URL
-				var mySrc = video.src;
-				//Chrome is picky about the redirect, so point directly to app/courses/ if currently pointing to courses/
-				//See https://support.chamilo.org/issues/8462
-				var coursesPathIndex = mySrc.indexOf('courses');
-				var newHtmlTag = '<cke:source src="' + mySrc + '" type="' + video.type + '" />';
-				var newLinks = link.replace('%src%', mySrc).replace('%type%', video.type);
-				//is video saved on courses folder ?
-				if (coursesPathIndex >= 0) {
-					//test if real path is not already present (in case of video edition)
-					var myPath = mySrc.indexOf('app');
-					if (myPath==-1) {
-						//add real path (app/) to video.src...
-						var myChromeSrc = mySrc.slice(0, coursesPathIndex) + "app/" + mySrc.slice(coursesPathIndex);
-						//insert full path link...
-						newHtmlTag = '<cke:source src="' + myChromeSrc + '" type="' + video.type + '" />';
-						newLinks = link.replace('%src%', myChromeSrc).replace('%type%', video.type);
-					}
-				}
-				innerHtml += newHtmlTag;
-				links += newLinks;
-			}
-			videoNode.setHtml( innerHtml + fallbackTemplate.replace( '%links%', links ) );
+            var innerHtml = '', links = '',
+                link = lang.linkTemplate || '',
+                fallbackTemplate = lang.fallbackTemplate || '';
+            for(var i=0; i<videos.length; i++)
+            {
+                var video = videos[i];
+                if ( !video || !video.src )
+                    continue;
+                innerHtml += '<cke:source src="' + video.src + '" type="' + video.type + '" />';
+                links += link.replace('%src%', video.src).replace('%type%', video.type);
+            }
+            videoNode.setHtml( innerHtml + fallbackTemplate.replace( '%links%', links ) );
 
-			// Refresh the fake image.
-			var newFakeImage = editor.createFakeElement( videoNode, 'cke_video', 'video', false );
-			newFakeImage.setStyles( extraStyles );
-			if ( this.fakeImage )
-			{
-				newFakeImage.replace( this.fakeImage );
-				editor.getSelection().selectElement( newFakeImage );
-			}
-			else
-			{
-				// Insert it in a div
-				var div = new CKEDITOR.dom.element( 'DIV', editor.document );
-				editor.insertElement( div );
-				div.append( newFakeImage );
-			}
-		},
-		onHide : function()
-		{
-			if ( this.previewImage )
-			{
-				this.previewImage.removeListener( 'load', onImgLoadEvent );
-				this.previewImage.removeListener( 'error', onImgLoadErrorEvent );
-				this.previewImage.removeListener( 'abort', onImgLoadErrorEvent );
-				this.previewImage.remove();
-				this.previewImage = null;		// Dialog is closed.
-			}
-		},
+            // Refresh the fake image.
+            var newFakeImage = editor.createFakeElement( videoNode, 'cke_video', 'video', false );
+            newFakeImage.setStyles( extraStyles );
+            if ( this.fakeImage )
+            {
+                newFakeImage.replace( this.fakeImage );
+                editor.getSelection().selectElement( newFakeImage );
+            }
+            else
+            {
+                // Insert it in a div
+                var div = new CKEDITOR.dom.element( 'DIV', editor.document );
+                editor.insertElement( div );
+                div.append( newFakeImage );
+            }
+        },
+        onHide : function()
+        {
+            if ( this.previewImage )
+            {
+                this.previewImage.removeListener( 'load', onImgLoadEvent );
+                this.previewImage.removeListener( 'error', onImgLoadErrorEvent );
+                this.previewImage.removeListener( 'abort', onImgLoadErrorEvent );
+                this.previewImage.remove();
+                this.previewImage = null;		// Dialog is closed.
+            }
+        },
 
-		contents :
-		[
-			{
-				id : 'info',
-				elements :
-				[
-					{
-						type : 'hbox',
-						widths: [ '33%', '33%', '33%'],
-						children : [
-							{
-								type : 'text',
-								id : 'width',
-								label : editor.lang.common.width,
-								'default' : 400,
-								validate : CKEDITOR.dialog.validate.notEmpty( lang.widthRequired ),
-								commit : commitValue,
-								setup : loadValue
-							},
-							{
-								type : 'text',
-								id : 'height',
-								label : editor.lang.common.height,
-								'default' : 300,
-								//validate : CKEDITOR.dialog.validate.notEmpty(lang.heightRequired ),
-								commit : commitValue,
-								setup : loadValue
-							},
-							{
-								type : 'text',
-								id : 'id',
-								label : 'Id',
-								commit : commitValue,
-								setup : loadValue
-							}
-								]
-					},
-					{
-						type : 'hbox',
-						widths: [ '', '100px', '75px'],
-						children : [
-							{
-								type : 'text',
-								id : 'src0',
-								label : lang.sourceVideo,
-								commit : commitSrc,
-								setup : loadSrc
-							},
-							{
-								type : 'button',
-								id : 'browse',
-								hidden : 'true',
-								style : 'display:inline-block;margin-top:10px;',
-								filebrowser :
-								{
-									action : 'Browse',
-									target: 'info:src0',
-									url: editor.config.filebrowserVideoBrowseUrl || editor.config.filebrowserBrowseUrl
-								},
-								label : editor.lang.common.browseServer
-							},
-							{
-								id : 'type0',
-								label : lang.sourceType,
-								type : 'select',
-								'default' : 'video/mp4',
-								items :
-								[
-									[ 'MP4', 'video/mp4' ],
-									[ 'WebM', 'video/webm' ]
-								],
-								commit : commitSrc,
-								setup : loadSrc
-							}]
-					},
+        contents :
+            [
+                {
+                    id : 'info',
+                    elements :
+                        [
+                            {
+                                type : 'hbox',
+                                widths: [ '33%', '33%', '33%'],
+                                children : [
+                                    {
+                                        type : 'text',
+                                        id : 'width',
+                                        label : editor.lang.common.width,
+                                        'default' : 400,
+                                        validate : CKEDITOR.dialog.validate.notEmpty( lang.widthRequired ),
+                                        commit : commitValue,
+                                        setup : loadValue
+                                    },
+                                    {
+                                        type : 'text',
+                                        id : 'height',
+                                        label : editor.lang.common.height,
+                                        'default' : 300,
+                                        //validate : CKEDITOR.dialog.validate.notEmpty(lang.heightRequired ),
+                                        commit : commitValue,
+                                        setup : loadValue
+                                    },
+                                    {
+                                        type : 'text',
+                                        id : 'id',
+                                        label : 'Id',
+                                        commit : commitValue,
+                                        setup : loadValue
+                                    }
+                                ]
+                            },
+                            {
+                                type : 'hbox',
+                                widths: [ '', '100px', '75px'],
+                                children : [
+                                    {
+                                        type : 'text',
+                                        id : 'src0',
+                                        label : lang.sourceVideo,
+                                        commit : commitSrc,
+                                        setup : loadSrc
+                                    },
+                                    {
+                                        type : 'button',
+                                        id : 'browse',
+                                        hidden : 'true',
+                                        style : 'display:inline-block;margin-top:10px;',
+                                        filebrowser :
+                                            {
+                                                action : 'Browse',
+                                                target: 'info:src0',
+                                                url: editor.config.filebrowserVideoBrowseUrl || editor.config.filebrowserBrowseUrl
+                                            },
+                                        label : editor.lang.common.browseServer
+                                    },
+                                    {
+                                        id : 'type0',
+                                        label : lang.sourceType,
+                                        type : 'select',
+                                        'default' : 'video/mp4',
+                                        items :
+                                            [
+                                                [ 'MP4', 'video/mp4' ],
+                                                [ 'WebM', 'video/webm' ]
+                                            ],
+                                        commit : commitSrc,
+                                        setup : loadSrc
+                                    }]
+                            },
 
-					{
-						type : 'hbox',
-						widths: [ '', '100px', '75px'],
-						children : [
-							{
-								type : 'text',
-								id : 'src1',
-								label : lang.sourceVideo,
-								commit : commitSrc,
-								setup : loadSrc
-							},
-							{
-								type : 'button',
-								id : 'browse',
-								hidden : 'true',
-								style : 'display:inline-block;margin-top:10px;',
-								filebrowser :
-								{
-									action : 'Browse',
-									target: 'info:src1',
-									url: editor.config.filebrowserVideoBrowseUrl || editor.config.filebrowserBrowseUrl
-								},
-								label : editor.lang.common.browseServer
-							},
-							{
-								id : 'type1',
-								label : lang.sourceType,
-								type : 'select',
-								'default':'video/webm',
-								items :
-								[
-									[ 'MP4', 'video/mp4' ],
-									[ 'WebM', 'video/webm' ]
-								],
-								commit : commitSrc,
-								setup : loadSrc
-							}]
-					},
-					{
-						type : 'hbox',
-						widths: [ '', '100px'],
-						children : [
-							{
-								type : 'text',
-								id : 'poster',
-								label : lang.poster,
-								commit : commitValue,
-								setup : loadValue,
-								onChange : function()
-								{
-									var dialog = this.getDialog(),
-										newUrl = this.getValue();
+                            {
+                                type : 'hbox',
+                                widths: [ '', '100px', '75px'],
+                                children : [
+                                    {
+                                        type : 'text',
+                                        id : 'src1',
+                                        label : lang.sourceVideo,
+                                        commit : commitSrc,
+                                        setup : loadSrc
+                                    },
+                                    {
+                                        type : 'button',
+                                        id : 'browse',
+                                        hidden : 'true',
+                                        style : 'display:inline-block;margin-top:10px;',
+                                        filebrowser :
+                                            {
+                                                action : 'Browse',
+                                                target: 'info:src1',
+                                                url: editor.config.filebrowserVideoBrowseUrl || editor.config.filebrowserBrowseUrl
+                                            },
+                                        label : editor.lang.common.browseServer
+                                    },
+                                    {
+                                        id : 'type1',
+                                        label : lang.sourceType,
+                                        type : 'select',
+                                        'default':'video/webm',
+                                        items :
+                                            [
+                                                [ 'MP4', 'video/mp4' ],
+                                                [ 'WebM', 'video/webm' ]
+                                            ],
+                                        commit : commitSrc,
+                                        setup : loadSrc
+                                    }]
+                            },
+                            {
+                                type : 'hbox',
+                                widths: [ '', '100px'],
+                                children : [
+                                    {
+                                        type : 'text',
+                                        id : 'poster',
+                                        label : lang.poster,
+                                        commit : commitValue,
+                                        setup : loadValue,
+                                        onChange : function()
+                                        {
+                                            var dialog = this.getDialog(),
+                                                newUrl = this.getValue();
 
-									//Update preview image
-									if ( newUrl.length > 0 )	//Prevent from load before onShow
-									{
-										dialog = this.getDialog();
-										var preview = dialog.previewImage;
+                                            //Update preview image
+                                            if ( newUrl.length > 0 )	//Prevent from load before onShow
+                                            {
+                                                dialog = this.getDialog();
+                                                var preview = dialog.previewImage;
 
-										preview.on( 'load', onImgLoadEvent, dialog );
-										preview.on( 'error', onImgLoadErrorEvent, dialog );
-										preview.on( 'abort', onImgLoadErrorEvent, dialog );
-										preview.setAttribute( 'src', newUrl );
-									}
-								}
-							},
-							{
-								type : 'button',
-								id : 'browse',
-								hidden : 'true',
-								style : 'display:inline-block;margin-top:10px;',
-								filebrowser :
-								{
-									action : 'Browse',
-									target: 'info:poster',
-									url: editor.config.filebrowserImageBrowseUrl || editor.config.filebrowserBrowseUrl
-								},
-								label : editor.lang.common.browseServer
-							}]
-					}
-				]
-			}
+                                                preview.on( 'load', onImgLoadEvent, dialog );
+                                                preview.on( 'error', onImgLoadErrorEvent, dialog );
+                                                preview.on( 'abort', onImgLoadErrorEvent, dialog );
+                                                preview.setAttribute( 'src', newUrl );
+                                            }
+                                        }
+                                    },
+                                    {
+                                        type : 'button',
+                                        id : 'browse',
+                                        hidden : 'true',
+                                        style : 'display:inline-block;margin-top:10px;',
+                                        filebrowser :
+                                            {
+                                                action : 'Browse',
+                                                target: 'info:poster',
+                                                url: editor.config.filebrowserImageBrowseUrl || editor.config.filebrowserBrowseUrl
+                                            },
+                                        label : editor.lang.common.browseServer
+                                    }]
+                            }
+                        ]
+                }
 
-		]
-	};
-} );
+            ]
+    };
+} );

+ 0 - 7
main/inc/lib/javascript/hotspot/js/hotspot.js

@@ -101,14 +101,12 @@ window.HotspotQuestion = (function () {
 
             if (x >= startX) {
                 width = x - startX;
-
                 this.set('radiusX', Math.round(width / 2));
                 this.set('centerX', startX + this.get('radiusX'));
             }
 
             if (y >= startY) {
                 height = y - startY;
-
                 this.set('radiusY', Math.round(height / 2));
                 this.set('centerY', startY + this.get('radiusY'));
             }
@@ -258,12 +256,9 @@ window.HotspotQuestion = (function () {
 
         var HotspotSVG = function (modelModel, index) {
             var self = this;
-
             this.model = modelModel;
             this.hotspotIndex = index;
-
             this.el = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
-
             this.model.onChange(function (hotspotModel) {
                 self.render();
             });
@@ -368,7 +363,6 @@ window.HotspotQuestion = (function () {
             </div>\n\
         ';
             $el.html(template);
-
             $el.find('select')
                 .on('change', function () {
                     selectedHotspotIndex = self.hotspotIndex;
@@ -747,7 +741,6 @@ window.HotspotQuestion = (function () {
                     hotspotsSVG = new AdminHotspotsSVG(hotspotsCollection, this);
 
                 $(config.selector).css('width', this.width).append(hotspotsSVG.render().el);
-
                 $(config.selector).parent().prepend('\n\
                     <div id="hotspot-messages" class="alert alert-info">\n\
                         <h4><span class="fa fa-info-circle" aria-hidden="true"></span> ' + lang.HotspotStatus1 + '</h4>\n\

+ 2 - 2
main/inc/lib/javascript/svgedit/extensions/imagelib/groups.php

@@ -22,7 +22,7 @@ $group_disk_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document
 $group_web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/document'.$groupdirpath.'/';
 
 //get all group files and folders
-$docs_and_folders = DocumentManager::get_all_document_data(
+$docs_and_folders = DocumentManager::getAllDocumentData(
     $course_info,
     $groupdirpath,
     $groupIid,
@@ -32,7 +32,7 @@ $docs_and_folders = DocumentManager::get_all_document_data(
 );
 
 //get all group filenames
-$array_to_search = is_array($docs_and_folders) ? $docs_and_folders : array();
+$array_to_search = !empty($docs_and_folders) ? $docs_and_folders : [];
 
 if (count($array_to_search) > 0) {
 	while (list($key) = each($array_to_search)) {

+ 2 - 2
main/inc/lib/javascript/svgedit/extensions/imagelib/index.php

@@ -15,7 +15,7 @@ $curdirpath='/images/gallery'; //path of library directory
 $course_info = api_get_course_info();
 
 // Get all files and folders
-$docs_and_folders = DocumentManager::get_all_document_data(
+$docs_and_folders = DocumentManager::getAllDocumentData(
     $course_info,
     $curdirpath,
     0,
@@ -25,7 +25,7 @@ $docs_and_folders = DocumentManager::get_all_document_data(
 );
 
 //get all filenames
-$array_to_search = is_array($docs_and_folders) ? $docs_and_folders : array();
+$array_to_search = !empty($docs_and_folders) ? $docs_and_folders : [];
 
 if (count($array_to_search) > 0) {
 	while (list($key) = each($array_to_search)) {

+ 0 - 2
main/inc/lib/login.lib.php

@@ -215,7 +215,6 @@ class Login
      */
     public static function sendResetEmail(User $user)
     {
-        //if (null === $user->getConfirmationToken()) {
         $uniqueId = api_get_unique_id();
         $user->setConfirmationToken($uniqueId);
         $user->setPasswordRequestedAt(new \DateTime());
@@ -237,7 +236,6 @@ class Login
             $mailBody
         );
         Display::addFlash(Display::return_message(get_lang('CheckYourEmailAndFollowInstructions')));
-        //}
     }
 
     /**

+ 45 - 7
main/inc/lib/message.lib.php

@@ -513,7 +513,7 @@ class MessageManager
      * @param bool   $directMessage
      * @param array  $smsParameters
      * @param bool   $uploadFiles        Do not upload files using the MessageManager class
-     * @param bool   $attachmentList
+     * @param array  $attachmentList
      *
      * @return bool
      */
@@ -2250,8 +2250,7 @@ class MessageManager
             while (!feof($mailFile)) {
                 $mailLine = fgets($mailFile);
                 //if ($iX == 4 && preg_match('/(.*):\s(.*)$/', $mailLine, $matches)) {
-                if (
-                    $iX == 2 &&
+                if ($iX == 2 &&
                     preg_match('/(.*)(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s(.*)/', $mailLine, $detailsMatches)
                 ) {
                     $mail_queue[$i]['reason'] = $detailsMatches[3];
@@ -2279,8 +2278,6 @@ class MessageManager
     /**
      * @param int $userId
      *
-     * @throws \Doctrine\DBAL\DBALException
-     *
      * @return array
      */
     public static function getUsersThatHadConversationWithUser($userId)
@@ -2314,8 +2311,6 @@ class MessageManager
      * @param int $userId
      * @param int $otherUserId
      *
-     * @throws \Doctrine\DBAL\DBALException
-     *
      * @return array
      */
     public static function getAllMessagesBetweenStudents($userId, $otherUserId)
@@ -2345,6 +2340,49 @@ class MessageManager
         return $list;
     }
 
+    /**
+     * @param string $subject
+     * @param string $message
+     * @param array  $courseInfo
+     * @param int    $sessionId
+     *
+     * @return bool
+     */
+    public static function sendMessageToAllUsersInCourse($subject, $message, $courseInfo, $sessionId = 0)
+    {
+        if (empty($courseInfo)) {
+            return false;
+        }
+        $senderId = api_get_user_id();
+        if (empty($senderId)) {
+            return false;
+        }
+        if (empty($sessionId)) {
+            // Course students and teachers
+            $users = CourseManager::get_user_list_from_course_code($courseInfo['code']);
+        } else {
+            // Course-session students and course session coaches
+            $users = CourseManager::get_user_list_from_course_code($courseInfo['code'], $sessionId);
+        }
+
+        if (empty($users)) {
+            return false;
+        }
+
+        foreach ($users as $userInfo) {
+            self::send_message_simple(
+                $userInfo['user_id'],
+                $subject,
+                $message,
+                $senderId,
+                false,
+                false,
+                [],
+                false
+            );
+        }
+    }
+
     /**
      * Execute the SQL necessary to know the number of messages in the database.
      *

+ 4 - 3
main/inc/lib/model.lib.php

@@ -54,14 +54,15 @@ class Model
      */
     public function delete($id)
     {
-        if (empty($id) or $id != strval(intval($id))) {
+        if (empty($id) || $id != strval(intval($id))) {
             return false;
         }
         $params = ['id = ?' => $id];
         if ($this->is_course_model) {
-            $course_id = api_get_course_int_id();
-            $params = ['id = ? AND c_id = ?' => [$id, $course_id]];
+            $courseId = api_get_course_int_id();
+            $params = ['id = ? AND c_id = ?' => [$id, $courseId]];
         }
+
         // Database table definition
         $result = Database::delete($this->table, $params);
         if ($result != 1) {

+ 9 - 17
main/inc/lib/notebook.lib.php

@@ -114,7 +114,7 @@ class NotebookManager
         }
 
         // Database table definition
-        $t_notebook = Database::get_course_table(TABLE_NOTEBOOK);
+        $table = Database::get_course_table(TABLE_NOTEBOOK);
         $course_id = api_get_course_int_id();
 
         $sql = "SELECT
@@ -122,7 +122,7 @@ class NotebookManager
                 title				AS note_title,
                 description 		AS note_comment,
                 session_id			AS session_id
-               FROM $t_notebook
+               FROM $table
                WHERE c_id = $course_id AND notebook_id = '".intval($notebook_id)."' ";
         $result = Database::query($sql);
         if (Database::num_rows($result) != 1) {
@@ -199,11 +199,11 @@ class NotebookManager
         }
 
         // Database table definition
-        $t_notebook = Database::get_course_table(TABLE_NOTEBOOK);
+        $table = Database::get_course_table(TABLE_NOTEBOOK);
 
         $course_id = api_get_course_int_id();
 
-        $sql = "DELETE FROM $t_notebook
+        $sql = "DELETE FROM $table
                 WHERE
                     c_id = $course_id AND
                     notebook_id='".intval($notebook_id)."' AND
@@ -231,6 +231,7 @@ class NotebookManager
      */
     public static function display_notes()
     {
+        $sessionId = api_get_session_id();
         $_user = api_get_user_info();
         if (!isset($_GET['direction'])) {
             $sort_direction = 'ASC';
@@ -246,15 +247,7 @@ class NotebookManager
         // action links
         echo '<div class="actions">';
         if (!api_is_anonymous()) {
-            if (api_get_session_id() == 0) {
-                echo '<a href="index.php?'.api_get_cidreq().'&action=addnote">'.
-                    Display::return_icon(
-                        'new_note.png',
-                        get_lang('NoteAddNew'),
-                        '',
-                        '32'
-                    ).'</a>';
-            } elseif (api_is_allowed_to_session_edit(false, true)) {
+            if ($sessionId == 0 || api_is_allowed_to_session_edit(false, true)) {
                 echo '<a href="index.php?'.api_get_cidreq().'&action=addnote">'.
                     Display::return_icon('new_note.png', get_lang('NoteAddNew'), '', '32').'</a>';
             }
@@ -278,17 +271,16 @@ class NotebookManager
         }
 
         // Database table definition
-        $t_notebook = Database::get_course_table(TABLE_NOTEBOOK);
+        $table = Database::get_course_table(TABLE_NOTEBOOK);
         $order_by = " ORDER BY ".$notebookView." $sort_direction ";
 
         // Condition for the session
-        $session_id = api_get_session_id();
-        $condition_session = api_get_session_condition($session_id);
+        $condition_session = api_get_session_condition($sessionId);
 
         $cond_extra = $notebookView == 'update_date' ? " AND update_date <> ''" : " ";
         $course_id = api_get_course_int_id();
 
-        $sql = "SELECT * FROM $t_notebook
+        $sql = "SELECT * FROM $table
                 WHERE
                     c_id = $course_id AND
                     user_id = '".api_get_user_id()."'

+ 14 - 22
main/inc/lib/pdf.lib.php

@@ -10,6 +10,7 @@ use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
  */
 class PDF
 {
+    /** @var mPDF */
     public $pdf;
     public $custom_header = [];
     public $custom_footer = [];
@@ -32,9 +33,7 @@ class PDF
         $template = null
     ) {
         $this->template = $template;
-        /* More info @ http://mpdf1.com/manual/index.php?tid=184&searchstring=mPDF
-         * mPDF ([ string $mode [, mixed $format [, float $default_font_size [, string $default_font [, float $margin_left , float $margin_right , float $margin_top , float $margin_bottom , float $margin_header , float $margin_footer [, string $orientation ]]]]]])
-         */
+        /* More info @ http://mpdf1.com/manual/index.php?tid=184&searchstring=mPDF */
         if (!in_array($orientation, ['P', 'L'])) {
             $orientation = 'P';
         }
@@ -311,7 +310,8 @@ class PDF
 
                 $document_html = self::fixImagesPaths($document_html, $course_data, $dirName);
 
-                api_set_encoding_html($document_html, 'UTF-8'); // The library mPDF expects UTF-8 encoded input data.
+                // The library mPDF expects UTF-8 encoded input data.
+                api_set_encoding_html($document_html, 'UTF-8');
                 // TODO: Maybe it is better idea the title to be passed through
                 $title = api_get_title_html($document_html, 'UTF-8', 'UTF-8');
                 // $_GET[] too, as it is done with file name.
@@ -395,7 +395,6 @@ class PDF
         //absolute path for frames.css //TODO: necessary?
         $absolute_css_path = api_get_path(WEB_CSS_PATH).api_get_setting('stylesheets').'/frames.css';
         $document_html = str_replace('href="./css/frames.css"', 'href="'.$absolute_css_path.'"', $document_html);
-
         $document_html = str_replace('../../', '', $document_html);
         $document_html = str_replace('../', '', $document_html);
         $document_html = str_replace(
@@ -419,8 +418,16 @@ class PDF
                     if (strpos($old_src, $protocol) === false) {
                         if (strpos($old_src, '/main/default_course_document') === false) {
                             if (strpos($old_src, '/main/inc/lib/') === false) {
-                                $old_src_fixed = str_replace(api_get_path(REL_COURSE_PATH).$course_data['path'].'/document/', '', $old_src);
-                                $old_src_fixed = str_replace('courses/'.$course_data['path'].'/document/', '', $old_src_fixed);
+                                $old_src_fixed = str_replace(
+                                    api_get_path(REL_COURSE_PATH).$course_data['path'].'/document/',
+                                    '',
+                                    $old_src
+                                );
+                                $old_src_fixed = str_replace(
+                                    'courses/'.$course_data['path'].'/document/',
+                                    '',
+                                    $old_src_fixed
+                                );
                                 $new_path = $document_path.$old_src_fixed;
                                 $document_html = str_replace($old_src, $new_path, $document_html);
                             }
@@ -438,22 +445,9 @@ class PDF
         );
         $document_html = str_replace(api_get_path(WEB_ARCHIVE_PATH), api_get_path(SYS_ARCHIVE_PATH), $document_html);
 
-        //replace relative path by absolute path for resources
-        //$document_html= str_replace('src="/chamilo/main/default_course_document/', 'temp_template_path', $document_html);// before save src templates not apply
-        //$document_html= str_replace('src="/', 'temp_template_path', $document_html);// before save src templates not apply
-        //$document_html= str_replace('src="/chamilo/main/default_course_document/', 'temp_template_path', $document_html);// before save src templates not apply
-
-        //$src_http_www= 'src="'.api_get_path(WEB_COURSE_PATH).$course_data['path'].'/document/';
-        //$document_html= str_replace('src="',$src_http_www, $document_html);
-        //$document_html= str_replace('temp_template_path', 'src="/main/default_course_document/', $document_html);// restore src templates
-
         // The library mPDF expects UTF-8 encoded input data.
         api_set_encoding_html($document_html, 'UTF-8');
-        // TODO: Maybe it is better idea the title to be passed through
-        $title = api_get_title_html($document_html, 'UTF-8', 'UTF-8');
-        // $_GET[] too, as it is done with file name.
         // At the moment the title is retrieved from the html document itself.
-
         if ($returnHtml) {
             return "<style>$css</style>".$document_html;
         }
@@ -641,7 +635,6 @@ class PDF
         $this->pdf->defaultheaderline = 1; // 1 to include line below header/above footer
 
         $userId = api_get_user_id();
-
         if (!empty($course_data['code'])) {
             $teacher_list = CourseManager::get_teacher_list_from_course_code($course_data['code']);
 
@@ -726,7 +719,6 @@ class PDF
             // Adding watermark
             if (api_get_setting('pdf_export_watermark_enable') == 'true') {
                 $watermark_file = self::get_watermark($course_code);
-
                 if ($watermark_file) {
                     //http://mpdf1.com/manual/index.php?tid=269&searchstring=watermark
                     $this->pdf->SetWatermarkImage($watermark_file);

+ 30 - 4
main/inc/lib/pear/HTML/QuickForm/Rule/Compare.php

@@ -57,7 +57,7 @@ class HTML_QuickForm_Rule_Compare extends HTML_QuickForm_Rule
     * @param  string     operator name
     * @return string     operator to use for validation
     */
-    function _findOperator($name)
+    public function _findOperator($name)
     {
         $name = trim($name);
         if (empty($name)) {
@@ -79,12 +79,38 @@ class HTML_QuickForm_Rule_Compare extends HTML_QuickForm_Rule
     public function validate($values, $operator = null)
     {
         $operator = $this->_findOperator($operator);
+
+        $a = $values[0];
+        $b = $values[1];
+
         if ('===' != $operator && '!==' != $operator) {
-            $compareFn = create_function('$a, $b', 'return floatval($a) ' . $operator . ' floatval($b);');
+            $a = floatval($a);
+            $b = floatval($b);
         } else {
-            $compareFn = create_function('$a, $b', 'return strval($a) ' . $operator . ' strval($b);');
+            $a = strval($a);
+            $b = strval($b);
+        }
+
+        switch ($operator) {
+            case '===':
+                return $a === $b;
+                break;
+            case '!==':
+                return $a !== $b;
+                break;
+            case '>':
+                return $a > $b;
+                break;
+            case '>=':
+                return $a >= $b;
+                break;
+            case '<':
+                return $a < $b;
+                break;
+            case '<=':
+                return $a <= $b;
+                break;
         }
-        return $compareFn($values[0], $values[1]);
     }
 
     public function getValidationScript($operator = null)

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

@@ -714,7 +714,7 @@ class HTML_QuickForm_advmultiselect extends HTML_QuickForm_select
             $attrHidden = $this->_getAttrString($this->_attributesHidden);
 
             // prepare option tables to be displayed as in POST order
-            $append = count($this->_values);
+            $append = empty($this->_values) ? 0 : count($this->_values);
             if ($append > 0) {
                 $arrHtmlSelected = array_fill(0, $append, ' ');
             } else {

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

@@ -333,7 +333,7 @@ class HTML_QuickForm_file extends HTML_QuickForm_input
                     dataUrl = canvas.toDataURL();
 
                 $image.attr(\'src\', dataUrl).cropper(\'destroy\').off(\'load\', imageCropper);
-                $(\'[name="'.$id.'_crop_image_base_64]"\').val(dataUrl);
+                $(\'[name="'.$id.'_crop_image_base_64"]\').val(dataUrl);
                 $cropButton.hide();
             });
         });

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

@@ -53,7 +53,7 @@ class HTML_QuickForm_select extends HTML_QuickForm_element
      * @since     1.0
      * @access    private
      */
-    protected $_values;
+    protected $_values = [];
 
     /**
      * Class constructor

+ 44 - 25
main/inc/lib/sessionmanager.lib.php

@@ -90,6 +90,23 @@ class SessionManager
             'send_subscription_notification' => $session->getSendSubscriptionNotification(),
         ];
 
+        // Converted to local values
+        $variables = [
+            'display_start_date',
+            'display_end_date',
+            'access_start_date',
+            'access_end_date',
+            'coach_access_start_date',
+            'coach_access_end_date',
+        ];
+
+        foreach ($variables as $value) {
+            $result[$value."_to_local_time"] = null;
+            if (!empty($result[$value])) {
+                $result[$value."_to_local_time"] = api_get_local_time($result[$value]);
+            }
+        }
+
         return $result;
     }
 
@@ -106,7 +123,8 @@ class SessionManager
      * @param string $coachStartDate               (YYYY-MM-DD hh:mm:ss)
      * @param string $coachEndDate                 (YYYY-MM-DD hh:mm:ss)
      * @param int    $sessionCategoryId            ID of the session category in which this session is registered
-     * @param mixed  $coachId                      If integer, this is the session coach id, if string, the coach ID will be looked for from the user table
+     * @param mixed  $coachId                      If int, this is the session coach id,
+     *                                             if string, the coach ID will be looked for from the user table
      * @param int    $visibility                   Visibility after end date (0 = read-only, 1 = invisible, 2 = accessible)
      * @param bool   $fixSessionNameIfExists
      * @param string $duration
@@ -142,7 +160,7 @@ class SessionManager
     ) {
         global $_configuration;
 
-        //Check portal limits
+        // Check portal limits
         $access_url_id = 1;
 
         if (api_get_multiple_access_url()) {
@@ -173,11 +191,15 @@ class SessionManager
             $msg = get_lang('SessionNameIsRequired');
 
             return $msg;
-        } elseif (!empty($startDate) && !api_is_valid_date($startDate, 'Y-m-d H:i') && !api_is_valid_date($startDate, 'Y-m-d H:i:s')) {
+        } elseif (!empty($startDate) && !api_is_valid_date($startDate, 'Y-m-d H:i') &&
+            !api_is_valid_date($startDate, 'Y-m-d H:i:s')
+        ) {
             $msg = get_lang('InvalidStartDate');
 
             return $msg;
-        } elseif (!empty($endDate) && !api_is_valid_date($endDate, 'Y-m-d H:i') && !api_is_valid_date($endDate, 'Y-m-d H:i:s')) {
+        } elseif (!empty($endDate) && !api_is_valid_date($endDate, 'Y-m-d H:i') &&
+            !api_is_valid_date($endDate, 'Y-m-d H:i:s')
+        ) {
             $msg = get_lang('InvalidEndDate');
 
             return $msg;
@@ -269,7 +291,6 @@ class SessionManager
 
                 if (!empty($session_id)) {
                     $extraFields['item_id'] = $session_id;
-
                     $sessionFieldValue = new ExtraFieldValue('session');
                     $sessionFieldValue->saveFieldValues($extraFields);
 
@@ -2774,11 +2795,11 @@ class SessionManager
     /**
      * Update an extra field value for a given session.
      *
-     * @param int    Course ID
-     * @param string    Field variable name
-     * @param string    Field value
+     * @param int    $sessionId Session ID
+     * @param string $variable  Field variable name
+     * @param string $value     Optional. Default field value
      *
-     * @return bool true if field updated, false otherwise
+     * @return bool|int An integer when register a new extra field. And boolean when update the extrafield
      */
     public static function update_session_extra_field_value($sessionId, $variable, $value = '')
     {
@@ -3164,10 +3185,10 @@ class SessionManager
      */
     public static function get_session_category($id)
     {
-        $tbl_session_category = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
+        $table = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
         $id = intval($id);
         $sql = "SELECT id, name, date_start, date_end
-                FROM $tbl_session_category
+                FROM $table
                 WHERE id= $id";
         $result = Database::query($sql);
         $num = Database::num_rows($result);
@@ -3185,9 +3206,9 @@ class SessionManager
      */
     public static function get_all_session_category()
     {
-        $tbl_session_category = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
+        $table = Database::get_main_table(TABLE_MAIN_SESSION_CATEGORY);
         $id = api_get_current_access_url_id();
-        $sql = 'SELECT * FROM '.$tbl_session_category.'
+        $sql = 'SELECT * FROM '.$table.'
                 WHERE access_url_id = '.$id.'
                 ORDER BY name ASC';
         $result = Database::query($sql);
@@ -3685,15 +3706,13 @@ class SessionManager
                     $row['access_end_date'] = null;
                 }
 
-                if (
-                    $row['coach_access_start_date'] == '0000-00-00 00:00:00' ||
+                if ($row['coach_access_start_date'] == '0000-00-00 00:00:00' ||
                     $row['coach_access_start_date'] == '0000-00-00'
                 ) {
                     $row['coach_access_start_date'] = null;
                 }
 
-                if (
-                    $row['coach_access_end_date'] == '0000-00-00 00:00:00' ||
+                if ($row['coach_access_end_date'] == '0000-00-00 00:00:00' ||
                     $row['coach_access_end_date'] == '0000-00-00'
                 ) {
                     $row['coach_access_end_date'] = null;
@@ -4190,8 +4209,8 @@ class SessionManager
     /**
      * Updates a session status.
      *
-     * @param	int 	session id
-     * @param	int 	status
+     * @param int session id
+     * @param int status
      */
     public static function set_session_status($session_id, $status)
     {
@@ -4375,9 +4394,9 @@ class SessionManager
     {
         $session_id = intval($session_id);
         $user_id = intval($user_id);
-        $session_table = Database::get_main_table(TABLE_MAIN_SESSION);
+        $table = Database::get_main_table(TABLE_MAIN_SESSION);
         $sql = "SELECT DISTINCT id
-	         	FROM $session_table
+	         	FROM $table
 	         	WHERE session.id_coach =  '".$user_id."' AND id = '$session_id'";
         $result = Database::query($sql);
         if ($result && Database::num_rows($result)) {
@@ -4566,8 +4585,8 @@ class SessionManager
      *                                                                false: if session exists a new session will be created adding a counter session1, session2, etc
      * @param int    $defaultUserId
      * @param mixed  $logger
-     * @param array  $extraFields                                     convert a file row to an extra field. Example in CSV file there's a SessionID then it will
-     *                                                                converted to extra_external_session_id if you set this: array('SessionId' => 'extra_external_session_id')
+     * @param array  $extraFields                                     convert a file row to an extra field. Example in CSV file there's a SessionID
+     *                                                                then it will converted to extra_external_session_id if you set: array('SessionId' => 'extra_external_session_id')
      * @param string $extraFieldId
      * @param int    $daysCoachAccessBeforeBeginning
      * @param int    $daysCoachAccessAfterBeginning
@@ -7603,7 +7622,7 @@ class SessionManager
             $sessionList = [];
             $sessionList[] = '';
             foreach ($sessions as $session) {
-                $sessionList[$session['id']] = $session['name'];
+                $sessionList[$session['id']] = strip_tags($session['name']);
             }
 
             $form->addSelect(
@@ -7659,7 +7678,7 @@ class SessionManager
             'id' => 'access',
         ]);
 
-        $form->addHtml('<div id="duration" style="display:none">');
+        $form->addHtml('<div id="duration_div" style="display:none">');
 
         $form->addElement(
             'number',

+ 102 - 3
main/inc/lib/skill.lib.php

@@ -2,6 +2,8 @@
 /* For licensing terms, see /license.txt */
 
 use Chamilo\CoreBundle\Component\Utils\ChamiloApi;
+use Chamilo\CoreBundle\Entity\Skill as SkillEntity;
+use Chamilo\SkillBundle\Entity\SkillRelCourse;
 use Chamilo\SkillBundle\Entity\SkillRelItem;
 use Chamilo\UserBundle\Entity\Repository\UserRepository;
 use Chamilo\UserBundle\Entity\User;
@@ -1541,6 +1543,7 @@ class Skill extends Model
                         'id' => '1',
                         'name' => get_lang('Root'),
                         'parent_id' => '0',
+                        'status' => 1,
                     ];
                     $skillInfo = $this->getSkillInfo($skill_id);
 
@@ -2444,7 +2447,7 @@ class Skill extends Model
      * @param int           $itemId
      * @param int           $userId
      */
-    public static function addSkillsToUserForm(FormValidator $form, $typeId, $itemId, $userId)
+    public static function addSkillsToUserForm(FormValidator $form, $typeId, $itemId, $userId, $resultId = 0, $addHeader = false)
     {
         $allowSkillInTools = api_get_configuration_value('allow_skill_rel_items');
         if ($allowSkillInTools && !empty($typeId) && !empty($itemId) && !empty($userId)) {
@@ -2478,8 +2481,12 @@ class Skill extends Model
                     'user_id' => $userId,
                     'course_id' => api_get_course_int_id(),
                     'session_id' => api_get_session_id(),
+                    'result_id' => $resultId,
                 ];
                 $params = json_encode($params);
+                if ($addHeader) {
+                    $form->addHtml(Display::page_subheader2(get_lang('Skills')));
+                }
                 $html = '
                 <script>
                     $(function() {
@@ -2501,6 +2508,9 @@ class Skill extends Model
                 ';
                 $form->addHtml($html);
                 $form->addLabel(get_lang('Skills'), $skills);
+                if ($addHeader) {
+                    $form->addHtml('<br />');
+                }
             }
         }
     }
@@ -2535,12 +2545,16 @@ 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;
         $form->addSelectAjax(
             'skills',
             get_lang('Skills'),
             $skillList,
             [
-                'url' => api_get_path(WEB_AJAX_PATH).'skill.ajax.php?a=search_skills',
+                'url' => $url,
                 'multiple' => 'multiple',
             ]
         );
@@ -2756,7 +2770,7 @@ class Skill extends Model
             // Add new one
             if (!empty($skills)) {
                 foreach ($skills as $skillId) {
-                    /** @var \Chamilo\CoreBundle\Entity\Skill $skill */
+                    /** @var SkillEntity $skill */
                     $skill = $em->getRepository('ChamiloCoreBundle:Skill')->find($skillId);
                     if ($skill) {
                         if (!$skill->hasItem($typeId, $itemId)) {
@@ -2778,4 +2792,89 @@ class Skill extends Model
             }
         }
     }
+
+    /**
+     * Relate skill with an item (exercise, gradebook, lp, etc).
+     *
+     * @param FormValidator $form
+     *
+     * @return bool
+     */
+    public static function saveSkillsToCourseFromForm(FormValidator $form)
+    {
+        $skills = (array) $form->getSubmitValue('skills');
+        $courseId = (int) $form->getSubmitValue('course_id');
+        $sessionId = $form->getSubmitValue('session_id');
+
+        return self::saveSkillsToCourse($skills, $courseId, $sessionId);
+    }
+
+    /**
+     * @param array $skills
+     * @param int   $courseId
+     * @param int   $sessionId
+     *
+     * @throws \Doctrine\ORM\OptimisticLockException
+     *
+     * @return bool
+     */
+    public static function saveSkillsToCourse($skills, $courseId, $sessionId)
+    {
+        $allowSkillInTools = api_get_configuration_value('allow_skill_rel_items');
+        if (!$allowSkillInTools) {
+            return false;
+        }
+
+        $em = Database::getManager();
+        $sessionId = empty($sessionId) ? null : (int) $sessionId;
+
+        $course = api_get_course_entity($courseId);
+        if (empty($course)) {
+            return false;
+        }
+        $session = null;
+        if (!empty($sessionId)) {
+            $session = api_get_session_entity($sessionId);
+            $courseExistsInSession = SessionManager::sessionHasCourse($sessionId, $course->getCode());
+            if (!$courseExistsInSession) {
+                return false;
+            }
+        }
+
+        // Delete old ones
+        $items = $em->getRepository('ChamiloSkillBundle:SkillRelCourse')->findBy(
+            ['course' => $courseId, 'session' => $sessionId]
+        );
+
+        if (!empty($items)) {
+            /** @var SkillRelCourse $item */
+            foreach ($items as $item) {
+                if (!in_array($item->getSkill()->getId(), $skills)) {
+                    $em->remove($item);
+                }
+            }
+            $em->flush();
+        }
+
+        // Add new one
+        if (!empty($skills)) {
+            foreach ($skills as $skillId) {
+                $item = new SkillRelCourse();
+                $item->setCourse($course);
+                $item->setSession($session);
+
+                /** @var SkillEntity $skill */
+                $skill = $em->getRepository('ChamiloCoreBundle:Skill')->find($skillId);
+                if ($skill) {
+                    if (!$skill->hasCourseAndSession($item)) {
+                        $skill->addToCourse($item);
+                        $em->persist($skill);
+                    }
+                }
+            }
+            $em->flush();
+        }
+
+        return true;
+    }
 }

+ 32 - 2
main/inc/lib/social.lib.php

@@ -924,6 +924,7 @@ class SocialManager extends UserManager
         $messagesIcon = Display::return_icon('sn-message.png', get_lang('Messages'), null, ICON_SIZE_SMALL);
         $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'));
 
         $html = '';
         $active = null;
@@ -1002,6 +1003,15 @@ class SocialManager extends UserManager
                 $myFiles = '';
             }
             $links .= $myFiles;
+            if (api_get_configuration_value('allow_portfolio_tool')) {
+                $links .= '
+                    <li class="portoflio-icon '.($show == 'portfolio' ? 'active' : '').'">
+                        <a href="'.api_get_path(WEB_CODE_PATH).'portfolio/index.php">
+                            '.$portfolioIcon.' '.get_lang('Portfolio').'
+                        </a>
+                    </li>
+                ';
+            }
             $links .= '</ul>';
 
             $html .= Display::panelCollapse(
@@ -1081,6 +1091,16 @@ class SocialManager extends UserManager
                     $myFiles = '';
                 }
                 $links .= $myFiles;
+
+                if (api_get_configuration_value('allow_portfolio_tool')) {
+                    $links .= '
+                        <li class="portoflio-icon '.($show == 'portfolio' ? 'active' : '').'">
+                            <a href="'.api_get_path(WEB_CODE_PATH).'portfolio/index.php">
+                                '.$portfolioIcon.' '.get_lang('Portfolio').'
+                            </a>
+                        </li>
+                    ';
+                }
             }
 
             // My friend profile.
@@ -1106,6 +1126,16 @@ class SocialManager extends UserManager
                     ]
                 );
                 $links .= '</li>';
+
+                if (api_get_configuration_value('allow_portfolio_tool')) {
+                    $links .= '
+                        <li class="portoflio-icon '.($show == 'portfolio' ? 'active' : '').'">
+                            <a href="'.api_get_path(WEB_CODE_PATH).'portfolio/index.php?user='.$user_id.'">
+                                '.$portfolioIcon.' '.get_lang('Portfolio').'
+                            </a>
+                        </li>
+                    ';
+                }
             }
 
             // Check if I already sent an invitation message
@@ -1492,7 +1522,7 @@ class SocialManager extends UserManager
         $messageId,
         $fileComment = ''
     ) {
-        $tbl_message_attach = Database::get_main_table(TABLE_MESSAGE_ATTACHMENT);
+        $table = Database::get_main_table(TABLE_MESSAGE_ATTACHMENT);
 
         // create directory
         $social = '/social/';
@@ -1533,7 +1563,7 @@ class SocialManager extends UserManager
                 'message_id' => $messageId,
                 'size' => $fileAttach['size'],
             ];
-            Database::insert($tbl_message_attach, $params);
+            Database::insert($table, $params);
             $flag = true;
         }
 

+ 6 - 2
main/inc/lib/sortable_table.class.php

@@ -235,8 +235,12 @@ class SortableTable extends HTML_Table
             $params['spacesBeforeSeparator'] = '';
             $params['spacesAfterSeparator'] = '';
             $query_vars = array_keys($_GET);
-            $query_vars_needed = [$this->param_prefix.'column', $this->param_prefix.'direction', $this->param_prefix.'per_page'];
-            if (count($this->additional_parameters) > 0) {
+            $query_vars_needed = [
+                $this->param_prefix.'column',
+                $this->param_prefix.'direction',
+                $this->param_prefix.'per_page',
+            ];
+            if (!empty($this->additional_parameters) && count($this->additional_parameters) > 0) {
                 $query_vars_needed = array_merge(
                     $query_vars_needed,
                     array_keys($this->additional_parameters)

+ 5 - 5
main/inc/lib/svg-edit/extensions/imagelib/groups.php

@@ -25,7 +25,7 @@ $group_disk_path = api_get_path(SYS_COURSE_PATH).$course_info['path'].'/document
 $group_web_path = api_get_path(WEB_COURSE_PATH).$course_info['path'].'/document'.$groupdirpath.'/';
 
 //get all group files and folders
-$docs_and_folders = DocumentManager::get_all_document_data(
+$docs_and_folders = DocumentManager::getAllDocumentData(
     $course_info,
     $groupdirpath,
     $groupIid,
@@ -35,12 +35,12 @@ $docs_and_folders = DocumentManager::get_all_document_data(
 );
 
 // get all group filenames
-$array_to_search = is_array($docs_and_folders) ? $docs_and_folders : array();
+$array_to_search = !empty($docs_and_folders) ? $docs_and_folders : array();
 
 if (count($array_to_search) > 0) {
-	while (list($key) = each($array_to_search)) {
-		$all_files[] = basename($array_to_search[$key]['path']);
-	}
+    while (list($key) = each($array_to_search)) {
+        $all_files[] = basename($array_to_search[$key]['path']);
+    }
 }
 
 //get all svg and png group files

+ 5 - 5
main/inc/lib/svg-edit/extensions/imagelib/index.php

@@ -16,7 +16,7 @@ $curdirpath='/images/gallery'; //path of library directory
 $course_info = api_get_course_info();
 
 //get all files and folders
-$docs_and_folders = DocumentManager::get_all_document_data(
+$docs_and_folders = DocumentManager::getAllDocumentData(
     $course_info,
     $curdirpath,
     0,
@@ -26,12 +26,12 @@ $docs_and_folders = DocumentManager::get_all_document_data(
 );
 
 //get all filenames
-$array_to_search = is_array($docs_and_folders) ? $docs_and_folders : array();
+$array_to_search = !empty($docs_and_folders) ? $docs_and_folders : array();
 
 if (count($array_to_search) > 0) {
-	while (list($key) = each($array_to_search)) {
-		$all_files[] = basename($array_to_search[$key]['path']);
-	}
+    while (list($key) = each($array_to_search)) {
+        $all_files[] = basename($array_to_search[$key]['path']);
+    }
 }
 
 //get all svg and png files

+ 77 - 35
main/inc/lib/table_sort.class.php

@@ -46,49 +46,106 @@ class TableSort
         }
 
         if ($type == SORT_REGULAR) {
+            $type = SORT_STRING;
             if (self::is_image_column($data, $column)) {
                 $type = SORT_IMAGE;
             } elseif (self::is_date_column($data, $column)) {
                 $type = SORT_DATE;
             } elseif (self::is_numeric_column($data, $column)) {
                 $type = SORT_NUMERIC;
-            } else {
-                $type = SORT_STRING;
             }
         }
+        $function = self::getSortFunction($type, $direction, $column);
+
+        // Sort the content
+        usort($data, $function);
+
+        return $data;
+    }
+
+    /**
+     * @param $type
+     * @param $direction
+     * @param $column
+     *
+     * @return Closure
+     */
+    public static function getSortFunction($type, $direction, $column)
+    {
+        $compareOperator = $direction == SORT_ASC ? '>' : '<=';
 
-        $compare_operator = $direction == SORT_ASC ? '>' : '<=';
         switch ($type) {
             case SORT_NUMERIC:
-                $compare_function = 'return strip_tags($a['.$column.']) '.$compare_operator.' strip_tags($b['.$column.']);';
+                $function = function ($a, $b) use ($column, $compareOperator) {
+                    $result = strip_tags($a[$column]) <= strip_tags($b[$column]);
+                    if ($compareOperator == '>') {
+                        $result = strip_tags($a[$column]) > strip_tags($b[$column]);
+                    }
+
+                    return $result;
+                };
                 break;
             case SORT_IMAGE:
-                $compare_function = 'return api_strnatcmp(api_strtolower(strip_tags($a['.$column.'], "<img>")), api_strtolower(strip_tags($b['.$column.'], "<img>"))) '.$compare_operator.' 0;';
+                $function = function ($a, $b) use ($column, $compareOperator) {
+                    $result = api_strnatcmp(
+                        api_strtolower(strip_tags($a[$column], "<img>")),
+                        api_strtolower(strip_tags($b[$column], "<img>"))
+                    ) <= 0;
+                    if ($compareOperator == '>') {
+                        $result = api_strnatcmp(
+                            api_strtolower(strip_tags($a[$column], "<img>")),
+                            api_strtolower(strip_tags($b[$column], "<img>"))
+                        ) > 0;
+                    }
+
+                    return $result;
+                };
+
                 break;
             case SORT_DATE:
-                $compare_function = 'return strtotime(strip_tags($a['.$column.'])) '.$compare_operator.' strtotime(strip_tags($b['.$column.']));';
+                $function = function ($a, $b) use ($column, $compareOperator) {
+                    $result = strtotime(strip_tags($a[$column])) <= strtotime(strip_tags($b[$column]));
+                    if ($compareOperator == '>') {
+                        $result = strtotime(strip_tags($a[$column])) > strtotime(strip_tags($b[$column]));
+                    }
+
+                    return $result;
+                };
                 break;
             case SORT_STRING:
             default:
-                $compare_function = 'return api_strnatcmp(api_strtolower(strip_tags($a['.$column.'])), api_strtolower(strip_tags($b['.$column.']))) '.$compare_operator.' 0;';
+                $function = function ($a, $b) use ($column, $compareOperator) {
+                    $result = api_strnatcmp(
+                        api_strtolower(strip_tags($a[$column])),
+                        api_strtolower(strip_tags($b[$column]))
+                    ) <= 0;
+                    if ($compareOperator == '>') {
+                        $result = api_strnatcmp(
+                            api_strtolower(strip_tags($a[$column])),
+                            api_strtolower(strip_tags($b[$column]))
+                        ) > 0;
+                    }
+
+                    return $result;
+                };
                 break;
         }
 
-        // Sort the content
-        usort($data, create_function('$a, $b', $compare_function));
-
-        return $data;
+        return $function;
     }
 
     /**
-     * Sorts 2-dimensional table. It is possile changing the columns that will be shown and the way that the columns are to be sorted.
+     * Sorts 2-dimensional table. It is possible changing the columns that will be
+     * shown and the way that the columns are to be sorted.
      *
-     * @param array    $data         the data to be sorted
-     * @param int      $column       The column on which the data should be sorted (default = 0)
-     * @param string   $direction    The direction to sort (SORT_ASC (default) orSORT_DESC)
-     * @param array    $column_show  The columns that we will show in the table i.e: $column_show = array('1','0','1') we will show the 1st and the 3th column.
-     * @param array    $column_order Changes how the columns will be sorted ie. $column_order = array('0','3','2','3') The column [1] will be sorted like the column [3]
-     * @param constant $type         How should data be sorted (SORT_REGULAR, SORT_NUMERIC, SORT_STRING, SORT_DATE, SORT_IMAGE)
+     * @param array $data         the data to be sorted
+     * @param int   $column       The column on which the data should be sorted (default = 0)
+     * @param int   $direction    The direction to sort (SORT_ASC (default) or SORT_DESC)
+     * @param array $column_show  The columns that we will show in the table
+     *                            i.e: $column_show = array('1','0','1') we will show the 1st and the 3th column.
+     * @param array $column_order Changes how the columns will be sorted
+     *                            ie. $column_order = array('0','3','2','3') The column [1] will be sorted like the column [3]
+     * @param int   $type         How should data be sorted (SORT_REGULAR, SORT_NUMERIC, SORT_STRING, SORT_DATE, SORT_IMAGE)
      *
      * @return array The sorted dataset
      *
@@ -184,25 +241,10 @@ class TableSort
                 $data = $new_data_order;
             }
         } else {
-            $compare_operator = $direction == SORT_ASC ? '>' : '<=';
-            switch ($type) {
-                case SORT_NUMERIC:
-                    $compare_function = 'return strip_tags($a['.$column.']) '.$compare_operator.' strip_tags($b['.$column.']);';
-                    break;
-                case SORT_IMAGE:
-                    $compare_function = 'return api_strnatcmp(api_strtolower(strip_tags($a['.$column.'], "<img>")), api_strtolower(strip_tags($b['.$column.'], "<img>"))) '.$compare_operator.' 0;';
-                    break;
-                case SORT_DATE:
-                    $compare_function = 'return strtotime(strip_tags($a['.$column.'])) '.$compare_operator.' strtotime(strip_tags($b['.$column.']));';
-                    break;
-                case SORT_STRING:
-                default:
-                    $compare_function = 'return api_strnatcmp(api_strtolower(strip_tags($a['.$column.'])), api_strtolower(strip_tags($b['.$column.']))) '.$compare_operator.' 0;';
-                    break;
-            }
+            $function = self::getSortFunction($type, $direction, $column);
 
             // Sort the content
-            usort($data, create_function('$a, $b', $compare_function));
+            usort($data, $function);
         }
 
         if (is_array($column_show) && !empty($column_show)) {

+ 109 - 61
main/inc/lib/text.lib.php

@@ -73,7 +73,12 @@ function api_set_encoding_html(&$string, $encoding)
         }
     } else {
         $count = 1;
-        $string = str_ireplace('</head>', '<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/></head>', $string, $count);
+        $string = str_ireplace(
+            '</head>',
+            '<meta http-equiv="Content-Type" content="text/html; charset='.$encoding.'"/></head>',
+            $string,
+            $count
+        );
     }
     $string = api_convert_encoding($string, $encoding, $old_encoding);
 }
@@ -82,7 +87,8 @@ function api_set_encoding_html(&$string, $encoding)
  * Returns the title of a html document.
  *
  * @param string $string          the contents of the input document
- * @param string $output_encoding The encoding of the retrieved title. If the value is not set, the system encoding is assumend.
+ * @param string $output_encoding The encoding of the retrieved title.
+ *                                If the value is not set, the system encoding is assumed.
  * @param string $input_encoding  The encoding of the input document. If the value is not set, it is detected.
  *
  * @return string the retrieved title, html-entities and extra-whitespace between the words are cleaned
@@ -97,7 +103,17 @@ function api_get_title_html(&$string, $output_encoding = null, $input_encoding =
             $input_encoding = api_detect_encoding_html($string);
         }
 
-        return trim(@preg_replace('/\s+/', ' ', api_html_entity_decode(api_convert_encoding($matches[1], $output_encoding, $input_encoding), ENT_QUOTES, $output_encoding)));
+        return trim(
+            @preg_replace(
+                '/\s+/',
+                ' ',
+                api_html_entity_decode(
+                    api_convert_encoding($matches[1], $output_encoding, $input_encoding),
+                    ENT_QUOTES,
+                    $output_encoding
+                )
+            )
+        );
     }
 
     return '';
@@ -114,7 +130,8 @@ define('_PCRE_XML_ENCODING', '/<\?xml.*encoding=[\'"](.*?)[\'"].*\?>/m');
  * Detects encoding of xml-formatted text.
  *
  * @param string $string           the input xml-formatted text
- * @param string $default_encoding This is the default encoding to be returned if there is no way the xml-text's encoding to be detected. If it not spesified, the system encoding is assumed then.
+ * @param string $default_encoding This is the default encoding to be returned
+ *                                 if there is no way the xml-text's encoding to be detected. If it not spesified, the system encoding is assumed then.
  *
  * @return string returns the detected encoding
  *
@@ -136,11 +153,13 @@ function api_detect_encoding_xml($string, $default_encoding = null)
 }
 
 /**
- * Converts character encoding of a xml-formatted text. If inside the text the encoding is declared, it is modified accordingly.
+ * Converts character encoding of a xml-formatted text.
+ * If inside the text the encoding is declared, it is modified accordingly.
  *
  * @param string $string        the text being converted
  * @param string $to_encoding   the encoding that text is being converted to
- * @param string $from_encoding (optional)  The encoding that text is being converted from. If it is omited, it is tried to be detected then.
+ * @param string $from_encoding (optional)  The encoding that text is being converted from.
+ *                              If it is omited, it is tried to be detected then.
  *
  * @return string returns the converted xml-text
  */
@@ -150,10 +169,12 @@ function api_convert_encoding_xml($string, $to_encoding, $from_encoding = null)
 }
 
 /**
- * Converts character encoding of a xml-formatted text into UTF-8. If inside the text the encoding is declared, it is set to UTF-8.
+ * Converts character encoding of a xml-formatted text into UTF-8.
+ * If inside the text the encoding is declared, it is set to UTF-8.
  *
  * @param string $string        the text being converted
- * @param string $from_encoding (optional)  The encoding that text is being converted from. If it is omited, it is tried to be detected then.
+ * @param string $from_encoding (optional)  The encoding that text is being converted from.
+ *                              If it is omited, it is tried to be detected then.
  *
  * @return string returns the converted xml-text
  */
@@ -163,10 +184,12 @@ function api_utf8_encode_xml($string, $from_encoding = null)
 }
 
 /**
- * Converts character encoding of a xml-formatted text from UTF-8 into a specified encoding. If inside the text the encoding is declared, it is modified accordingly.
+ * Converts character encoding of a xml-formatted text from UTF-8 into a specified encoding.
+ * If inside the text the encoding is declared, it is modified accordingly.
  *
  * @param string $string      the text being converted
- * @param string $to_encoding (optional)    The encoding that text is being converted to. If it is omited, the platform character set is assumed.
+ * @param string $to_encoding (optional)    The encoding that text is being converted to.
+ *                            If it is omitted, the platform character set is assumed.
  *
  * @return string returns the converted xml-text
  */
@@ -176,11 +199,13 @@ function api_utf8_decode_xml($string, $to_encoding = 'UTF-8')
 }
 
 /**
- * Converts character encoding of a xml-formatted text. If inside the text the encoding is declared, it is modified accordingly.
+ * Converts character encoding of a xml-formatted text.
+ * If inside the text the encoding is declared, it is modified accordingly.
  *
  * @param string $string        the text being converted
  * @param string $to_encoding   the encoding that text is being converted to
- * @param string $from_encoding (optional)  The encoding that text is being converted from. If the value is empty, it is tried to be detected then.
+ * @param string $from_encoding (optional)  The encoding that text is being converted from.
+ *                              If the value is empty, it is tried to be detected then.
  *
  * @return string returns the converted xml-text
  */
@@ -191,11 +216,16 @@ function _api_convert_encoding_xml(&$string, $to_encoding, $from_encoding)
     }
     $to_encoding = api_refine_encoding_id($to_encoding);
     if (!preg_match('/<\?xml.*\?>/m', $string, $matches)) {
-        return api_convert_encoding('<?xml version="1.0" encoding="'.$to_encoding.'"?>'."\n".$string, $to_encoding, $from_encoding);
+        return api_convert_encoding(
+            '<?xml version="1.0" encoding="'.$to_encoding.'"?>'."\n".$string,
+            $to_encoding,
+            $from_encoding
+        );
     }
     if (!preg_match(_PCRE_XML_ENCODING, $string)) {
         if (strpos($matches[0], 'standalone') !== false) {
-            // The encoding option should precede the standalone option, othewise DOMDocument fails to load the document.
+            // The encoding option should precede the standalone option,
+            // othewise DOMDocument fails to load the document.
             $replace = str_replace('standalone', ' encoding="'.$to_encoding.'" standalone', $matches[0]);
         } else {
             $replace = str_replace('?>', ' encoding="'.$to_encoding.'"?>', $matches[0]);
@@ -297,7 +327,8 @@ function api_camel_case_to_underscore($string)
  * Works correctly with ASCII strings only, implementation for human-language strings is not necessary.
  *
  * @param string $string                The input string (ASCII)
- * @param bool   $capitalise_first_char (optional)    If true (default), the function capitalises the first char in the result string
+ * @param bool   $capitalise_first_char (optional)
+ *                                      If true (default), the function capitalises the first char in the result string
  *
  * @return string The converted result string
  */
@@ -322,9 +353,11 @@ function _api_camelize($match)
  * @author Brouckaert Olivier
  *
  * @param string $text     the text to truncate
- * @param int    $length   The approximate desired length. The length of the suffix below is to be added to have the total length of the result string.
+ * @param int    $length   The approximate desired length. The length of the suffix below is to be added to
+ *                         have the total length of the result string.
  * @param string $suffix   a suffix to be added as a replacement
- * @param string $encoding (optional)    The encoding to be used. If it is omitted, the platform character set will be used by default.
+ * @param string $encoding (optional)   The encoding to be used. If it is omitted,
+ *                         the platform character set will be used by default.
  * @param bool   $middle   if this parameter is true, truncation is done in the middle of the string
  *
  * @return string truncated string, decorated with the given suffix (replacement)
@@ -339,7 +372,23 @@ function api_trunc_str($text, $length = 30, $suffix = '...', $middle = false, $e
         return $text;
     }
     if ($middle) {
-        return rtrim(api_substr($text, 0, round($length / 2), $encoding)).$suffix.ltrim(api_substr($text, -round($length / 2), $text_length, $encoding));
+        return rtrim(
+            api_substr(
+                $text,
+                0,
+                round($length / 2),
+                $encoding
+            )
+        ).
+        $suffix.
+        ltrim(
+            api_substr(
+                $text,
+                -round($length / 2),
+                $text_length,
+                $encoding
+            )
+        );
     }
 
     return rtrim(api_substr($text, 0, $length, $encoding)).$suffix;
@@ -406,7 +455,8 @@ function _make_url_clickable_cb($matches)
     $url = $matches[2];
 
     if (')' == $matches[3] && strpos($url, '(')) {
-        // If the trailing character is a closing parethesis, and the URL has an opening parenthesis in it, add the closing parenthesis to the URL.
+        // If the trailing character is a closing parethesis, and the URL has an opening
+        // parenthesis in it, add the closing parenthesis to the URL.
         // Then we can let the parenthesis balancer do its thing below.
         $url .= $matches[3];
         $suffix = '';
@@ -442,7 +492,8 @@ function _make_url_clickable_cb($matches)
  *
  * @param string $url       the URL to be cleaned
  * @param array  $protocols Optional. An array of acceptable protocols.
- *                          Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher', 'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
+ *                          Defaults to 'http', 'https', 'ftp', 'ftps', 'mailto', 'news', 'irc', 'gopher',
+ *                          'nntp', 'feed', 'telnet', 'mms', 'rtsp', 'svn' if not set.
  * @param string $_context  Private. Use esc_url_raw() for database usage.
  *
  * @return string the cleaned $url after the 'clean_url' filter is applied
@@ -506,7 +557,8 @@ function esc_url($url, $protocols = null, $_context = 'display')
  *
  * @since wordpress  2.8.1
  *
- * @param string|array $search  The value being searched for, otherwise known as the needle. An array may be used to designate multiple needles.
+ * @param string|array $search  The value being searched for, otherwise known as the needle.
+ *                              An array may be used to designate multiple needles.
  * @param string       $subject the string being searched and replaced on, otherwise known as the haystack
  *
  * @return string the string with the replaced svalues
@@ -586,7 +638,8 @@ function _make_email_clickable_cb($matches)
 function make_clickable($text)
 {
     $r = '';
-    $textarr = preg_split('/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE); // split out HTML tags
+    // split out HTML tags
+    $textarr = preg_split('/(<[^<>]+>)/', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
     $nested_code_pre = 0; // Keep track of how many levels link is nested inside <pre> or <code>
     foreach ($textarr as $piece) {
         if (preg_match('|^<code[\s>]|i', $piece) || preg_match('|^<pre[\s>]|i', $piece)) {
@@ -595,7 +648,10 @@ function make_clickable($text)
             $nested_code_pre--;
         }
 
-        if ($nested_code_pre || empty($piece) || ($piece[0] === '<' && !preg_match('|^<\s*[\w]{1,20}+://|', $piece))) {
+        if ($nested_code_pre ||
+            empty($piece) ||
+            ($piece[0] === '<' && !preg_match('|^<\s*[\w]{1,20}+://|', $piece))
+        ) {
             $r .= $piece;
             continue;
         }
@@ -603,7 +659,8 @@ function make_clickable($text)
         // Long strings might contain expensive edge cases ...
         if (10000 < strlen($piece)) {
             // ... break it up
-            foreach (_split_str_by_whitespace($piece, 2100) as $chunk) { // 2100: Extra room for scheme and leading and trailing paretheses
+            foreach (_split_str_by_whitespace($piece, 2100) as $chunk) {
+                // 2100: Extra room for scheme and leading and trailing paretheses
                 if (2101 < strlen($chunk)) {
                     $r .= $chunk; // Too big, no whitespace: bail.
                 } else {
@@ -629,8 +686,16 @@ function make_clickable($text)
             // Tell PCRE to spend more time optimizing since, when used on a page load, it will probably be used several times.
 
             $ret = preg_replace_callback($url_clickable, '_make_url_clickable_cb', $ret);
-            $ret = preg_replace_callback('#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is', '_make_web_ftp_clickable_cb', $ret);
-            $ret = preg_replace_callback('#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i', '_make_email_clickable_cb', $ret);
+            $ret = preg_replace_callback(
+                '#([\s>])((www|ftp)\.[\w\\x80-\\xff\#$%&~/.\-;:=,?@\[\]+]+)#is',
+                '_make_web_ftp_clickable_cb',
+                $ret
+            );
+            $ret = preg_replace_callback(
+                '#([\s>])([.0-9a-z_+-]+)@(([0-9a-z-]+\.)+[0-9a-z]{2,})#i',
+                '_make_email_clickable_cb',
+                $ret
+            );
 
             $ret = substr($ret, 1, -1); // Remove our whitespace padding.
             $r .= $ret;
@@ -651,7 +716,8 @@ function make_clickable($text)
  *
  * Joining the returned chunks with empty delimiters reconstructs the input string losslessly.
  *
- * Input string must have no null characters (or eventual transformations on output chunks must not care about null characters)
+ * Input string must have no null characters (or eventual transformations on output chunks
+ * must not care about null characters)
  *
  * <code>
  * _split_str_by_whitespace( "1234 67890 1234 67890a cd 1234   890 123456789 1234567890a    45678   1 3 5 7 90 ", 10 ) ==
@@ -728,19 +794,31 @@ function cut($text, $maxchar, $embed = false)
  *
  * @param mixed     Number to convert
  * @param int       Decimal points 0=never, 1=if needed, 2=always
+ * @param string $decimalPoint
+ * @param string $thousandsSeparator
  *
  * @return mixed An integer or a float depends on the parameter
  */
-function float_format($number, $flag = 1)
+function float_format($number, $flag = 1, $decimalPoint = '.', $thousandsSeparator = ',')
 {
     if (is_numeric($number)) {
         if (!$number) {
             $result = ($flag == 2 ? '0.'.str_repeat('0', EXERCISE_NUMBER_OF_DECIMALS) : '0');
         } else {
             if (floor($number) == $number) {
-                $result = number_format($number, ($flag == 2 ? EXERCISE_NUMBER_OF_DECIMALS : 0));
+                $result = number_format(
+                    $number,
+                    ($flag == 2 ? EXERCISE_NUMBER_OF_DECIMALS : 0),
+                    $decimalPoint,
+                    $thousandsSeparator
+                );
             } else {
-                $result = number_format(round($number, 2), ($flag == 0 ? 0 : EXERCISE_NUMBER_OF_DECIMALS));
+                $result = number_format(
+                    round($number, 2),
+                    ($flag == 0 ? 0 : EXERCISE_NUMBER_OF_DECIMALS),
+                    $decimalPoint,
+                    $thousandsSeparator
+                );
             }
         }
 
@@ -795,7 +873,7 @@ function get_week_from_day($date)
 /**
  * This function splits the string into words and then joins them back together again one by one.
  * Example: "Test example of a long string"
- * 			substrwords(5) = Test ... *.
+ * substrwords(5) = Test ... *.
  *
  * @param string
  * @param int the max number of character
@@ -867,36 +945,6 @@ function format_file_size($file_size)
     return $file_size;
 }
 
-function return_datetime_from_array($array)
-{
-    $year = '0000';
-    $month = $day = $hours = $minutes = $seconds = '00';
-    if (isset($array['Y']) && (isset($array['F']) || isset($array['M'])) && isset($array['d']) && isset($array['H']) && isset($array['i'])) {
-        $year = $array['Y'];
-        $month = isset($array['F']) ? $array['F'] : $array['M'];
-        if (intval($month) < 10) {
-            $month = '0'.$month;
-        }
-        $day = $array['d'];
-        if (intval($day) < 10) {
-            $day = '0'.$day;
-        }
-        $hours = $array['H'];
-        if (intval($hours) < 10) {
-            $hours = '0'.$hours;
-        }
-        $minutes = $array['i'];
-        if (intval($minutes) < 10) {
-            $minutes = '0'.$minutes;
-        }
-    }
-    if (checkdate($month, $day, $year)) {
-        $datetime = $year.'-'.$month.'-'.$day.' '.$hours.':'.$minutes.':'.$seconds;
-    }
-
-    return $datetime;
-}
-
 /**
  * Converts an string CLEANYO[admin][amann,acostea]
  * into an array:.

+ 11 - 7
main/inc/lib/thematic.lib.php

@@ -500,6 +500,7 @@ class Thematic
         global $thematic_id;
         $table = Database::get_course_table(TABLE_THEMATIC_ADVANCE);
         $course_id = api_get_course_int_id();
+        $thematic_id = (int) $thematic_id;
 
         $sql = "SELECT COUNT(id) AS total_number_of_items 
                 FROM $table
@@ -534,6 +535,7 @@ class Thematic
         }
         $data = [];
         $course_id = api_get_course_int_id();
+        $thematic_id = (int) $thematic_id;
         if (api_is_allowed_to_edit(null, true)) {
             $sql = "SELECT id AS col0, start_date AS col1, duration AS col2, content AS col3
                     FROM $table
@@ -559,8 +561,10 @@ class Thematic
                     $thematic_advance[1] = api_get_local_time($thematic_advance[1]);
                     $thematic_advance[1] = api_format_date($thematic_advance[1], DATE_TIME_FORMAT_LONG);
                     $actions = '';
-                    $actions .= '<a href="index.php?'.api_get_cidreq().'&action=thematic_advance_edit&thematic_id='.$thematic_id.'&thematic_advance_id='.$thematic_advance[0].'">'.Display::return_icon('edit.png', get_lang('Edit'), '', 22).'</a>';
-                    $actions .= '<a onclick="javascript:if(!confirm(\''.get_lang('AreYouSureToDelete').'\')) return false;" href="index.php?'.api_get_cidreq().'&action=thematic_advance_delete&thematic_id='.$thematic_id.'&thematic_advance_id='.$thematic_advance[0].'">'.Display::return_icon('delete.png', get_lang('Delete'), '', 22).'</a></center>';
+                    $actions .= '<a href="index.php?'.api_get_cidreq().'&action=thematic_advance_edit&thematic_id='.$thematic_id.'&thematic_advance_id='.$thematic_advance[0].'">'.
+                        Display::return_icon('edit.png', get_lang('Edit'), '', 22).'</a>';
+                    $actions .= '<a onclick="javascript:if(!confirm(\''.get_lang('AreYouSureToDelete').'\')) return false;" href="index.php?'.api_get_cidreq().'&action=thematic_advance_delete&thematic_id='.$thematic_id.'&thematic_advance_id='.$thematic_advance[0].'">'.
+                        Display::return_icon('delete.png', get_lang('Delete'), '', 22).'</a></center>';
                     $data[] = [$i, $thematic_advance[1], $thematic_advance[2], $thematic_advance[3], $actions];
                     $i++;
                 }
@@ -585,7 +589,7 @@ class Thematic
 
         // set current course
         $table = Database::get_course_table(TABLE_THEMATIC_ADVANCE);
-        $thematic_id = intval($thematic_id);
+        $thematic_id = (int) $thematic_id;
         $data = [];
         $sql = "SELECT * FROM $table
                 WHERE c_id = $course_id AND thematic_id = $thematic_id ";
@@ -631,8 +635,9 @@ class Thematic
                     }
                 }
                 // DATE_TIME_FORMAT_LONG
-                $thematic_advance_item = '<div><strong>'.api_convert_and_format_date($thematic_advance['start_date'], DATE_TIME_FORMAT_LONG).$session_star.'</strong></div>';
-                //				$thematic_advance_item .= '<div>'.get_lang('DurationInHours').' : '.$thematic_advance['duration'].'</div>';
+                $thematic_advance_item = '<div><strong>'.
+                    api_convert_and_format_date($thematic_advance['start_date'], DATE_TIME_FORMAT_LONG).
+                    $session_star.'</strong></div>';
                 $thematic_advance_item .= '<div>'.$thematic_advance['duration'].' '.get_lang('HourShort').'</div>';
                 $thematic_advance_item .= '<div>'.Security::remove_XSS($thematic_advance['content'], STUDENT).'</div>';
                 $return_array[$thematic_id][$thematic_advance['id']] = $thematic_advance_item;
@@ -838,8 +843,7 @@ class Thematic
     /**
      * delete  thematic advance.
      *
-     * @param int        Thematic advance id
-     * @param int $id
+     * @param int $id Thematic advance id
      *
      * @return int Affected rows
      */

+ 12 - 13
main/inc/lib/tracking.lib.php

@@ -73,12 +73,15 @@ class Tracking
                         $courseInfo['real_id'],
                         $sessionId
                     );
-                    $avg_student_score += self::get_avg_student_score(
+                    $average = self::get_avg_student_score(
                         $user_data['user_id'],
                         $courseInfo['code'],
                         [],
                         $sessionId
                     );
+                    if (is_numeric($average)) {
+                        $avg_student_score += $average;
+                    }
                     $avg_student_progress += self::get_avg_student_progress(
                         $user_data['user_id'],
                         $courseInfo['code'],
@@ -1006,12 +1009,9 @@ class Tracking
                                         $my_exo_exe_id = $row_attempts['exe_exo_id'];
                                         $mktime_start_date = api_strtotime($row_attempts['start_date'], 'UTC');
                                         $mktime_exe_date = api_strtotime($row_attempts['exe_date'], 'UTC');
+                                        $time_attemp = ' - ';
                                         if ($mktime_start_date && $mktime_exe_date) {
-                                            $mytime = ((int) $mktime_exe_date - (int) $mktime_start_date);
-                                            $time_attemp = learnpathItem::getScormTimeFromParameter('js', $mytime);
-                                            $time_attemp = str_replace('NaN', '00'.$h.'00\'00"', $time_attemp);
-                                        } else {
-                                            $time_attemp = ' - ';
+                                            $time_attemp = api_format_time($row_attempts['exe_duration'], 'js');
                                         }
                                         if (!$is_allowed_to_edit && $result_disabled_ext_all) {
                                             $view_score = Display::return_icon(
@@ -2684,7 +2684,6 @@ class Tracking
 
             // Check the real number of LPs corresponding to the filter in the
             // database (and if no list was given, get them all)
-
             if (empty($session_id)) {
                 $sql = "SELECT DISTINCT(id), use_max_score
                         FROM $lp_table
@@ -3007,7 +3006,6 @@ class Tracking
                 if ($debug) {
                     echo '<h3>Final return</h3>';
                 }
-
                 if ($lp_with_quiz != 0) {
                     if (!$return_array) {
                         $score_of_scorm_calculate = round(($global_result / $lp_with_quiz), 2);
@@ -6558,15 +6556,16 @@ class Tracking
     /**
      * Get the HTML code for show a block with the achieved user skill on course/session.
      *
-     * @param int $userId
-     * @param int $courseId  optional
-     * @param int $sessionId optional
+     * @param int  $userId
+     * @param int  $courseId
+     * @param int  $sessionId
+     * @param bool $forceView forces the view of the skills, not checking for deeper access
      *
      * @return string
      */
-    public static function displayUserSkills($userId, $courseId = 0, $sessionId = 0)
+    public static function displayUserSkills($userId, $courseId = 0, $sessionId = 0, $forceView = false)
     {
-        if (Skill::isAllowed($userId, false) === false) {
+        if (Skill::isAllowed($userId, false) === false && $forceView == false) {
             return '';
         }
         $skillManager = new Skill();

+ 19 - 11
main/inc/lib/usergroup.lib.php

@@ -208,8 +208,8 @@ class UserGroup extends Model
     /**
      * Gets a list of course ids by user group.
      *
-     * @param int   $id             user group id
-     * @param array $loadCourseData
+     * @param int  $id             user group id
+     * @param bool $loadCourseData
      *
      * @return array
      */
@@ -483,16 +483,17 @@ class UserGroup extends Model
     /**
      * Gets a list of user ids by user group.
      *
-     * @param int $id user group id
+     * @param int   $id    user group id
+     * @param array $roles
      *
      * @return array with a list of user ids
      */
-    public function get_users_by_usergroup($id = null, $relationList = [])
+    public function get_users_by_usergroup($id = null, $roles = [])
     {
         $relationCondition = '';
-        if (!empty($relationList)) {
+        if (!empty($roles)) {
             $relationConditionArray = [];
-            foreach ($relationList as $relation) {
+            foreach ($roles as $relation) {
                 $relation = (int) $relation;
                 if (empty($relation)) {
                     $relationConditionArray[] = " (relation_type = 0 OR relation_type IS NULL OR relation_type = '') ";
@@ -501,7 +502,7 @@ class UserGroup extends Model
                 }
             }
             $relationCondition = " AND ( ";
-            $relationCondition .= implode("AND", $relationConditionArray);
+            $relationCondition .= implode('OR', $relationConditionArray);
             $relationCondition .= " ) ";
         }
 
@@ -968,7 +969,7 @@ class UserGroup extends Model
             foreach ($result as $group) {
                 $group['sessions'] = count($this->get_sessions_by_usergroup($group['id']));
                 $group['courses'] = count($this->get_courses_by_usergroup($group['id']));
-
+                $roles = [];
                 switch ($group['group_type']) {
                     case 0:
                         $group['group_type'] = Display::label(get_lang('Class'), 'info');
@@ -2027,8 +2028,15 @@ class UserGroup extends Model
         } else {
             $num = intval($num);
         }
-        $where_relation_condition = " WHERE g.group_type = ".self::SOCIAL_CLASS." AND
-                                      gu.relation_type IN ('".GROUP_USER_PERMISSION_ADMIN."' , '".GROUP_USER_PERMISSION_READER."', '".GROUP_USER_PERMISSION_HRM."') ";
+
+        $where = " WHERE 
+                        g.group_type = ".self::SOCIAL_CLASS." AND
+                        gu.relation_type IN 
+                        ('".GROUP_USER_PERMISSION_ADMIN."' , 
+                        '".GROUP_USER_PERMISSION_READER."',
+                        '".GROUP_USER_PERMISSION_MODERATOR."',  
+                        '".GROUP_USER_PERMISSION_HRM."') 
+                    ";
         $sql = "SELECT DISTINCT
                   count(user_id) as count,
                   g.picture,
@@ -2038,7 +2046,7 @@ class UserGroup extends Model
                 FROM $tbl_group g
                 INNER JOIN $table_group_rel_user gu
                 ON gu.usergroup_id = g.id
-                $where_relation_condition
+                $where
                 GROUP BY g.id
                 ORDER BY created_at DESC
                 LIMIT $num ";

+ 66 - 13
main/inc/lib/usermanager.lib.php

@@ -305,6 +305,7 @@ class UserManager
 
         $currentDate = api_get_utc_datetime();
         $now = new DateTime();
+
         if (empty($expirationDate) || $expirationDate == '0000-00-00 00:00:00') {
             // Default expiration date
             // if there is a default duration of a valid account then
@@ -326,7 +327,7 @@ class UserManager
 
         /** @var User $user */
         $user = $userManager->createUser();
-        error_log('langua5');
+
         $user
             ->setLastname($lastName)
             ->setFirstname($firstName)
@@ -476,18 +477,70 @@ class UserManager
                         'password' => $original_password,
                     ];
 
-                    api_mail_html(
-                        $recipient_name,
-                        $email,
-                        $emailSubject,
-                        $emailBody,
-                        $sender_name,
-                        $email_admin,
-                        null,
-                        null,
-                        null,
-                        $additionalParameters
-                    );
+                    $twoEmail = api_get_configuration_value('send_two_inscription_confirmation_mail');
+                    if ($twoEmail === true) {
+                        $layoutContent = $tplContent->get_template('mail/new_user_first_email_confirmation.tpl');
+                        $emailBody = $tplContent->fetch($layoutContent);
+
+                        api_mail_html(
+                            $recipient_name,
+                            $email,
+                            $emailSubject,
+                            $emailBody,
+                            $sender_name,
+                            $email_admin,
+                            null,
+                            null,
+                            null,
+                            $additionalParameters
+                        );
+
+                        $layoutContent = $tplContent->get_template('mail/new_user_second_email_confirmation.tpl');
+                        $emailBody = $tplContent->fetch($layoutContent);
+
+                        api_mail_html(
+                            $recipient_name,
+                            $email,
+                            $emailSubject,
+                            $emailBody,
+                            $sender_name,
+                            $email_admin,
+                            null,
+                            null,
+                            null,
+                            $additionalParameters
+                        );
+                    } else {
+                        $sendToInbox = api_get_configuration_value('send_inscription_msg_to_inbox');
+                        if ($sendToInbox) {
+                            $adminList = UserManager::get_all_administrators();
+                            $senderId = 1;
+                            if (!empty($adminList)) {
+                                $adminInfo = current($adminList);
+                                $senderId = $adminInfo['user_id'];
+                            }
+
+                            MessageManager::send_message_simple(
+                                $userId,
+                                $emailSubject,
+                                $emailBody,
+                                $senderId
+                            );
+                        } else {
+                            api_mail_html(
+                                $recipient_name,
+                                $email,
+                                $emailSubject,
+                                $emailBody,
+                                $sender_name,
+                                $email_admin,
+                                null,
+                                null,
+                                null,
+                                $additionalParameters
+                            );
+                        }
+                    }
 
                     $notification = api_get_configuration_value('send_notification_when_user_added');
                     if (!empty($notification) && isset($notification['admins']) && is_array($notification['admins'])) {

+ 65 - 73
main/inc/lib/webservices/Rest.php

@@ -311,7 +311,7 @@ class Rest extends WebService
         }
 
         $courseInfo = api_get_course_info_by_id($this->course->getId());
-        $documents = DocumentManager::get_all_document_data(
+        $documents = DocumentManager::getAllDocumentData(
             $courseInfo,
             $path,
             0,
@@ -322,7 +322,7 @@ class Rest extends WebService
         );
         $results = [];
 
-        if (is_array($documents)) {
+        if (!empty($documents)) {
             $webPath = api_get_path(WEB_CODE_PATH).'document/document.php?';
 
             /** @var array $document */
@@ -1031,135 +1031,127 @@ class Rest extends WebService
     }
 
     /**
-     * @param array $course_param
+     * @param array $courseParam
      *
      * @return array
      */
-    public function addCourse($course_param)
+    public function addCourse(array $courseParam)
     {
-        $table_course = Database::get_main_table(TABLE_MAIN_COURSE);
-        $extra_list = [];
-
-        $title = isset($course_param['title']) ? $course_param['title'] : '';
-        $category_code = isset($course_param['category_code']) ? $course_param['category_code'] : '';
-        $wanted_code = isset($course_param['wanted_code']) ? intval($course_param['wanted_code']) : 0;
-        $tutor_name = isset($course_param['tutor_name']) ? $course_param['tutor_name'] : '';
-        $admin_id = isset($course_param['admin_id']) ? $course_param['admin_id'] : null;
-        $language = isset($course_param['language']) ? $course_param['language'] : null;
-        $original_course_id = isset($course_param['original_course_id']) ? $course_param['original_course_id'] : null;
-        $diskQuota = isset($course_param['disk_quota']) ? $course_param['disk_quota'] : '100';
-        $visibility = isset($course_param['visibility']) ? (int) $course_param['visibility'] : null;
-
-        if (isset($course_param['visibility'])) {
-            if ($course_param['visibility'] &&
-                $course_param['visibility'] >= 0 &&
-                $course_param['visibility'] <= 3
+        $tableCourse = Database::get_main_table(TABLE_MAIN_COURSE);
+        $extraList = [];
+        $results = [];
+
+        $title = isset($courseParam['title']) ? $courseParam['title'] : '';
+        $categoryCode = isset($courseParam['category_code']) ? $courseParam['category_code'] : '';
+        $wantedCode = isset($courseParam['wanted_code']) ? intval($courseParam['wanted_code']) : 0;
+        $tutorName = isset($courseParam['tutor_name']) ? $courseParam['tutor_name'] : '';
+        $courseLanguage = isset($courseParam['language']) ? $courseParam['language'] : null;
+        $originalCourseIdName = isset($courseParam['original_course_id_name'])
+            ? $courseParam['original_course_id_name']
+            : null;
+        $originalCourseIdValue = isset($courseParam['original_course_id_value'])
+            ? $courseParam['original_course_id_value']
+            : null;
+        $diskQuota = isset($courseParam['disk_quota']) ? $courseParam['disk_quota'] : '100';
+        $visibility = isset($courseParam['visibility']) ? (int) $courseParam['visibility'] : null;
+
+        if (isset($courseParam['visibility'])) {
+            if ($courseParam['visibility'] &&
+                $courseParam['visibility'] >= 0 &&
+                $courseParam['visibility'] <= 3
             ) {
-                $visibility = (int) $course_param['visibility'];
+                $visibility = (int) $courseParam['visibility'];
             }
         }
 
         // Check whether exits $x_course_code into user_field_values table.
         $courseInfo = CourseManager::getCourseInfoFromOriginalId(
-            'id',
-            $course_param['original_course_id_name']
+            $originalCourseIdValue,
+            $originalCourseIdName
         );
 
         if (!empty($courseInfo)) {
             if ($courseInfo['visibility'] != 0) {
-                $sql = "UPDATE $table_course SET
-                            course_language = '".Database::escape_string($course_language)."',
+                $sql = "UPDATE $tableCourse SET
+                            course_language = '".Database::escape_string($courseLanguage)."',
                             title = '".Database::escape_string($title)."',
-                            category_code = '".Database::escape_string($category_code)."',
-                            tutor_name = '".Database::escape_string($tutor_name)."',
-                            visual_code = '".Database::escape_string($wanted_code)."'";
+                            category_code = '".Database::escape_string($categoryCode)."',
+                            tutor_name = '".Database::escape_string($tutorName)."',
+                            visual_code = '".Database::escape_string($wantedCode)."'";
                 if ($visibility !== null) {
                     $sql .= ", visibility = $visibility ";
                 }
                 $sql .= " WHERE id = ".$courseInfo['real_id'];
                 Database::query($sql);
-                if (is_array($extra_list) && count($extra_list) > 0) {
-                    foreach ($extra_list as $extra) {
-                        $extra_field_name = $extra['field_name'];
-                        $extra_field_value = $extra['field_value'];
+                if (is_array($extraList) && count($extraList) > 0) {
+                    foreach ($extraList as $extra) {
+                        $extraFieldName = $extra['field_name'];
+                        $extraFieldValue = $extra['field_value'];
                         // Save the external system's id into course_field_value table.
                         CourseManager::update_course_extra_field_value(
                             $courseInfo['code'],
-                            $extra_field_name,
-                            $extra_field_value
+                            $extraFieldName,
+                            $extraFieldValue
                         );
                     }
                 }
                 $results[] = $courseInfo['code'];
-            } else {
-                $results[] = 0;
             }
         }
 
-        if (!empty($course_param['course_language'])) {
-            $course_language = $course_param['course_language'];
-        }
-
         $params = [];
         $params['title'] = $title;
-        $params['wanted_code'] = $wanted_code;
-        $params['category_code'] = $category_code;
-        $params['course_category'] = $category_code;
-        $params['tutor_name'] = $tutor_name;
-        $params['course_language'] = $course_language;
+        $params['wanted_code'] = $wantedCode;
+        $params['category_code'] = $categoryCode;
+        $params['course_category'] = $categoryCode;
+        $params['tutor_name'] = $tutorName;
+        $params['course_language'] = $courseLanguage;
         $params['user_id'] = $this->user->getId();
         $params['visibility'] = $visibility;
         $params['disk_quota'] = $diskQuota;
+        $params['subscribe'] = empty($courseParam['subscribe']) ? 0 : 1;
+        $params['unsubscribe'] = empty($courseParam['unsubscribe']) ? 0 : 1;
 
-        if (isset($subscribe) && $subscribe != '') { // Valid values: 0, 1
-            $params['subscribe'] = $subscribe;
-        }
-        if (isset($unsubscribe) && $subscribe != '') { // Valid values: 0, 1
-            $params['unsubscribe'] = $unsubscribe;
-        }
-
-        $course_info = CourseManager::create_course($params, $params['user_id']);
+        $courseInfo = CourseManager::create_course($params, $params['user_id']);
 
-        if (!empty($course_info)) {
-            $course_code = $course_info['code'];
+        if (!empty($courseInfo)) {
+            $courseCode = $courseInfo['code'];
 
             // Save new field label into course_field table
             CourseManager::create_course_extra_field(
-                $original_course_id_name,
+                $originalCourseIdName,
                 1,
-                $original_course_id_name,
+                $originalCourseIdName,
                 ''
             );
 
             // Save the external system's id into user_field_value table.
             CourseManager::update_course_extra_field_value(
-                $course_code,
-                $original_course_id_name,
-                $original_course_id_value
+                $courseCode,
+                $originalCourseIdName,
+                $originalCourseIdValue
             );
 
-            if (is_array($extra_list) && count($extra_list) > 0) {
-                foreach ($extra_list as $extra) {
-                    $extra_field_name = $extra['field_name'];
-                    $extra_field_value = $extra['field_value'];
+            if (is_array($extraList) && count($extraList) > 0) {
+                foreach ($extraList as $extra) {
+                    $extraFieldName = $extra['field_name'];
+                    $extraFieldValue = $extra['field_value'];
                     // Save new fieldlabel into course_field table.
                     CourseManager::create_course_extra_field(
-                        $extra_field_name,
+                        $extraFieldName,
                         1,
-                        $extra_field_name,
+                        $extraFieldName,
                         ''
                     );
                     // Save the external system's id into course_field_value table.
                     CourseManager::update_course_extra_field_value(
-                        $course_code,
-                        $extra_field_name,
-                        $extra_field_value
+                        $courseCode,
+                        $extraFieldName,
+                        $extraFieldValue
                     );
                 }
             }
-            $results[] = $course_code;
-        } else {
-            $results[] = 0;
+            $results[] = $courseCode;
         }
 
         return $results;

File diff suppressed because it is too large
+ 118 - 166
main/lp/learnpath.class.php


+ 0 - 23
main/messages/outbox.php

@@ -49,29 +49,6 @@ if ($allowMessage) {
         Display::return_icon('outbox.png', get_lang('Outbox')).'</a>';
 }
 
-$info_delete_outbox = [];
-$info_delete_outbox = isset($_GET['form_delete_outbox']) ? explode(',', $_GET['form_delete_outbox']) : '';
-$count_delete_outbox = count($info_delete_outbox) - 1;
-
-if (isset($info_delete_outbox[0]) && trim($info_delete_outbox[0]) == 'delete') {
-    for ($i = 1; $i <= $count_delete_outbox; $i++) {
-        MessageManager::delete_message_by_user_sender(api_get_user_id(), $info_delete_outbox[$i]);
-    }
-    $message_box = get_lang('SelectedMessagesDeleted').
-        '&nbsp
-        <br><a href="../social/index.php?#remote-tab-3">'.
-        get_lang('BackToOutbox').
-        '</a>';
-    Display::addFlash(
-        Display::return_message(
-            api_xml_http_response_encode($message_box),
-            'normal',
-            false
-        )
-    );
-    exit;
-}
-
 $action = null;
 if (isset($_REQUEST['action'])) {
     $action = $_REQUEST['action'];

+ 17 - 7
main/mySpace/myStudents.php

@@ -1604,13 +1604,23 @@ if (empty($details)) {
 <?php
 } //end details
 
-echo Tracking::displayUserSkills(
-    $user_info['user_id'],
-    $courseInfo ? $courseInfo['real_id'] : 0,
-    $sessionId,
-    api_get_configuration_value('allow_teacher_access_student_skills')
-);
-
+$allowAll = api_get_configuration_value('allow_teacher_access_student_skills');
+if ($allowAll) {
+    // Show all skills
+    echo Tracking::displayUserSkills(
+        $user_info['user_id'],
+        0,
+        0,
+        true
+    );
+} else {
+    // Default behaviour - Show all skills depending the course and session id
+    echo Tracking::displayUserSkills(
+        $user_info['user_id'],
+        $courseInfo ? $courseInfo['real_id'] : 0,
+        $sessionId
+    );
+}
 if ($allowMessages === true) {
     // Messages
     echo Display::page_subheader2(get_lang('Messages'));

+ 2 - 2
main/mySpace/works_in_session_report.php

@@ -82,7 +82,7 @@ if ($session) {
             $usersInfo[$user->getId()][$course->getId().'_score'] = Tracking::get_avg_student_score(
                 $user->getId(),
                 $course->getCode(),
-                null,
+                [],
                 $session->getId(),
                 false,
                 false,
@@ -91,7 +91,7 @@ if ($session) {
             $usersInfo[$user->getId()][$course->getId().'_progress'] = Tracking::get_avg_student_progress(
                 $user->getId(),
                 $course->getCode(),
-                null,
+                [],
                 $session->getId()
             );
 

+ 2 - 2
main/upload/form.scorm.php

@@ -1,6 +1,6 @@
 <?php
-
 /* For licensing terms, see /license.txt */
+
 /**
  * Display part of the SCORM sub-process for upload. This script MUST BE included by upload/index.php
  * as it prepares most of the variables needed here.
@@ -39,7 +39,7 @@ function get_zip_files_in_garbage()
 /**
  * Just display the form needed to upload a SCORM and give its settings.
  */
-$nameTools = get_lang("FileUpload");
+$nameTools = get_lang('FileUpload');
 $interbreadcrumb[] = [
     "url" => api_get_path(WEB_CODE_PATH)."lp/lp_controller.php?action=list?".api_get_cidreq(),
     "name" => get_lang("ToolLearnpath"),

Some files were not shown because too many files changed in this diff