Browse Source

Merge remote-tracking branch 'oficial/1.11.x' into 1.11.x_facturas

Nosolored 6 years ago
parent
commit
bb2499e3ac
58 changed files with 785 additions and 363 deletions
  1. 2 0
      app/Resources/public/css/base.css
  2. 6 3
      main/admin/user_edit.php
  3. 4 0
      main/auth/profile.php
  4. 1 1
      main/document/document.php
  5. 1 1
      main/document/edit_odf.php
  6. 1 1
      main/document/show_content.php
  7. 5 7
      main/document/showinframes.php
  8. 10 10
      main/dropbox/index.php
  9. 20 20
      main/forum/forumfunction.inc.php
  10. 14 13
      main/forum/index.php
  11. 0 3
      main/gradebook/gradebook_add_link.php
  12. 17 13
      main/gradebook/lib/fe/displaygradebook.php
  13. 2 1
      main/group/group_category.php
  14. 24 25
      main/inc/ajax/course_home.ajax.php
  15. 8 3
      main/inc/ajax/model.ajax.php
  16. 7 3
      main/inc/ajax/social.ajax.php
  17. 2 2
      main/inc/lib/TicketManager.php
  18. 36 29
      main/inc/lib/attendance.lib.php
  19. 3 4
      main/inc/lib/course_home.lib.php
  20. 1 0
      main/inc/lib/database.constants.inc.php
  21. 52 0
      main/inc/lib/display.lib.php
  22. 41 24
      main/inc/lib/document.lib.php
  23. 1 1
      main/inc/lib/online.inc.php
  24. 38 22
      main/inc/lib/social.lib.php
  25. 3 3
      main/inc/lib/template.lib.php
  26. 19 7
      main/inc/lib/tracking.lib.php
  27. 15 9
      main/inc/lib/usergroup.lib.php
  28. 36 5
      main/inc/lib/usermanager.lib.php
  29. 9 2
      main/lp/learnpath.class.php
  30. 12 12
      main/lp/lp_list.php
  31. 2 0
      main/lp/lp_view.php
  32. 3 3
      main/messages/new_message.php
  33. 6 11
      main/mySpace/session.php
  34. 5 5
      main/social/personal_data.php
  35. 0 33
      main/template/default/layout/main.js.tpl
  36. 4 5
      main/template/default/layout/page.tpl
  37. 4 4
      main/template/default/layout/show_header.tpl
  38. 7 6
      main/template/default/learnpath/view.tpl
  39. 9 3
      main/ticket/ticket_details.php
  40. 17 7
      main/wiki/wiki.inc.php
  41. 0 9
      main/work/upload.php
  42. 2 1
      plugin/bbb/lang/english.php
  43. 2 1
      plugin/bbb/lang/french.php
  44. 1 1
      plugin/bbb/lang/german.php
  45. 1 1
      plugin/bbb/lang/spanish.php
  46. 3 1
      plugin/bbb/lib/bbb.lib.php
  47. 0 1
      plugin/bbb/start.php
  48. 1 1
      plugin/buycourses/lang/spanish.php
  49. 3 0
      plugin/keycloak/KeycloakPlugin.php
  50. 52 0
      plugin/keycloak/README.md
  51. 1 1
      plugin/keycloak/lang/english.php
  52. 27 12
      plugin/keycloak/start.php
  53. 3 0
      src/Chamilo/CourseBundle/Component/CourseCopy/Course.php
  54. 61 33
      src/Chamilo/CourseBundle/Component/CourseCopy/CourseRecycler.php
  55. 1 0
      src/Chamilo/CourseBundle/Component/CourseCopy/CourseSelectForm.php
  56. 61 0
      tests/scripts/delete_items_from_csv.php
  57. 34 0
      tests/scripts/edit_course_html_files.php
  58. 85 0
      tests/scripts/restore_deleted_documents.php

+ 2 - 0
app/Resources/public/css/base.css

@@ -7683,6 +7683,8 @@ footer {
     padding: 3px;
     border-radius: 4px;
     border: 1px solid #e6e6e6;
+    max-height: 40px;
+    max-width: 40px;
 }
 
 .everybodyview {

+ 6 - 3
main/admin/user_edit.php

@@ -463,9 +463,12 @@ if ($form->validate()) {
             $template
         );
 
-        if (isset($user['student_boss'])) {
-            UserManager::subscribeUserToBossList($user_id, $user['student_boss']);
-        }
+        $studentBossListSent = isset($user['student_boss']) ? $user['student_boss'] : [];
+        UserManager::subscribeUserToBossList(
+            $user_id,
+            $studentBossListSent,
+            true
+        );
 
         if (api_get_setting('openid_authentication') == 'true' && !empty($user['openid'])) {
             $up = UserManager::update_openid($user_id, $user['openid']);

+ 4 - 0
main/auth/profile.php

@@ -661,6 +661,10 @@ if ($form->validate()) {
     Session::write('_user', $userInfo);
 
     if ($hook) {
+        Database::getManager()->clear(User::class); //Avoid cache issue (user entity is used before)
+
+        $user = api_get_user_entity(api_get_user_id()); //Get updated user info for hook event
+
         $hook->setEventData(['user' => $user]);
         $hook->notifyUpdateUser(HOOK_EVENT_TYPE_POST);
     }

+ 1 - 1
main/document/document.php

@@ -612,7 +612,7 @@ if (isset($document_id) && empty($action)) {
 
         $visibility = DocumentManager::check_visibility_tree(
             $document_id,
-            api_get_course_id(),
+            $courseInfo,
             $sessionId,
             api_get_user_id(),
             $groupIid

+ 1 - 1
main/document/edit_odf.php

@@ -27,7 +27,7 @@ if (empty($documentInfo)) {
 //Check user visibility
 $is_visible = DocumentManager::check_visibility_tree(
     $documentId,
-    api_get_course_id(),
+    api_get_course_info(),
     api_get_session_id(),
     api_get_user_id(),
     api_get_group_id()

+ 1 - 1
main/document/show_content.php

@@ -76,7 +76,7 @@ if ($is_allowed_in_course == false) {
 // Check user visibility
 $is_visible = DocumentManager::check_visibility_tree(
     $document_id,
-    api_get_course_id(),
+    api_get_course_info(),
     api_get_session_id(),
     api_get_user_id(),
     api_get_group_id()

+ 5 - 7
main/document/showinframes.php

@@ -91,10 +91,11 @@ if ($is_allowed_in_course == false) {
 // Check user visibility.
 $is_visible = DocumentManager::check_visibility_tree(
     $document_id,
-    api_get_course_id(),
+    api_get_course_info(),
     api_get_session_id(),
     api_get_user_id(),
-    api_get_group_id()
+    api_get_group_id(),
+    false
 );
 
 if (!$is_allowed_to_edit && !$is_visible) {
@@ -173,9 +174,7 @@ if (api_is_course_admin()) {
     $frameheight = 165;
 }
 
-$js_glossary_in_documents = '
-    setFrameReady("mainFrame");
-';
+$frameReady = Display::getFrameReadyBlock('top.mainFrame');
 
 $web_odf_supported_files = DocumentManager::get_web_odf_extension_list();
 // PDF should be displayed with viewerJS
@@ -251,8 +250,7 @@ if (!$playerSupported && $execute_iframe) {
         // Fixes the content height of the frame
         window.onload = function() {
             updateContentHeight();
-            '.$js_glossary_in_documents.'
-
+            '.$frameReady.'
         }
     </script>';
 }

+ 10 - 10
main/dropbox/index.php

@@ -282,6 +282,11 @@ if ($action != 'add') {
         /* Menu Sent */
         if (api_get_session_id() == 0) {
             echo '<div class="actions">';
+            if (empty($viewSentCategory)) {
+                echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=add\">".
+                    Display::return_icon('upload_file.png', get_lang('UploadNewFile'), '', ICON_SIZE_MEDIUM).
+                    "</a>";
+            }
             if ($view_dropbox_category_sent != 0) {
                 echo '<a href="'.api_get_self().'?'.api_get_cidreq().'&view_received_category='.$viewReceivedCategory.'&view_sent_category=0&view='.$view.'">'.
                     Display::return_icon('folder_up.png', get_lang('Up').' '.get_lang('Root'), '', ICON_SIZE_MEDIUM).
@@ -291,15 +296,15 @@ if ($action != 'add') {
                 echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=addsentcategory\">".
                     Display::return_icon('new_folder.png', get_lang('AddNewCategory'), '', ICON_SIZE_MEDIUM)."</a>\n";
             }
-            if (empty($viewSentCategory)) {
-                echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=add\">".
-                    Display::return_icon('upload_file.png', get_lang('UploadNewFile'), '', ICON_SIZE_MEDIUM).
-                    "</a>";
-            }
             echo '</div>';
         } else {
             if (api_is_allowed_to_session_edit(false, true)) {
                 echo '<div class="actions">';
+                if (empty($viewSentCategory)) {
+                    echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=add\">".
+                        Display::return_icon('upload_file.png', get_lang('UploadNewFile'), '', ICON_SIZE_MEDIUM).
+                    "</a>";
+                }
                 if ($view_dropbox_category_sent != 0) {
                     echo get_lang('CurrentlySeeing').': <strong>'.Security::remove_XSS($dropbox_categories[$view_dropbox_category_sent]['cat_name']).'</strong> ';
                     echo '<a href="'.api_get_self().'?'.api_get_cidreq().'&view_received_category='.$viewReceivedCategory.'&view_sent_category=0&view='.$view.'">'.
@@ -309,11 +314,6 @@ if ($action != 'add') {
                     echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=addsentcategory\">".
                         Display::return_icon('new_folder.png', get_lang('AddNewCategory'), '', ICON_SIZE_MEDIUM)."</a>\n";
                 }
-                if (empty($viewSentCategory)) {
-                    echo "<a href=\"".api_get_self()."?".api_get_cidreq()."&view=".$view."&action=add\">".
-                        Display::return_icon('upload_file.png', get_lang('UploadNewFile'), '', ICON_SIZE_MEDIUM).
-                    "</a>";
-                }
                 echo '</div>';
             }
         }

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

@@ -2634,9 +2634,10 @@ function count_number_of_forums_in_category($cat_id)
 {
     $table_forums = Database::get_course_table(TABLE_FORUM);
     $course_id = api_get_course_int_id();
+    $cat_id = (int) $cat_id;
     $sql = "SELECT count(*) AS number_of_forums
-            FROM ".$table_forums."
-            WHERE c_id = $course_id AND forum_category='".Database::escape_string($cat_id)."'";
+            FROM $table_forums
+            WHERE c_id = $course_id AND forum_category = $cat_id";
     $result = Database::query($sql);
     $row = Database::fetch_array($result);
 
@@ -4344,10 +4345,8 @@ function send_notification_mails($forumId, $thread_id, $reply_info)
  * be new posts and the user might have indicated that (s)he wanted to be
  * informed about the new posts by mail.
  *
- * @param string  Content type (post, thread, forum, forum_category)
- * @param int     Item DB ID
- * @param string $content
- * @param int    $id
+ * @param string    $content    Content type (post, thread, forum, forum_category)
+ * @param int       $id         Item DB ID of the corresponding content type
  *
  * @return string language variable
  *
@@ -4364,13 +4363,14 @@ function handle_mail_cue($content, $id)
     $table_users = Database::get_main_table(TABLE_MAIN_USER);
 
     $course_id = api_get_course_int_id();
+    $id = (int) $id;
 
     /* If the post is made visible we only have to send mails to the people
      who indicated that they wanted to be informed for that thread.*/
     if ($content == 'post') {
         // Getting the information about the post (need the thread_id).
         $post_info = get_post_information($id);
-        $thread_id = intval($post_info['thread_id']);
+        $thread_id = (int) $post_info['thread_id'];
 
         // Sending the mail to all the users that wanted to be informed for replies on this thread.
         $sql = "SELECT users.firstname, users.lastname, users.user_id, users.email
@@ -4378,11 +4378,11 @@ function handle_mail_cue($content, $id)
                 WHERE
                     posts.c_id = $course_id AND
                     mailcue.c_id = $course_id AND
-                    posts.thread_id='$thread_id'
-                    AND posts.post_notification='1'
-                    AND mailcue.thread_id='$thread_id'
-                    AND users.user_id=posts.poster_id
-                    AND users.active=1
+                    posts.thread_id = $thread_id AND
+                    posts.post_notification = '1' AND
+                    mailcue.thread_id = $thread_id AND
+                    users.user_id = posts.poster_id AND
+                    users.active = 1
                 GROUP BY users.email";
 
         $result = Database::query($sql);
@@ -4396,11 +4396,11 @@ function handle_mail_cue($content, $id)
                 WHERE
                     posts.c_id = $course_id AND
                     mailcue.c_id = $course_id AND
-                    posts.thread_id = ".intval($id)."
-                    AND posts.post_notification='1'
-                    AND mailcue.thread_id = ".intval($id)."
-                    AND users.user_id=posts.poster_id
-                    AND users.active=1
+                    posts.thread_id = $id AND
+                    posts.post_notification = '1' AND
+                    mailcue.thread_id = $id AND
+                    users.user_id = posts.poster_id
+                    users.active = 1
                 GROUP BY users.email";
         $result = Database::query($sql);
         while ($row = Database::fetch_array($result)) {
@@ -4409,18 +4409,18 @@ function handle_mail_cue($content, $id)
 
         // Deleting the relevant entries from the mailcue.
         $sql = "DELETE FROM $table_mailcue
-                WHERE c_id = $course_id AND thread_id='".Database::escape_string($id)."'";
+                WHERE c_id = $course_id AND thread_id = $id";
         Database::query($sql);
     } elseif ($content == 'forum') {
         $sql = "SELECT thread_id FROM $table_threads
-                WHERE c_id = $course_id AND forum_id='".Database::escape_string($id)."'";
+                WHERE c_id = $course_id AND forum_id = $id";
         $result = Database::query($sql);
         while ($row = Database::fetch_array($result)) {
             handle_mail_cue('thread', $row['thread_id']);
         }
     } elseif ($content == 'forum_category') {
         $sql = "SELECT forum_id FROM $table_forums
-                WHERE c_id = $course_id AND forum_category ='".Database::escape_string($id)."'";
+                WHERE c_id = $course_id AND forum_category = $id";
         $result = Database::query($sql);
         while ($row = Database::fetch_array($result)) {
             handle_mail_cue('forum', $row['forum_id']);

+ 14 - 13
main/forum/index.php

@@ -182,21 +182,8 @@ if (!empty($_GET['lp_id']) || !empty($_POST['lp_id'])) {
         $url
     );
 }
-if (!empty($allCourseForums)) {
-    $actionLeft .= search_link();
-}
 
 if (api_is_allowed_to_edit(false, true)) {
-    $actionLeft .= Display::url(
-        Display::return_icon(
-            'new_folder.png',
-            get_lang('AddForumCategory'),
-            null,
-            ICON_SIZE_MEDIUM
-        ),
-        api_get_self().'?'.api_get_cidreq().'&action=add&content=forumcategory&lp_id='.$lp_id
-    );
-
     if (is_array($forumCategories) && !empty($forumCategories)) {
         $actionLeft .= Display::url(
             Display::return_icon(
@@ -208,6 +195,20 @@ if (api_is_allowed_to_edit(false, true)) {
             api_get_self().'?'.api_get_cidreq().'&action=add&content=forum&lp_id='.$lp_id
         );
     }
+
+    $actionLeft .= Display::url(
+        Display::return_icon(
+            'new_folder.png',
+            get_lang('AddForumCategory'),
+            null,
+            ICON_SIZE_MEDIUM
+        ),
+        api_get_self().'?'.api_get_cidreq().'&action=add&content=forumcategory&lp_id='.$lp_id
+    );
+}
+
+if (!empty($allCourseForums)) {
+    $actionLeft .= search_link();
 }
 
 $actions = Display::toolbarAction('toolbar-forum', [$actionLeft]);

+ 0 - 3
main/gradebook/gradebook_add_link.php

@@ -154,7 +154,6 @@ if (isset($typeSelected) && $typeSelected != '0') {
     }
 }
 
-<<<<<<< HEAD
 $action_details = '';
 $current_id = 0;
 if (isset($_GET['selectcat'])) {
@@ -173,8 +172,6 @@ $logInfo = [
 ];
 Event::registerLog($logInfo);
 
-=======
->>>>>>> de59e9d72409ccb3dda93caa5fe09c8fba7dfe75
 $interbreadcrumb[] = [
     'url' => Category::getUrl().'selectcat='.$selectCat,
     'name' => get_lang('Gradebook'),

+ 17 - 13
main/gradebook/lib/fe/displaygradebook.php

@@ -400,15 +400,6 @@ class DisplayGradebook
         $actionsRight = '';
         $my_api_cidreq = api_get_cidreq();
         if (api_is_allowed_to_edit(null, true)) {
-            if (empty($grade_model_id) || $grade_model_id == -1) {
-                $actionsLeft .= '<a href="gradebook_add_cat.php?'.api_get_cidreq().'&selectcat='.$catobj->get_id().'">'.
-                    Display::return_icon(
-                        'new_folder.png',
-                        get_lang('AddGradebook'),
-                        [],
-                        ICON_SIZE_MEDIUM
-                    ).'</a></td>';
-            }
             if ($selectcat != '0') {
                 $my_category = $catobj->showAllCategoryInfo($catobj->get_id());
                 if ($my_api_cidreq == '') {
@@ -416,18 +407,31 @@ class DisplayGradebook
                 }
                 if ($show_add_link && !$message_resource) {
                     $actionsLeft .= '<a href="gradebook_add_eval.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'" >'.
-                        Display::return_icon('new_evaluation.png', get_lang('NewEvaluation'), '', ICON_SIZE_MEDIUM).'</a>';
+                        Display::return_icon('new_evaluation.png', get_lang('NewEvaluation'), '',
+                            ICON_SIZE_MEDIUM).'</a>';
                     $cats = Category::load($selectcat);
 
                     if ($cats[0]->get_course_code() != null && !$message_resource) {
                         $actionsLeft .= '<a href="gradebook_add_link.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
-                            Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '', ICON_SIZE_MEDIUM).'</a>';
+                            Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '',
+                                ICON_SIZE_MEDIUM).'</a>';
                     } else {
                         $actionsLeft .= '<a href="gradebook_add_link_select_course.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
-                            Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '', ICON_SIZE_MEDIUM).'</a>';
+                            Display::return_icon('new_online_evaluation.png', get_lang('MakeLink'), '',
+                                ICON_SIZE_MEDIUM).'</a>';
                     }
                 }
-
+            }
+            if (empty($grade_model_id) || $grade_model_id == -1) {
+                $actionsLeft .= '<a href="gradebook_add_cat.php?'.api_get_cidreq().'&selectcat='.$catobj->get_id().'">'.
+                    Display::return_icon(
+                        'new_folder.png',
+                        get_lang('AddGradebook'),
+                        [],
+                        ICON_SIZE_MEDIUM
+                    ).'</a></td>';
+            }
+            if ($selectcat != '0') {
                 if (!$message_resource) {
                     $actionsLeft .= '<a href="gradebook_flatview.php?'.$my_api_cidreq.'&selectcat='.$catobj->get_id().'">'.
                         Display::return_icon('statistics.png', get_lang('FlatView'), '', ICON_SIZE_MEDIUM).'</a>';

+ 2 - 1
main/group/group_category.php

@@ -79,6 +79,7 @@ if (isset($_GET['id'])) {
         'announcements_state' => GroupManager::TOOL_PRIVATE,
         'forum_state' => GroupManager::TOOL_PRIVATE,
         'max_student' => 0,
+        'document_access' => 0,
     ];
 }
 
@@ -440,7 +441,7 @@ if ($form->validate()) {
 }
 
 // Else display the form
-Display :: display_header($nameTools, 'Group');
+Display::display_header($nameTools, 'Group');
 
 // actions bar
 echo '<div class="actions">';

+ 24 - 25
main/inc/ajax/course_home.ajax.php

@@ -139,33 +139,33 @@ switch ($action) {
         require_once __DIR__.'/../global.inc.php';
 
         // Get the name of the database course.
-        $tbl_course_description = Database::get_course_table(TABLE_COURSE_DESCRIPTION);
         $course_info = api_get_course_info($_GET['code']);
-
-        if (api_get_setting('course_catalog_hide_private') === 'true' &&
-            $course_info['visibility'] == COURSE_VISIBILITY_REGISTERED
-        ) {
-            echo get_lang('PrivateAccess');
-            break;
-        }
-
-        $sql = "SELECT * FROM $tbl_course_description
-                WHERE c_id = ".$course_info['real_id']." AND session_id = 0
-                ORDER BY id";
-        $result = Database::query($sql);
-        if (Database::num_rows($result) > 0) {
-            while ($description = Database::fetch_object($result)) {
-                $descriptions[$description->id] = $description;
+        $content = get_lang('NoDescription');
+        if (!empty($course_info)) {
+            if (api_get_setting('course_catalog_hide_private') === 'true' &&
+                $course_info['visibility'] == COURSE_VISIBILITY_REGISTERED
+            ) {
+                echo get_lang('PrivateAccess');
+                break;
+            }
+            $table = Database::get_course_table(TABLE_COURSE_DESCRIPTION);
+            $sql = "SELECT * FROM $table
+                    WHERE c_id = ".$course_info['real_id']." AND session_id = 0
+                    ORDER BY id";
+            $result = Database::query($sql);
+            if (Database::num_rows($result) > 0) {
+                while ($description = Database::fetch_object($result)) {
+                    $descriptions[$description->id] = $description;
+                }
+                // Function that displays the details of the course description in html.
+                $content = CourseManager::get_details_course_description_html(
+                    $descriptions,
+                    api_get_system_encoding(),
+                    false
+                );
             }
-            // Function that displays the details of the course description in html.
-            echo CourseManager::get_details_course_description_html(
-                $descriptions,
-                api_get_system_encoding(),
-                false
-            );
-        } else {
-            echo get_lang('NoDescription');
         }
+        echo $content;
         break;
     case 'session_courses_lp_default':
         /**
@@ -584,7 +584,6 @@ switch ($action) {
         $courseInfo = api_get_course_info_by_id($courseId);
         $courseInfo['id_session'] = $sessionId;
         $courseInfo['status'] = $status;
-
         $id = 'notification_'.$courseId.'_'.$sessionId.'_'.$status;
 
         $notificationId = Session::read($id);

+ 8 - 3
main/inc/ajax/model.ajax.php

@@ -850,7 +850,7 @@ switch ($action) {
             '*',
             $object->table,
             [
-                'where' => ["session_id = ? " => $sessionId],
+                'where' => ['session_id = ? ' => $sessionId],
                 'order' => "$sidx $sord",
                 'LIMIT' => "$start , $limit", ]
         );
@@ -1433,6 +1433,9 @@ switch ($action) {
         break;
     case 'get_sessions_tracking':
         if (api_is_drh() || api_is_session_admin()) {
+            $orderByName = Database::escape_string($sidx);
+            $orderByName = in_array($orderByName, ['name', 'access_start_date']) ? $orderByName : 'name';
+            $orderBy = " ORDER BY $orderByName $sord";
             $sessions = SessionManager::get_sessions_followed_by_drh(
                 api_get_user_id(),
                 $start,
@@ -1440,7 +1443,7 @@ switch ($action) {
                 false,
                 false,
                 false,
-                null,
+                $orderBy,
                 $keyword,
                 $description
             );
@@ -1452,7 +1455,9 @@ switch ($action) {
                 $limit,
                 false,
                 $keyword,
-                $description
+                $description,
+                $sidx,
+                $sord
             );
         }
 

+ 7 - 3
main/inc/ajax/social.ajax.php

@@ -198,10 +198,14 @@ switch ($action) {
         }
         break;
     case 'list_wall_message':
-        $start = isset($_REQUEST['start']) ? intval($_REQUEST['start']) - 1 : 0;
-        $length = isset($_REQUEST['length']) ? intval($_REQUEST['length']) : 10;
-        $userId = isset($_REQUEST['u']) ? intval($_REQUEST['u']) : api_get_user_id();
+        if (api_is_anonymous()) {
+            break;
+        }
+        $start = isset($_REQUEST['start']) ? (int) $_REQUEST['start'] - 1 : 0;
+        $length = isset($_REQUEST['length']) ? (int) $_REQUEST['length'] : 10;
+        $userId = isset($_REQUEST['u']) ? (int) $_REQUEST['u'] : api_get_user_id();
         $friendId = $userId;
+
         $array = SocialManager::getWallMessagesPostHTML($userId, $friendId, null, $length, $start);
         if (!empty($array)) {
             ksort($array);

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

@@ -966,7 +966,7 @@ class TicketManager
 
             if ($isAdmin) {
                 $ticket = [
-                    $icon.' '.$row['subject'],
+                    $icon.' '.Security::remove_XSS($row['subject']),
                     $row['status_name'],
                     $row['start_date'],
                     $row['sys_lastedit_datetime'],
@@ -977,7 +977,7 @@ class TicketManager
                 ];
             } else {
                 $ticket = [
-                    $icon.' '.$row['subject'],
+                    $icon.' '.Security::remove_XSS($row['subject']),
                     $row['status_name'],
                     $row['start_date'],
                     $row['sys_lastedit_datetime'],

+ 36 - 29
main/inc/lib/attendance.lib.php

@@ -52,7 +52,9 @@ class Attendance
         $sql = "SELECT COUNT(att.id) AS total_number_of_items
                 FROM $tbl_attendance att
                 WHERE c_id = $course_id $condition_session ";
-        if ($active == 1 || $active == 0) {
+
+        $active = (int) $active;
+        if ($active === 1 || $active === 0) {
             $sql .= "AND att.active = $active";
         }
         $res = Database::query($sql);
@@ -72,12 +74,12 @@ class Attendance
     public function get_attendances_list($course_id = 0, $session_id = 0)
     {
         $table = Database::get_course_table(TABLE_ATTENDANCE);
-        $course_id = intval($course_id);
+        $course_id = (int) $course_id;
         if (empty($course_id)) {
             $course_id = api_get_course_int_id();
         }
 
-        $session_id = !empty($session_id) ? intval($session_id) : api_get_session_id();
+        $session_id = !empty($session_id) ? (int) $session_id : api_get_session_id();
         $condition_session = api_get_session_condition($session_id);
 
         // Get attendance data
@@ -286,7 +288,7 @@ class Attendance
     public function get_attendance_by_id($attendanceId)
     {
         $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
-        $attendanceId = intval($attendanceId);
+        $attendanceId = (int) $attendanceId;
         $course_id = api_get_course_int_id();
         $attendance_data = [];
         $sql = "SELECT * FROM $tbl_attendance
@@ -394,7 +396,7 @@ class Attendance
 
         $session_id = api_get_session_id();
         $user_id = api_get_user_id();
-        $attendanceId = intval($attendanceId);
+        $attendanceId = (int) $attendanceId;
         $course_code = $_course['code'];
         $course_id = $_course['real_id'];
         $title_gradebook = $this->attendance_qualify_title;
@@ -470,7 +472,7 @@ class Attendance
         $course_id = $_course['real_id'];
         if (is_array($attendanceId)) {
             foreach ($attendanceId as $id) {
-                $id = intval($id);
+                $id = (int) $id;
                 $sql = "UPDATE $tbl_attendance SET active = 1
                         WHERE c_id = $course_id AND id = '$id'";
                 $result = Database::query($sql);
@@ -487,7 +489,7 @@ class Attendance
                 }
             }
         } else {
-            $attendanceId = intval($attendanceId);
+            $attendanceId = (int) $attendanceId;
             $sql = "UPDATE $tbl_attendance SET active = 1
                     WHERE c_id = $course_id AND id = '$attendanceId'";
             $result = Database::query($sql);
@@ -575,7 +577,7 @@ class Attendance
         $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
         $user_id = api_get_user_id();
         $course_id = $_course['real_id'];
-        $status = intval($status);
+        $status = (int) $status;
 
         $action = 'visible';
         if ($status == 0) {
@@ -584,7 +586,7 @@ class Attendance
 
         if (is_array($attendanceId)) {
             foreach ($attendanceId as $id) {
-                $id = intval($id);
+                $id = (int) $id;
                 $sql = "UPDATE $tbl_attendance SET active = $status
                         WHERE c_id = $course_id AND id = '$id'";
                 $result = Database::query($sql);
@@ -601,7 +603,7 @@ class Attendance
                 }
             }
         } else {
-            $attendanceId = intval($attendanceId);
+            $attendanceId = (int) $attendanceId;
             $sql = "UPDATE $tbl_attendance SET active = $status
                     WHERE c_id = $course_id AND id = '$attendanceId'";
             $result = Database::query($sql);
@@ -633,8 +635,8 @@ class Attendance
     {
         $tbl_attendance = Database::get_course_table(TABLE_ATTENDANCE);
         $course_id = api_get_course_int_id();
-        $attendanceId = intval($attendanceId);
-        $locked = ($lock) ? 1 : 0;
+        $attendanceId = (int) $attendanceId;
+        $locked = $lock ? 1 : 0;
         $upd = "UPDATE $tbl_attendance SET locked = $locked
                 WHERE c_id = $course_id AND id = $attendanceId";
         $result = Database::query($upd);
@@ -776,8 +778,8 @@ class Attendance
         $tbl_attendance_sheet = Database::get_course_table(TABLE_ATTENDANCE_SHEET);
         $tbl_attendance_calendar = Database::get_course_table(TABLE_ATTENDANCE_CALENDAR);
 
-        $calendar_id = intval($calendar_id);
-        $attendanceId = intval($attendanceId);
+        $calendar_id = (int) $calendar_id;
+        $attendanceId = (int) $attendanceId;
         $users = $this->get_users_rel_course();
         $course_id = api_get_course_int_id();
 
@@ -794,7 +796,7 @@ class Attendance
 
         // save users present in class
         foreach ($users_present as $user_present) {
-            $uid = intval($user_present);
+            $uid = (int) $user_present;
             // check if user already was registered with the $calendar_id
             $sql = "SELECT user_id FROM $tbl_attendance_sheet
                     WHERE c_id = $course_id AND user_id='$uid' AND attendance_calendar_id = '$calendar_id'";
@@ -901,6 +903,7 @@ class Attendance
         // get count of presences by users inside current attendance and save like results
         if (count($user_ids) > 0) {
             foreach ($user_ids as $uid) {
+                $uid = (int) $uid;
                 $count_presences = 0;
                 if (count($calendar_ids) > 0) {
                     $sql = "SELECT count(presence) as count_presences
@@ -1239,6 +1242,7 @@ class Attendance
             $user_ids = array_keys($users);
             if (count($calendar_ids) > 0 && count($user_ids) > 0) {
                 foreach ($user_ids as $uid) {
+                    $uid = (int) $uid;
                     $sql = "SELECT * FROM $tbl_attendance_sheet
                             WHERE
                                 c_id = $course_id AND
@@ -1255,7 +1259,7 @@ class Attendance
             }
         } else {
             // Get attendance for current user
-            $user_id = intval($user_id);
+            $user_id = (int) $user_id;
             if (count($calendar_ids) > 0) {
                 $sql = "SELECT cal.date_time, att.presence
                         FROM $tbl_attendance_sheet att
@@ -1290,7 +1294,7 @@ class Attendance
     public function get_next_attendance_calendar_id($attendanceId)
     {
         $table = Database::get_course_table(TABLE_ATTENDANCE_CALENDAR);
-        $attendanceId = intval($attendanceId);
+        $attendanceId = (int) $attendanceId;
         $course_id = api_get_course_int_id();
 
         $sql = "SELECT id FROM $table
@@ -1321,7 +1325,7 @@ class Attendance
     {
         $table = Database::get_course_table(TABLE_ATTENDANCE_CALENDAR);
         $course_id = api_get_course_int_id();
-        $attendanceId = intval($attendanceId);
+        $attendanceId = (int) $attendanceId;
         $sql = "SELECT id, date_time FROM $table
                 WHERE
                     c_id = $course_id AND
@@ -1358,6 +1362,7 @@ class Attendance
         $attendanceId = intval($attendanceId);
         $groupId = (int) $groupId;
         $course_id = api_get_course_int_id();
+
         if (empty($groupId)) {
             $sql = "SELECT score FROM $tbl_attendance_result
                     WHERE
@@ -1647,13 +1652,13 @@ class Attendance
      */
     public static function is_all_attendance_calendar_done($attendanceId)
     {
-        $attendanceId = intval($attendanceId);
+        $attendanceId = (int) $attendanceId;
         $done_calendar = self::get_done_attendance_calendar($attendanceId);
         $count_dates_in_calendar = self::get_count_dates_inside_attendance_calendar($attendanceId);
         $number_of_dates = self::get_number_of_attendance_calendar($attendanceId);
 
         $result = false;
-        if ($number_of_dates && (intval($count_dates_in_calendar) == intval($done_calendar))) {
+        if ($number_of_dates && intval($count_dates_in_calendar) == intval($done_calendar)) {
             $result = true;
         }
 
@@ -1670,7 +1675,7 @@ class Attendance
      */
     public static function is_locked_attendance($attendanceId)
     {
-        //use gradebook lock
+        //  Use gradebook lock
         $result = api_resource_is_locked_by_gradebook($attendanceId, LINK_ATTENDANCE);
 
         return $result;
@@ -1739,7 +1744,6 @@ class Attendance
         }
 
         $table = Database::get_course_table(TABLE_ATTENDANCE_CALENDAR_REL_GROUP);
-
         foreach ($groupList as $groupId) {
             if (empty($groupId)) {
                 continue;
@@ -1777,7 +1781,8 @@ class Attendance
         return Database::select(
             '*',
             $table,
-            ['where' => [
+            [
+                'where' => [
                     'calendar_id = ? AND c_id = ?' => [$calendarId, $courseId],
                 ],
             ]
@@ -1798,7 +1803,8 @@ class Attendance
         return Database::select(
             '*',
             $table,
-            ['where' => [
+            [
+                'where' => [
                     'calendar_id = ? AND c_id = ? AND group_id = ?' => [$calendarId, $courseId, $groupId],
                 ],
             ]
@@ -1935,6 +1941,8 @@ class Attendance
         $tbl_attendance_sheet = Database::get_course_table(TABLE_ATTENDANCE_SHEET);
 
         $attendanceId = intval($attendanceId);
+        $calendar_id = (int) $calendar_id;
+
         // get all registered users inside current course
         $users = $this->get_users_rel_course();
         $user_ids = array_keys($users);
@@ -1961,11 +1969,11 @@ class Attendance
         } else {
             // delete just one row from attendance sheet by the calendar id
             $sql = "DELETE FROM $tbl_attendance_sheet
-                    WHERE c_id = $course_id AND attendance_calendar_id = '".intval($calendar_id)."'";
+                    WHERE c_id = $course_id AND attendance_calendar_id = '".$calendar_id."'";
             Database::query($sql);
             // delete data from attendance calendar
             $sql = "DELETE FROM $tbl_attendance_calendar
-                    WHERE c_id = $course_id AND id = '".intval($calendar_id)."'";
+                    WHERE c_id = $course_id AND id = '".$calendar_id."'";
             Database::query($sql);
 
             $this->deleteAttendanceCalendarGroup($calendar_id, $course_id);
@@ -1978,10 +1986,9 @@ class Attendance
         return $affected_rows;
     }
 
-    /** Setters for fields of attendances tables */
-    public function set_session_id($session_id)
+    public function set_session_id($sessionId)
     {
-        $this->session_id = $session_id;
+        $this->session_id = $sessionId;
     }
 
     public function set_course_id($course_id)

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

@@ -1337,14 +1337,13 @@ class CourseHome
         }
 
         $blocks = self::getUserBlocks();
-
         $html = '';
         if (!empty($blocks)) {
-            $style_id = 'toolshortcuts_vertical';
+            $styleId = 'toolshortcuts_vertical';
             if ($orientation == SHORTCUTS_HORIZONTAL) {
-                $style_id = 'toolshortcuts_horizontal';
+                $styleId = 'toolshortcuts_horizontal';
             }
-            $html .= '<div id="'.$style_id.'">';
+            $html .= '<div id="'.$styleId.'">';
 
             $html .= Display::url(
                 Display::return_icon('home.png', get_lang('CourseHomepageLink'), '', ICON_SIZE_MEDIUM),

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

@@ -204,6 +204,7 @@ define('TABLE_LP_VIEW', 'lp_view');
 define('TABLE_LP_ITEM_VIEW', 'lp_item_view');
 define('TABLE_LP_IV_INTERACTION', 'lp_iv_interaction'); // IV = Item View
 define('TABLE_LP_IV_OBJECTIVE', 'lp_iv_objective'); // IV = Item View
+define('TABLE_LP_CATEGORY', 'lp_category');
 
 // Smartblogs (Kevin Van Den Haute::kevin@develop-it.be)
 // Permission tables

+ 52 - 0
main/inc/lib/display.lib.php

@@ -2745,4 +2745,56 @@ HTML;
                     <hr />
               </div>';
     }
+
+    /**
+     * @param string $frameName
+     *
+     * @return string
+     */
+    public static function getFrameReadyBlock($frameName)
+    {
+        $defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen', 'vrview'];
+        $features = api_get_configuration_value('video_features');
+        if (!empty($features) && isset($features['features'])) {
+            foreach ($features['features'] as $feature) {
+                if ($feature === 'vrview') {
+                    continue;
+                }
+                $defaultFeatures[] = $feature;
+            }
+        }
+
+        $defaultFeatures = implode("','", $defaultFeatures);
+        $frameReady = '
+          $.frameReady(function() {
+            $(document).ready(function () {
+                $("video:not(.skip), audio:not(.skip)").mediaelementplayer({
+                    pluginPath: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/",            
+                    features: ["'.$defaultFeatures.'"],
+                    success: function(mediaElement, originalNode, instance) {
+                    },
+                    vrPath: "'.api_get_path(WEB_PUBLIC_PATH).'assets/vrview/build/vrview.js"
+                });
+            });
+          }, "'.$frameName.'",
+          {
+            load: [
+                { type:"script", id:"_fr1", src:"'.api_get_jquery_web_path().'"},
+                { type:"script", id:"_fr7", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/MathJax/MathJax.js?config=AM_HTMLorMML"},
+                { type:"script", id:"_fr4", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/jquery-ui.min.js"},
+                { type:"stylesheet", id:"_fr5", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/jquery-ui.min.css"},
+                { type:"stylesheet", id:"_fr6", src:"'.api_get_path(WEB_PUBLIC_PATH).'assets/jquery-ui/themes/smoothness/theme.css"},
+                { type:"script", id:"_fr2", src:"'.api_get_path(WEB_LIBRARY_PATH).'javascript/jquery.highlight.js"},
+                { type:"script", id:"_fr3", src:"'.api_get_path(WEB_CODE_PATH).'glossary/glossary.js.php?'.api_get_cidreq().'"},
+                {type: "script", id: "_media1", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/mediaelement-and-player.min.js"},
+                {type: "stylesheet", id: "_media2", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/build/mediaelementplayer.min.css"},                
+                {type: "stylesheet", id: "_media4", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/plugins/vrview/vrview.css"},
+                {type: "script", id: "_media4", src: "'.api_get_path(WEB_PUBLIC_PATH).'assets/mediaelement/plugins/vrview/vrview.js"},
+            ]
+          });';
+
+        return $frameReady;
+
+
+    }
 }

+ 41 - 24
main/inc/lib/document.lib.php

@@ -601,7 +601,7 @@ class DocumentManager
                 foreach ($documentData as $row) {
                     $isVisible = self::check_visibility_tree(
                         $row['id'],
-                        $courseInfo['code'],
+                        $courseInfo,
                         $sessionId,
                         api_get_user_id(),
                         $toGroupId
@@ -3507,31 +3507,44 @@ class DocumentManager
     }
 
     /**
-     * @param int    $doc_id
-     * @param string $course_code
-     * @param int    $session_id
-     * @param int    $user_id
-     * @param int    $groupId     iid
+     * @param int   $doc_id
+     * @param array $courseInfo
+     * @param int   $sessionId
+     * @param int   $user_id
+     * @param int   $groupId               iid
+     * @param bool  $checkParentVisibility
      *
      * @return bool
      */
     public static function check_visibility_tree(
         $doc_id,
-        $course_code,
-        $session_id,
+        $courseInfo,
+        $sessionId,
         $user_id,
-        $groupId = 0
+        $groupId = 0,
+        $checkParentVisibility = true
     ) {
+        if (empty($courseInfo)) {
+            return false;
+        }
+
+        $courseCode = $courseInfo['code'];
+
+        if (empty($courseCode)) {
+            return false;
+        }
+
         $document_data = self::get_document_data_by_id(
             $doc_id,
-            $course_code,
+            $courseCode,
             null,
-            $session_id
+            $sessionId
         );
-        if ($session_id != 0 && !$document_data) {
+
+        if ($sessionId != 0 && !$document_data) {
             $document_data = self::get_document_data_by_id(
                 $doc_id,
-                $course_code,
+                $courseCode,
                 null,
                 0
             );
@@ -3539,30 +3552,34 @@ class DocumentManager
 
         if (!empty($document_data)) {
             // If admin or course teacher, allow anyway
-            if (api_is_platform_admin() || CourseManager::is_course_teacher($user_id, $course_code)) {
+            if (api_is_platform_admin() || CourseManager::is_course_teacher($user_id, $courseCode)) {
                 return true;
             }
-            $course_info = api_get_course_info($course_code);
+
             if ($document_data['parent_id'] == false || empty($document_data['parent_id'])) {
                 if (!empty($groupId)) {
                     return true;
                 }
-                $visible = self::is_visible_by_id($doc_id, $course_info, $session_id, $user_id);
+                $visible = self::is_visible_by_id($doc_id, $courseInfo, $sessionId, $user_id);
 
                 return $visible;
             } else {
-                $visible = self::is_visible_by_id($doc_id, $course_info, $session_id, $user_id);
+                $visible = self::is_visible_by_id($doc_id, $courseInfo, $sessionId, $user_id);
 
                 if (!$visible) {
                     return false;
                 } else {
-                    return self::check_visibility_tree(
-                        $document_data['parent_id'],
-                        $course_code,
-                        $session_id,
-                        $user_id,
-                        $groupId
-                    );
+                    if ($checkParentVisibility) {
+                        return self::check_visibility_tree(
+                            $document_data['parent_id'],
+                            $courseInfo,
+                            $sessionId,
+                            $user_id,
+                            $groupId
+                        );
+                    }
+
+                    return true;
                 }
             }
         } else {

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

@@ -177,7 +177,7 @@ function online_logout($user_id = null, $logout_redirect = false)
     Session::destroy();
 
     $pluginKeycloak = api_get_plugin_setting('keycloak', 'tool_enable') === 'true';
-    if ($pluginKeycloak) {
+    if ($pluginKeycloak && $uinfo['auth_source'] === 'keycloack') {
         $pluginUrl = api_get_path(WEB_PLUGIN_PATH).'keycloak/start.php?slo';
         header('Location: '.$pluginUrl);
         exit;

+ 38 - 22
main/inc/lib/social.lib.php

@@ -152,14 +152,16 @@ class SocialManager extends UserManager
         $search_name = null,
         $load_extra_info = true
     ) {
+        $user_id = (int) $user_id;
+
         $list_ids_friends = [];
         $tbl_my_friend = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
         $tbl_my_user = Database::get_main_table(TABLE_MAIN_USER);
         $sql = 'SELECT friend_user_id FROM '.$tbl_my_friend.'
                 WHERE
                     relation_type NOT IN ('.USER_RELATION_TYPE_DELETED.', '.USER_RELATION_TYPE_RRHH.') AND
-                    friend_user_id<>'.((int) $user_id).' AND
-                    user_id='.((int) $user_id);
+                    friend_user_id<>'.$user_id.' AND
+                    user_id='.$user_id;
         if (isset($id_group) && $id_group > 0) {
             $sql .= ' AND relation_type='.$id_group;
         }
@@ -309,17 +311,17 @@ class SocialManager extends UserManager
      *
      * @author isaac flores paz
      *
-     * @param int user receiver id
+     * @param int $userId user receiver id
      *
      * @return int
      */
-    public static function get_message_number_invitation_by_user_id($user_receiver_id)
+    public static function get_message_number_invitation_by_user_id($userId)
     {
         $table = Database::get_main_table(TABLE_MESSAGE);
-        $user_receiver_id = (int) $user_receiver_id;
+        $userId = (int) $userId;
         $sql = 'SELECT COUNT(*) as count_message_in_box FROM '.$table.'
                 WHERE
-                    user_receiver_id='.$user_receiver_id.' AND
+                    user_receiver_id='.$userId.' AND
                     msg_status='.MESSAGE_STATUS_INVITATION_PENDING;
         $res = Database::query($sql);
         $row = Database::fetch_array($res, 'ASSOC');
@@ -333,16 +335,17 @@ class SocialManager extends UserManager
     /**
      * Get number of messages sent to other users.
      *
-     * @param int $sender_id
+     * @param int $userId
      *
      * @return int
      */
-    public static function getCountMessagesSent($sender_id)
+    public static function getCountMessagesSent($userId)
     {
+        $userId = (int) $userId;
         $table = Database::get_main_table(TABLE_MESSAGE);
         $sql = 'SELECT COUNT(*) FROM '.$table.'
                 WHERE
-                    user_sender_id='.intval($sender_id).' AND
+                    user_sender_id='.$userId.' AND
                     msg_status < 5';
         $res = Database::query($sql);
         $row = Database::fetch_row($res);
@@ -474,6 +477,8 @@ class SocialManager extends UserManager
      */
     public static function getCountInvitationSent($userId)
     {
+        $userId = (int) $userId;
+
         if (empty($userId)) {
             return 0;
         }
@@ -482,7 +487,7 @@ class SocialManager extends UserManager
         $sql = 'SELECT count(user_receiver_id) count
                 FROM '.$table.'
                 WHERE
-                    user_sender_id = '.intval($userId).' AND
+                    user_sender_id = '.$userId.' AND
                     msg_status = '.MESSAGE_STATUS_INVITATION_PENDING;
         $res = Database::query($sql);
         if (Database::num_rows($res)) {
@@ -697,6 +702,8 @@ class SocialManager extends UserManager
     public static function get_logged_user_course_html($my_course, $count)
     {
         $result = '';
+        $count = (int) $count;
+
         // Table definitions
         $main_user_table = Database::get_main_table(TABLE_MAIN_USER);
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
@@ -806,6 +813,9 @@ class SocialManager extends UserManager
      */
     public static function show_social_avatar_block($show = '', $group_id = 0, $user_id = 0)
     {
+        $user_id = (int) $user_id;
+        $group_id = (int) $group_id;
+
         if (empty($user_id)) {
             $user_id = api_get_user_id();
         }
@@ -897,6 +907,9 @@ class SocialManager extends UserManager
         $show_full_profile = false,
         $show_delete_account_button = false
     ) {
+        $user_id = (int) $user_id;
+        $group_id = (int) $group_id;
+
         if (empty($user_id)) {
             $user_id = api_get_user_id();
         }
@@ -1277,7 +1290,7 @@ class SocialManager extends UserManager
      */
     public static function display_user_list($user_list, $wrap = true)
     {
-        $html = null;
+        $html = '';
 
         if (isset($_GET['id']) || count($user_list) < 1) {
             return false;
@@ -1354,7 +1367,7 @@ class SocialManager extends UserManager
     public static function display_individual_user($user_id)
     {
         global $interbreadcrumb;
-        $safe_user_id = intval($user_id);
+        $safe_user_id = (int) $user_id;
         $currentUserId = api_get_user_id();
 
         $user_table = Database::get_main_table(TABLE_MAIN_USER);
@@ -1610,7 +1623,7 @@ class SocialManager extends UserManager
      * Gets all messages from someone's wall (within specific limits).
      *
      * @param int        $userId        id of wall shown
-     * @param string     $messageStatus status wall message
+     * @param int        $messageStatus status wall message
      * @param int|string $parentId      id message (Post main)
      * @param string     $start         Date from which we want to show the messages, in UTC time
      * @param int        $limit         Limit for the number of parent messages we want to show
@@ -1634,10 +1647,11 @@ class SocialManager extends UserManager
 
         $tblMessage = Database::get_main_table(TABLE_MESSAGE);
         $tblMessageAttachment = Database::get_main_table(TABLE_MESSAGE_ATTACHMENT);
-
-        $userId = intval($userId);
+        $parentId = (int) $parentId;
+        $userId = (int) $userId;
         $start = Database::escape_string($start);
-        $limit = intval($limit);
+        $offset = (int) $offset;
+        $messageStatus = (int) $messageStatus;
 
         $sql = "SELECT
                     id,
@@ -1679,7 +1693,7 @@ class SocialManager extends UserManager
      *
      * @param int    $userId    USER ID of the person's wall
      * @param int    $friendId  id person
-     * @param int    $idMessage id message
+     * @param int    $messageId id message
      * @param string $start     Start date (from when we want the messages until today)
      * @param int    $limit     Limit to the number of messages we want
      * @param int    $offset    Wall messages offset
@@ -1689,7 +1703,7 @@ class SocialManager extends UserManager
     public static function getWallMessagesHTML(
         $userId,
         $friendId,
-        $idMessage,
+        $messageId,
         $start = null,
         $limit = 10,
         $offset = 0
@@ -1702,7 +1716,7 @@ class SocialManager extends UserManager
         $messages = self::getWallMessages(
             $userId,
             MESSAGE_STATUS_WALL,
-            $idMessage,
+            $messageId,
             $start,
             $limit,
             $offset
@@ -1748,7 +1762,7 @@ class SocialManager extends UserManager
                 $media .= Display::url(
                     Display::returnFontAwesomeIcon('trash'),
                     $url,
-                    ['title' => get_lang("SocialMessageDelete")]
+                    ['title' => get_lang('SocialMessageDelete')]
                 );
                 $media .= '</div>';
                 $media .= '</div>';
@@ -1762,7 +1776,7 @@ class SocialManager extends UserManager
         $formattedList .= '<div class="mediapost-form">';
         $formattedList .= '<form name="social_wall_message" method="POST">
                 <label for="social_wall_new_msg" class="hide">'.get_lang('SocialWriteNewComment').'</label>
-                <input type="hidden" name = "messageId" value="'.$idMessage.'" />
+                <input type="hidden" name = "messageId" value="'.$messageId.'" />
                 <textarea placeholder="'.get_lang('SocialWriteNewComment').
                 '" name="social_wall_new_msg" rows="1" style="width:80%;" ></textarea>
                 <button type="submit" name="social_wall_new_msg_submit"
@@ -2237,6 +2251,9 @@ class SocialManager extends UserManager
      */
     private static function headerMessagePost($authorId, $receiverId, $users, $message, $isOwnWall = false)
     {
+        $authorId = (int) $authorId;
+        $receiverId = (int) $receiverId;
+
         $date = api_get_local_time($message['send_date']);
         $avatarAuthor = $users[$authorId]['avatar'];
         $urlAuthor = api_get_path(WEB_CODE_PATH).'social/profile.php?u='.$authorId;
@@ -2260,7 +2277,6 @@ class SocialManager extends UserManager
         if (!empty($message['path'])) {
             $imageBig = UserManager::getUserPicture($authorId, USER_IMAGE_SIZE_BIG);
             $imageSmall = UserManager::getUserPicture($authorId, USER_IMAGE_SIZE_SMALL);
-
             $wallImage = '<a class="thumbnail ajax" href="'.$imageBig.'"><img src="'.$imageSmall.'"></a>';
         }
 

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

@@ -470,19 +470,19 @@ class Template
         $this->assign('show_toolbar', $show_toolbar);
 
         // Only if course is available
-        $show_course_shortcut = '';
+        $courseToolBar = '';
         $show_course_navigation_menu = '';
         if (!empty($this->course_id) && $this->user_is_logged_in) {
             if (api_get_setting('show_toolshortcuts') != 'false') {
                 // Course toolbar
-                $show_course_shortcut = CourseHome::show_navigation_tool_shortcuts();
+                $courseToolBar = CourseHome::show_navigation_tool_shortcuts();
             }
             if (api_get_setting('show_navigation_menu') != 'false') {
                 // Course toolbar
                 $show_course_navigation_menu = CourseHome::show_navigation_menu();
             }
         }
-        $this->assign('show_course_shortcut', $show_course_shortcut);
+        $this->assign('show_course_shortcut', $courseToolBar);
         $this->assign('show_course_navigation_menu', $show_course_navigation_menu);
     }
 

+ 19 - 7
main/inc/lib/tracking.lib.php

@@ -3734,12 +3734,14 @@ class Tracking
     /**
      * Get sessions coached by user.
      *
-     * @param $coach_id
+     * @param        $coach_id
      * @param int    $start
      * @param int    $limit
      * @param bool   $getCount
      * @param string $keyword
      * @param string $description
+     * @param string $orderByName
+     * @param string $orderByDirection
      *
      * @return mixed
      */
@@ -3749,12 +3751,14 @@ class Tracking
         $limit = 0,
         $getCount = false,
         $keyword = '',
-        $description = ''
+        $description = '',
+        $orderByName = '',
+        $orderByDirection = ''
     ) {
         // table definition
         $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
         $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
-        $coach_id = intval($coach_id);
+        $coach_id = (int) $coach_id;
 
         $select = " SELECT * FROM ";
         if ($getCount) {
@@ -3781,6 +3785,15 @@ class Tracking
         $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
         $access_url_id = api_get_current_access_url_id();
 
+        $orderBy = '';
+        if (!empty($orderByName)) {
+            if (in_array($orderByName, ['name', 'access_start_date'])) {
+                $orderByDirection = in_array(strtolower($orderByDirection), ['asc', 'desc']) ? $orderByDirection : 'asc';
+                $orderByName = Database::escape_string($orderByName);
+                $orderBy .= " ORDER BY $orderByName $orderByDirection";
+            }
+        }
+
         $sql = "
             $select
             (
@@ -3813,7 +3826,7 @@ class Tracking
                 WHERE
                     access_url_id = $access_url_id
                     $keywordCondition
-            ) as sessions $limitCondition
+            ) as sessions $limitCondition $orderBy
             ";
 
         $rs = Database::query($sql);
@@ -3825,7 +3838,7 @@ class Tracking
 
         $sessions = [];
         while ($row = Database::fetch_array($rs)) {
-            if ($row['access_start_date'] == '0000-00-00 00:00:00') {
+            if ($row['access_start_date'] === '0000-00-00 00:00:00') {
                 $row['access_start_date'] = null;
             }
 
@@ -3834,8 +3847,7 @@ class Tracking
 
         if (!empty($sessions)) {
             foreach ($sessions as &$session) {
-                if (empty($session['access_start_date'])
-                ) {
+                if (empty($session['access_start_date'])) {
                     $session['status'] = get_lang('SessionActive');
                 } else {
                     $time_start = api_strtotime($session['access_start_date'], 'UTC');

+ 15 - 9
main/inc/lib/usergroup.lib.php

@@ -407,6 +407,7 @@ class UserGroup extends Model
 
     /**
      * @param array $options
+     * @param int   $type
      *
      * @return array
      */
@@ -550,7 +551,8 @@ class UserGroup extends Model
                     ],
                 ],
             ];
-            $from = $this->usergroup_rel_course_table." as c INNER JOIN ".$this->access_url_rel_usergroup." a
+            $from = $this->usergroup_rel_course_table." as c 
+                    INNER JOIN ".$this->access_url_rel_usergroup." a
                     ON c.usergroup_id = a.usergroup_id";
         } else {
             $options = ['where' => ['c.course_id = ?' => $course_id]];
@@ -1369,6 +1371,8 @@ class UserGroup extends Model
     public function update_group_picture($group_id, $file = null, $source_file = null)
     {
         // Validation 1.
+        $group_id = (int) $group_id;
+
         if (empty($group_id)) {
             return false;
         }
@@ -1774,12 +1778,12 @@ class UserGroup extends Model
             default: // Base: empty, the result path below will be relative.
                 $base = '';
         }
+        $id = (int) $id;
 
         if (empty($id) || empty($type)) {
             return $anonymous ? ['dir' => $base.'img/', 'file' => 'unknown.jpg'] : ['dir' => '', 'file' => ''];
         }
 
-        $id = (int) $id;
         $group_table = Database::get_main_table(TABLE_USERGROUP);
         $sql = "SELECT picture FROM $group_table WHERE id = ".$id;
         $res = Database::query($sql);
@@ -1976,7 +1980,7 @@ class UserGroup extends Model
     {
         $table_url_rel_group = $this->usergroup_rel_user_table;
         $result_array = [];
-        $relation_type = intval($relation_type);
+        $relation_type = (int) $relation_type;
 
         if (is_array($user_list) && is_array($group_list)) {
             foreach ($group_list as $group_id) {
@@ -1987,7 +1991,7 @@ class UserGroup extends Model
 		               			SET
 		               			    user_id = ".intval($user_id).",
 		               			    usergroup_id = ".intval($group_id).",
-		               			    relation_type = ".intval($relation_type);
+		               			    relation_type = ".$relation_type;
 
                         $result = Database::query($sql);
                         if ($result) {
@@ -2093,15 +2097,16 @@ class UserGroup extends Model
      *
      * @author Julio Montoya
      * */
-    public function get_groups_by_user($user_id = '', $relation_type = GROUP_USER_PERMISSION_READER, $with_image = false)
+    public function get_groups_by_user($user_id, $relation_type = GROUP_USER_PERMISSION_READER, $with_image = false)
     {
         $table_group_rel_user = $this->usergroup_rel_user_table;
         $tbl_group = $this->table;
+        $user_id = (int) $user_id;
 
         if ($relation_type == 0) {
             $relationCondition = '';
         } else {
-            $relation_type = intval($relation_type);
+            $relation_type = (int) $relation_type;
             $relationCondition = " AND gu.relation_type = $relation_type ";
         }
 
@@ -2536,7 +2541,6 @@ class UserGroup extends Model
         $from = intval($from);
         $number_of_items = intval($number_of_items);
 
-        //$sql .= " ORDER BY col$column $direction ";
         $sql .= " LIMIT $from,$number_of_items";
 
         $res = Database::query($sql);
@@ -2559,6 +2563,8 @@ class UserGroup extends Model
     public static function get_parent_groups($group_id)
     {
         $t_rel_group = Database::get_main_table(TABLE_USERGROUP_REL_USERGROUP);
+        $group_id = (int) $group_id;
+
         $max_level = 10;
         $select_part = "SELECT ";
         $cond_part = '';
@@ -2605,7 +2611,7 @@ class UserGroup extends Model
         $relationType = GROUP_USER_PERMISSION_ADMIN,
         $includeSubgroupsUsers = true
     ) {
-        $userId = intval($userId);
+        $userId = (int) $userId;
         $groups = $this->get_groups_by_user($userId, $relationType);
         $groupsId = array_keys($groups);
         $subgroupsId = [];
@@ -2656,7 +2662,7 @@ class UserGroup extends Model
     public static function getGroupsByDepthLevel($groupId, $levels = 10)
     {
         $groups = [];
-        $groupId = intval($groupId);
+        $groupId = (int) $groupId;
 
         $groupTable = Database::get_main_table(TABLE_USERGROUP);
         $groupRelGroupTable = Database::get_main_table(TABLE_USERGROUP_REL_USERGROUP);

+ 36 - 5
main/inc/lib/usermanager.lib.php

@@ -5529,7 +5529,28 @@ class UserManager
     }
 
     /**
-     * Subscribe boss to students.
+     * @param int $userId
+     *
+     * @return bool
+     */
+    public static function removeAllBossFromStudent($userId)
+    {
+        $userId = (int) $userId;
+
+        if (empty($userId)) {
+            return false;
+        }
+
+        $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
+        $sql = "DELETE FROM $userRelUserTable 
+                WHERE user_id = $userId AND relation_type = ".USER_RELATION_TYPE_BOSS;
+        Database::query($sql);
+
+        return true;
+    }
+
+    /**
+     * Subscribe boss to students, if $bossList is empty then the boss list will be empty too.
      *
      * @param int   $studentId
      * @param array $bossList
@@ -5543,7 +5564,8 @@ class UserManager
         $sendNotification = false
     ) {
         $inserted = 0;
-        if ($bossList) {
+        if (!empty($bossList)) {
+            sort($bossList);
             $studentId = (int) $studentId;
             $studentInfo = api_get_user_info($studentId);
 
@@ -5551,10 +5573,17 @@ class UserManager
                 return false;
             }
 
+            $previousBossList = self::getStudentBossList($studentId);
+            $previousBossList = !empty($previousBossList) ? array_column($previousBossList, 'boss_id') : [];
+            sort($previousBossList);
+
+            // Boss list is the same, nothing changed.
+            if ($bossList == $previousBossList) {
+                return false;
+            }
+
             $userRelUserTable = Database::get_main_table(TABLE_MAIN_USER_REL_USER);
-            $sql = "DELETE FROM $userRelUserTable 
-                    WHERE user_id = $studentId AND relation_type = ".USER_RELATION_TYPE_BOSS;
-            Database::query($sql);
+            self::removeAllBossFromStudent($studentId);
 
             foreach ($bossList as $bossId) {
                 $bossId = (int) $bossId;
@@ -5578,6 +5607,8 @@ class UserManager
                     $inserted++;
                 }
             }
+        } else {
+            self::removeAllBossFromStudent($studentId);
         }
 
         return $inserted;

+ 9 - 2
main/lp/learnpath.class.php

@@ -3704,11 +3704,9 @@ class learnpath
                         $item_id,
                         $this->get_view_id()
                     );
-
                     if ($this->debug > 0) {
                         error_log('rl_get_resource_link_for_learnpath - file: '.$file, 0);
                     }
-
                     switch ($lp_item_type) {
                         case 'document':
                             // Shows a button to download the file instead of just downloading the file directly.
@@ -6853,6 +6851,15 @@ class learnpath
     ) {
         $actionsLeft = '';
         $actionsRight = '';
+        $actionsLeft .= Display::url(
+            Display::return_icon(
+                'back.png',
+                get_lang('ReturnToLearningPaths'),
+                '',
+                ICON_SIZE_MEDIUM
+            ),
+            'lp_controller.php?'.api_get_cidreq()
+        );
         $actionsLeft .= Display::url(
             Display::return_icon(
                 'preview_view.png',

+ 12 - 12
main/lp/lp_list.php

@@ -74,18 +74,6 @@ $actions = '';
 
 if ($is_allowed_to_edit) {
     $actionLeft = '';
-    if (!$sessionId) {
-        $actionLeft .= Display::url(
-            Display::return_icon(
-                'new_folder.png',
-                get_lang('AddCategory'),
-                [],
-                ICON_SIZE_MEDIUM
-            ),
-            api_get_self().'?'.api_get_cidreq().'&action=add_lp_category'
-        );
-    }
-
     $actionLeft .= Display::url(
         Display::return_icon(
             'new_learnpath.png',
@@ -116,6 +104,18 @@ if ($is_allowed_to_edit) {
             '../upload/upload_ppt.php?'.api_get_cidreq().'&curdirpath=/&tool='.TOOL_LEARNPATH
         );
     }
+
+    if (!$sessionId) {
+        $actionLeft .= Display::url(
+            Display::return_icon(
+                'new_folder.png',
+                get_lang('AddCategory'),
+                [],
+                ICON_SIZE_MEDIUM
+            ),
+            api_get_self().'?'.api_get_cidreq().'&action=add_lp_category'
+        );
+    }
     $actions = Display::toolbarAction('actions-lp', [$actionLeft]);
 }
 

+ 2 - 0
main/lp/lp_view.php

@@ -623,6 +623,8 @@ $template->assign(
     )
 );
 
+$frameReady = Display::getFrameReadyBlock('top.content_name');
+$template->assign('frame_ready', $frameReady);
 $view = $template->get_template('learnpath/view.tpl');
 $content = $template->fetch($view);
 

+ 3 - 3
main/messages/new_message.php

@@ -186,7 +186,7 @@ function manageForm($default, $select_from_user_list = null, $sent_to = '', $tpl
 
     if (isset($_GET['re_id'])) {
         $message_reply_info = MessageManager::get_message_by_id($_GET['re_id']);
-        $default['title'] = get_lang('MailSubjectReplyShort').' '.$message_reply_info['title'];
+        $default['title'] = get_lang('MailSubjectReplyShort').' '.Security::remove_XSS($message_reply_info['title']);
         $form->addHidden('re_id', (int) $_GET['re_id']);
         $form->addHidden('save_form', 'save_form');
 
@@ -207,14 +207,14 @@ function manageForm($default, $select_from_user_list = null, $sent_to = '', $tpl
             $fileListToString = !empty($attachments) ? implode('<br />', $attachments) : '';
             $form->addLabel('', $fileListToString);
         }
-        $default['title'] = '['.get_lang('MailSubjectForwardShort').": ".$message_reply_info['title'].']';
+        $default['title'] = '['.get_lang('MailSubjectForwardShort').": ".Security::remove_XSS($message_reply_info['title']).']';
         $form->addHidden('forward_id', $forwardId);
         $form->addHidden('save_form', 'save_form');
         $receiverInfo = api_get_user_info($message_reply_info['user_receiver_id']);
 
         $forwardMessage = '---------- '.get_lang('ForwardedMessage').' ---------'.'<br />';
         $forwardMessage .= get_lang('Date').': '.api_get_local_time($message_reply_info['send_date']).'<br />';
-        $forwardMessage .= get_lang('Subject').': '.$message_reply_info['title'].'<br />';
+        $forwardMessage .= get_lang('Subject').': '.Security::remove_XSS($message_reply_info['title']).'<br />';
         $forwardMessage .= get_lang('To').': '.$receiverInfo['complete_name'].' - '.$receiverInfo['email'].' <br />';
         $default['content'] = '<p><br/></p>'.$forwardMessage.'<br />'.Security::filter_terms($message_reply_info['content']);
     }

+ 6 - 11
main/mySpace/session.php

@@ -13,19 +13,14 @@ require_once __DIR__.'/../inc/global.inc.php';
 api_block_anonymous_users();
 
 $this_section = SECTION_TRACKING;
-
-api_block_anonymous_users();
-
 $export_csv = false;
-
 if (isset($_GET['export']) && $_GET['export'] == 'csv') {
     $export_csv = true;
 }
 
+$id_coach = api_get_user_id();
 if (isset($_GET['id_coach']) && $_GET['id_coach'] != '') {
-    $id_coach = intval($_GET['id_coach']);
-} else {
-    $id_coach = api_get_user_id();
+    $id_coach = (int) $_GET['id_coach'];
 }
 
 $allowToTrack = api_is_platform_admin(true, true) || api_is_teacher();
@@ -35,7 +30,7 @@ if (!$allowToTrack) {
 }
 
 $htmlHeadXtra[] = api_get_jqgrid_js();
-$interbreadcrumb[] = ["url" => "index.php", "name" => get_lang('MySpace')];
+$interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('MySpace')];
 Display::display_header(get_lang('Sessions'));
 
 if (api_is_platform_admin(true, true)) {
@@ -44,11 +39,11 @@ if (api_is_platform_admin(true, true)) {
     if (!api_is_session_admin()) {
         $menu_items[] = Display::url(
             Display::return_icon('statistics.png', get_lang('MyStats'), '', ICON_SIZE_MEDIUM),
-            api_get_path(WEB_CODE_PATH)."auth/my_progress.php"
+            api_get_path(WEB_CODE_PATH).'auth/my_progress.php'
         );
         $menu_items[] = Display::url(
             Display::return_icon('user.png', get_lang('Students'), [], ICON_SIZE_MEDIUM),
-            "index.php?view=drh_students&amp;display=yourstudents"
+            'index.php?view=drh_students&amp;display=yourstudents'
         );
         $menu_items[] = Display::url(
             Display::return_icon('teacher.png', get_lang('Trainers'), [], ICON_SIZE_MEDIUM),
@@ -143,7 +138,7 @@ $columns = [
 // Column config
 $columnModel = [
     ['name' => 'name', 'index' => 'name', 'width' => '255', 'align' => 'left'],
-    ['name' => 'date', 'index' => 'date', 'width' => '150', 'align' => 'left', 'sortable' => 'false'],
+    ['name' => 'date', 'index' => 'access_start_date', 'width' => '150', 'align' => 'left'],
     ['name' => 'course_per_session', 'index' => 'course_per_session', 'width' => '150', 'sortable' => 'false'],
     ['name' => 'student_per_session', 'index' => 'student_per_session', 'width' => '100', 'sortable' => 'false'],
     ['name' => 'details', 'index' => 'details', 'width' => '100', 'sortable' => 'false'],

+ 5 - 5
main/social/personal_data.php

@@ -256,7 +256,7 @@ foreach ($properties as $key => $value) {
                         $personalDataContent .= '<li>'.get_lang('NoData').'</li>';
                     } else {
                         foreach ($subValue as $subSubValue) {
-                            $personalDataContent .= '<li>'.$subSubValue.'</li>';
+                            $personalDataContent .= '<li>'.Security::remove_XSS($subSubValue).'</li>';
                         }
                     }
                     $personalDataContent .= '</ul>';
@@ -268,7 +268,7 @@ foreach ($properties as $key => $value) {
                     $personalDataContent .= '<li>'.get_lang('NoData').'</li>';
                 } else {
                     foreach ($value as $subValue) {
-                        $personalDataContent .= '<li>'.$subValue->variable.': '.$subValue->value.'</li>';
+                        $personalDataContent .= '<li>'.$subValue->variable.': '.Security::remove_XSS($subValue->value).'</li>';
                     }
                 }
                 $personalDataContent .= '</ul>';
@@ -292,7 +292,7 @@ foreach ($properties as $key => $value) {
                                 );
                                 $personalDataContent .= '<li>'.$documentLink.'</li>';
                             } else {
-                                $personalDataContent .= '<li>'.$subSubValue.'</li>';
+                                $personalDataContent .= '<li>'.Security::remove_XSS($subSubValue).'</li>';
                             }
                         }
                     }
@@ -312,7 +312,7 @@ foreach ($properties as $key => $value) {
                     $personalDataContent .= '<li>'.get_lang('NoData').'</li>';
                 } else {
                     foreach ($value as $subValue) {
-                        $personalDataContent .= '<li>'.$subValue.'</li>';
+                        $personalDataContent .= '<li>'.Security::remove_XSS($subValue).'</li>';
                     }
                 }
                 $personalDataContent .= '</ul>';
@@ -350,7 +350,7 @@ foreach ($properties as $key => $value) {
             $personalDataContent .= '<li>'.$key.': '.get_lang('ComplexDataNotShown').'</li>';
         }*/
     } else {
-        $personalDataContent .= '<li>'.$key.': '.$value.'</li>';
+        $personalDataContent .= '<li>'.$key.': '.Security::remove_XSS($value).'</li>';
     }
 }
 $personalDataContent .= '</ul>';

+ 0 - 33
main/template/default/layout/main.js.tpl

@@ -598,37 +598,4 @@ function copyTextToClipBoard(elementId)
 
     /* Copy the text inside the text field */
     document.execCommand("copy");
-
-    /* Alert the copied text */
-    //alert('Copied');
 }
-
-function setFrameReady(iframeName) {
-    $.frameReady(function () {
-        $(document).ready(function () {
-            $('video:not(.skip), audio:not(.skip)').mediaelementplayer({
-                pluginPath: '{{ _p.web }}web/assets/mediaelement/build/',
-                //renderers: ['html5', 'flash_video', 'native_flv'],
-                features: ['{{ video_features }}'],
-                success: function(mediaElement, originalNode, instance) {
-                },
-                vrPath: '{{ _p.web }}web/assets/vrview/build/vrview.js'
-            });
-        });
-    }, 'top.' + iframeName, {
-        load: [
-            {type: 'script', id: '_fr1', src: '{{ _p.web }}web/assets/jquery/dist/jquery.min.js'},
-            {type: 'script', id: '_fr7', src: '{{ _p.web }}web/assets/MathJax/MathJax.js?config=AM_HTMLorMML'},
-            {type: 'script', id: '_fr4', src: '{{ _p.web }}web/assets/jquery-ui/jquery-ui.min.js'},
-            {type: 'stylesheet', id: '_fr5', src: '{{ _p.web }}web/assets/jquery-ui/themes/smoothness/jquery-ui.min.css'},
-            {type: 'stylesheet', id: '_fr6', src: '{{ _p.web }}web/assets/jquery-ui/themes/smoothness/theme.css'},
-            {type: 'script', id: '_fr2', src: '{{ _p.web_lib }}javascript/jquery.highlight.js'},
-            {type: 'script', id: '_fr3', src: '{{ _p.web_main }}glossary/glossary.js.php?{{ _p.web_cid_query }}'},
-            {type: 'script', id: '_media1', src: '{{ _p.web }}web/assets/mediaelement/build/mediaelement-and-player.min.js'},
-            {type: 'stylesheet', id: '_media2', src: '{{ _p.web }}web/assets/mediaelement/build/mediaelementplayer.min.css'},
-            {#{type: 'script', id: '_media3', src: '{{ _p.web_lib }}javascript/iframe-js-loader.js'},#}
-            {type: 'stylesheet', id: '_media4', src: '{{ _p.web }}web/assets/mediaelement/plugins/vrview/vrview.css'},
-            {type: 'script', id: '_media4', src: '{{ _p.web }}web/assets/mediaelement/plugins/vrview/vrview.js'},
-        ]
-    });
-}

+ 4 - 5
main/template/default/layout/page.tpl

@@ -47,11 +47,6 @@
             <!-- START CONTENT -->
             <section id="cm-content">
                 <div class="container">
-                    {% block breadcrumb %}
-                        {{ breadcrumb }}
-                    {% endblock %}
-
-                    <!-- END HEADER -->
                     {% if show_course_shortcut is not null %}
                         <!-- TOOLS SHOW COURSE -->
                         <div id="cm-tools" class="nav-tools">
@@ -60,6 +55,10 @@
                         <!-- END TOOLS SHOW COURSE -->
                     {% endif %}
 
+                    {% block breadcrumb %}
+                        {{ breadcrumb }}
+                    {% endblock %}
+
                     {% block body %}
                         {{ content }}
                     {% endblock %}

+ 4 - 4
main/template/default/layout/show_header.tpl

@@ -48,10 +48,6 @@
     <section id="cm-content">
         <div class="container">
             {% if show_header == true %}
-                {% block breadcrumb %}
-                    {{ breadcrumb }}
-                {% endblock %}
-
                 {% if show_course_shortcut is not null %}
                     <!-- TOOLS SHOW COURSE -->
                     <div id="cm-tools" class="nav-tools">
@@ -59,5 +55,9 @@
                     </div>
                     <!-- END TOOLS SHOW COURSE -->
                 {% endif %}
+
+                {% block breadcrumb %}
+                    {{ breadcrumb }}
+                {% endblock %}
             {% endif %}
             {{ flash_messages }}

+ 7 - 6
main/template/default/learnpath/view.tpl

@@ -383,13 +383,14 @@
                 {% endif %}
             })();
         {% endif %}
-
         {% if disable_js_in_lp_view == 0 %}
-            $('iframe#content_id').on('load', function () {
-                var arr = ['link', 'sco'];
-                if (!$.inArray(olms.lms_item_type, arr)) {
-                    setFrameReady('content_name');
-                }
+            $(document).ready(function () {
+                $('iframe#content_id').on('load', function () {
+                    var arr = ['link', 'sco'];
+                    if ($.inArray(olms.lms_item_type, arr) == -1) {
+                        {{ frame_ready }}
+                    }
+                });
             });
         {% endif %}
 

+ 9 - 3
main/ticket/ticket_details.php

@@ -121,7 +121,12 @@ $htmlHeadXtra[] = '<style>
 
 $ticket_id = (int) $_REQUEST['ticket_id'];
 $ticket = TicketManager::get_ticket_detail_by_id($ticket_id);
-if (!isset($ticket['ticket'])) {
+if (!isset($ticket['ticket']) ||
+    // make sure it's either a user assigned to this ticket, or the reporter, or and admin
+    !($ticket['ticket']['assigned_last_user'] == $user_id ||
+      $ticket['ticket']['sys_insert_user_id'] == $user_id ||
+      $isAdmin)
+    ) {
     api_not_allowed(true);
 }
 if (!isset($_REQUEST['ticket_id'])) {
@@ -351,7 +356,7 @@ echo '<table width="100%" >
         <tr>
           <td colspan="3">
           <h1>'.$title.'</h1>
-          <h2>'.$ticket['ticket']['subject'].'</h2>
+          <h2>'.Security::remove_XSS($ticket['ticket']['subject']).'</h2>
           <p>
             '.$senderData.' '.
             get_lang('Created').' '.
@@ -405,11 +410,12 @@ if ($ticket['ticket']['course_url'] != null) {
             <td colspan="2"></td>
           </tr>';
 }
+
 echo '<tr>
         <td>
         <hr />
         <b>'.get_lang('Description').':</b> <br />
-        '.$ticket['ticket']['message'].'
+        '.Security::remove_XSS($ticket['ticket']['message']).'
         <hr />
         </td>            
      </tr>

+ 17 - 7
main/wiki/wiki.inc.php

@@ -6033,7 +6033,7 @@ class Wiki
                     'addnew'
                 ).'>'
                 .Display::return_icon(
-                    'add.png',
+                    'new_document.png',
                     get_lang('AddNew'),
                     '',
                     ICON_SIZE_MEDIUM
@@ -6069,7 +6069,7 @@ class Wiki
                 get_lang('SearchPages'),
                 '',
                 ICON_SIZE_MEDIUM
-            ).'</a></li>';
+            ).'</a>';
         ///menu more
         $actionsLeft .= '<a href="index.php?cidReq='.$_course['id'].'&action=searchpages&session_id='.$session_id.'&group_id='.$groupId.'&action=more&title='.api_htmlentities(
                 urlencode($page)
@@ -6079,18 +6079,28 @@ class Wiki
                 get_lang('Statistics'),
                 '',
                 ICON_SIZE_MEDIUM
-            ).'</a></li>';
+            ).'</a>';
 
         // menu all pages
-        $actionsLeft .= '<a class="btn btn-default" href="index.php?cidReq='.$_course['id'].'&action=allpages&session_id='.$session_id.'&group_id='.$groupId.'"'.self::is_active_navigation_tab(
+        $actionsLeft .= '<a href="index.php?cidReq='.$_course['id'].'&action=allpages&session_id='.$session_id.'&group_id='.$groupId.'"'.self::is_active_navigation_tab(
                 'allpages'
             ).'>'.
-            get_lang('AllPages').'</a>';
+            Display::return_icon(
+                'list_badges.png',
+                get_lang('AllPages'),
+                '',
+                ICON_SIZE_MEDIUM
+            ).'</a>';
         // menu recent changes
-        $actionsLeft .= '<a class="btn btn-default" href="index.php?cidReq='.$_course['id'].'&action=recentchanges&session_id='.$session_id.'&group_id='.$groupId.'"'.self::is_active_navigation_tab(
+        $actionsLeft .= '<a href="index.php?cidReq='.$_course['id'].'&action=recentchanges&session_id='.$session_id.'&group_id='.$groupId.'"'.self::is_active_navigation_tab(
                 'recentchanges'
             ).'>'.
-            get_lang('RecentChanges').'</a>';
+            Display::return_icon(
+                'history.png',
+                get_lang('RecentChanges'),
+                '',
+                ICON_SIZE_MEDIUM
+            )   .'</a>';
         echo Display::toolbarAction('toolbar-wiki', [$actionsLeft]);
     }
 

+ 0 - 9
main/work/upload.php

@@ -50,15 +50,6 @@ if ($onlyOnePublication) {
     $count = get_work_count_by_student($user_id, $work_id);
     if ($count >= 1) {
         api_not_allowed(true);
-
-        /*Display::display_header();
-        if (api_get_course_setting('student_delete_own_publication') == '1') {
-            echo Display::return_message(get_lang('CantUploadDeleteYourPaperFirst'), 'warning');
-        } else {
-            echo Display::return_message(get_lang('YouAlreadySentAPaperYouCantUpload'), 'warning');
-        }
-        Display::display_footer();
-        exit;*/
     }
 }
 

+ 2 - 1
plugin/bbb/lang/english.php

@@ -30,7 +30,8 @@ $strings['ServerIsNotConfigured'] = "Videoconference server is not configured";
 $strings['XUsersOnLine'] = "%s user(s) online";
 
 $strings['host'] = 'BigBlueButton host';
-$strings['host_help'] = 'This is the name of the server where your BigBlueButton server is running. Might be localhost, an IP address (e.g. 192.168.13.54) or a domain name (e.g. my.video.com).';
+$strings['host_help'] = 'This is the name of the server where your BigBlueButton server is running. 
+Might be localhost, an IP address (e.g. http://192.168.13.54) or a domain name (e.g. http://my.video.com).';
 
 $strings['salt'] = 'BigBlueButton salt';
 $strings['salt_help'] = 'This is the security key of your BigBlueButton server, which will allow your server to authentify the Chamilo installation. Refer to the BigBlueButton documentation to locate it. Try bbb-conf --salt';

+ 2 - 1
plugin/bbb/lang/french.php

@@ -30,7 +30,8 @@ $strings['ServerIsNotConfigured'] = "Le serveur de vidéoconférence n'est pas c
 $strings['XUsersOnLine'] = "%s utilisateurs dans la salle";
 
 $strings['host'] = 'Hôte de BigBlueButton';
-$strings['host_help'] = "C'est le nom du serveur où le serveur de vidéoconférence a été habilité. Cela peut être localhost, une adresse IP (du genre 192.168.13.54) ou un nom de domaine (du genre ma.video.com).";
+$strings['host_help'] = "C'est le nom du serveur où le serveur de vidéoconférence a été habilité. 
+Cela peut être localhost, une adresse IP (du genre http://192.168.13.54) ou un nom de domaine (du genre http://ma.video.com).";
 
 $strings['salt'] = 'Clef BigBlueButton';
 $strings['salt_help'] = "C'est la clef de sécurité de votre serveur BigBlueButton (appelée 'salt' en anglais) qui permet à votre serveur de vérifier l'identité de votre installation de Chamilo et ainsi l'autoriser à se connecter. Veuillez vous référer à la documentation de BigBlueButton pour la localiser, ou utilisez la commande 'bbb-conf --salt' si vous disposez d'un accès en ligne de commande au serveur de vidéoconférence.";

+ 1 - 1
plugin/bbb/lang/german.php

@@ -30,7 +30,7 @@ $strings['ServerIsNotConfigured'] = "Videokonferenzserver ist nicht konfiguriert
 $strings['XUsersOnLine'] = "%s Benutzer online";
 
 $strings['host'] = 'BigBlueButton host';
-$strings['host_help'] = 'This is the name of the server where your BigBlueButton server is running. Might be localhost, an IP address (e.g. 192.168.13.54) or a domain name (e.g. my.video.com).';
+$strings['host_help'] = 'This is the name of the server where your BigBlueButton server is running. Might be localhost, an IP address (e.g. http://192.168.13.54) or a domain name (e.g. http://my.video.com).';
 
 $strings['salt'] = 'BigBlueButton salt';
 $strings['salt_help'] = 'This is the security key of your BigBlueButton server, which will allow your server to authentify the Chamilo installation. Refer to the BigBlueButton documentation to locate it. Try bbb-conf --salt';

+ 1 - 1
plugin/bbb/lang/spanish.php

@@ -30,7 +30,7 @@ $strings['ServerIsNotConfigured'] = "El servidor de videoconferencia no está co
 $strings['XUsersOnLine'] = "%s usuario(s) en la sala";
 
 $strings['host'] = 'Host de BigBlueButton';
-$strings['host_help'] = 'Este es el nombre del servidor donde su servidor BigBlueButton está corriendo. Puede ser localhost, una dirección IP (ej: 192.168.13.54) o un nombre de dominio (ej: mi.video.com).';
+$strings['host_help'] = 'Este es el nombre del servidor donde su servidor BigBlueButton está corriendo. Puede ser localhost, una dirección IP (ej: http://192.168.13.54) o un nombre de dominio (ej: http://mi.video.com).';
 
 $strings['salt'] = 'Clave BigBlueButton';
 $strings['salt_help'] = 'Esta es la llave de seguridad de su servidor BigBlueButton (llamada "salt" en inglés), que permitirá a su servidor de autentifica la instalación de Chamilo (como autorizada). Refiérese a la documentación de BigBlueButton para ubicarla, o use el comando "bbb-conf --salt" si tiene acceso al servidor en línea de comando.';

+ 3 - 1
plugin/bbb/lib/bbb.lib.php

@@ -119,7 +119,8 @@ class bbb
                 $this->url = str_replace($this->protocol, '', $this->url);
                 $urlWithProtocol = $bbb_host;
             } else {
-                $urlWithProtocol = '://'.$bbb_host;
+                // We asume it's an http, if user wants to use https host must include the protocol.
+                $urlWithProtocol = 'http://'.$bbb_host;
             }
 
             // Setting BBB api
@@ -605,6 +606,7 @@ class bbb
             $url = $this->api->getJoinMeetingURL($joinParams);
             $url = $this->protocol.$url;
         }
+
         if ($this->debug) {
             error_log("return url :".$url);
         }

+ 0 - 1
plugin/bbb/start.php

@@ -18,7 +18,6 @@ $logInfo = [
     'action' => '',
     'action_details' => '',
     'current_id' => 0,
-    'info' => '',
 ];
 Event::registerLog($logInfo);
 

+ 1 - 1
plugin/buycourses/lang/spanish.php

@@ -20,7 +20,7 @@ $strings['SetCommissions'] = "Aplicar comisiones";
 $strings['CommissionsConfig'] = "Configurar Comisiones";
 $strings['PayoutReport'] = "Reporte de Pagos";
 $strings['Stats'] = "Estadísticas";
-$strings['InfoCommissions'] = "Ingrese aquí la comisión de ventas, en porcentaje (%), para la organización que controla la plataforma. Este porcentaje se deducirá del monto percibido por los docentes por cada curso o sessión vendido en la plataforma.";
+$strings['InfoCommissions'] = "Ingrese aquí la comisión de ventas, en porcentaje (%), para la organización que controla la plataforma. Este porcentaje se deducirá del monto percibido por los docentes por cada curso o sesión vendido en la plataforma.";
 $strings['NeedToSelectPaymentType'] = "Necesita seleccionar el tipo de pago";
 $strings['IndividualPayout'] = "Pago individual";
 $strings['CancelPayout'] = "Cancelar Payout";

+ 3 - 0
plugin/keycloak/KeycloakPlugin.php

@@ -48,6 +48,9 @@ class KeycloakPlugin extends Plugin
         return $this->get('content');
     }
 
+    /**
+     * Deletes all keycloak chamilo session data
+     */
     public function logout()
     {
         Session::erase('samlUserdata');

+ 52 - 0
plugin/keycloak/README.md

@@ -0,0 +1,52 @@
+Keycloak
+==============
+
+1. Enable the plugin.
+2. Create a new settings.php file here plugin/keycloak/settings.php you can find an example here:
+plugin/keycloak/settings.dist.php
+
+3. Edit the settings.php file with your Keycloak settings:
+
+<pre>
+'idp' => array(
+    'entityId' => 'http://localhost:8080/auth/realms/master',
+    'singleSignOnService' => array (
+        'url' => 'http://localhost:8080/auth/realms/master/protocol/saml',
+    ),
+    'singleLogoutService' => array (
+        'url' => 'http://localhost:8080/auth/realms/master/protocol/saml',
+    ),
+    'x509cert' => 'xxx',
+)
+</pre>
+
+4. Configure your keycloak server with the following settings:
+
+* Create a client with "client id" value:
+ http://www.example.org/plugin/keycloak/metadata.php
+ 
+* Valid redirect URIs: * 
+* Client Protocol: saml
+* Logout service redirect Binding URL:  http://www.example.org/plugin/keycloak/start.php?sls
+
+
+Change the client scope roles to "Single role attribute".
+
+- Client Scopes-> role_list -> Mappers -> role list -> "Single Role Attribute" = true
+
+Add user mappers for "Firstname" "LastName" and "Email" so Chamilo can get those values.
+
+Clients -> (select the client previously created) -> mappers -> create
+
+Name: Email
+Mapper Type = User Attribute. 
+User Attribute: Email
+Friendly Name: Email
+SAML Attribute Name: Email
+SAML Attribute: Basic 
+
+Repeat the process for the 3 attributes. 
+
+Create a demo user in keycloak
+
+Try to login using the keycloak new button in Chamilo. 

+ 1 - 1
plugin/keycloak/lang/english.php

@@ -1,6 +1,6 @@
 <?php
 
 $strings['plugin_title'] = 'Keycloak';
-$strings['plugin_comment'] = 'Keycloak login integration with Chamilo';
+$strings['plugin_comment'] = 'Keycloak login integration with Chamilo.';
 $strings['block_title'] = 'Keycloak';
 $strings['tool_enable'] = 'Enabled';

+ 27 - 12
plugin/keycloak/start.php

@@ -15,13 +15,21 @@ if (!$pluginKeycloak) {
 }
 
 // Create a settings.dist.php
-require_once 'settings.php';
+if (file_exists('settings.php')) {
+    require_once 'settings.php';
+} else {
+    $message = '';
+    if (api_is_platform_admin()) {
+        $message = 'Create a settings.php file in plugin/keycloak/';
+    }
+    api_not_allowed(true, $message);
+}
 
-$content = '';
 
+$content = '';
 $auth = new Auth($settingsInfo);
 
-if (isset($_REQUEST['delete'])) {
+/*if (isset($_REQUEST['delete'])) {
     Session::erase('samlNameId');
     Session::erase('samlSessionIndex');
     Session::erase('samlNameIdFormat');
@@ -30,7 +38,7 @@ if (isset($_REQUEST['delete'])) {
     Session::erase('LogoutRequestID');
     echo 'delete all';
     exit;
-}
+}*/
 
 $settings = new Settings($settingsInfo);
 $authRequest = new AuthnRequest($settings);
@@ -92,10 +100,10 @@ if (isset($_GET['sso'])) {
         exit;
     }
 
-    $keyCloackUserName = Session::read('samlNameId');
+    $keyCloackUserName = $auth->getNameId();
     $userInfo = api_get_user_info_from_username($keyCloackUserName);
     $attributes = $auth->getAttributes();
-
+    $userId = 0;
     if (!empty($attributes) && empty($userInfo)) {
         $firstName = reset($attributes['FirstName']);
         $lastName = reset($attributes['LastName']);
@@ -118,8 +126,15 @@ if (isset($_GET['sso'])) {
             '',
             'keycloak'
         );
+
+        if ($userId) {
+            $userInfo = api_get_user_info($userId);
+        }
     } else {
-        $userId = $userInfo['user_id'];
+        // Only load users that were created using this method.
+        if ($userInfo['auth_source'] === 'keycloak') {
+            $userId = $userInfo['user_id'];
+        }
     }
 
     if (!empty($userId)) {
@@ -135,11 +150,13 @@ if (isset($_GET['sso'])) {
         Session::write('_user', $userInfo);
         Session::write('is_platformAdmin', false);
         Session::write('is_allowedCreateCourse', false);
+    } else {
+        Display::addFlash(Display::return_message(get_lang('InvalidId')));
     }
 
-    if (isset($_POST['RelayState']) && \OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) {
-        //$auth->redirectTo($_POST['RelayState']);
-    }
+    /*if (isset($_POST['RelayState']) && \OneLogin\Saml2\Utils::getSelfURL() != $_POST['RelayState']) {
+        $auth->redirectTo($_POST['RelayState']);
+    }*/
     header('Location: '.api_get_path(WEB_PATH));
     exit;
 } elseif (isset($_GET['sls'])) {
@@ -167,9 +184,7 @@ $template = new Template('');
 
 if (isset($_SESSION['samlUserdata'])) {
     $attributes = Session::read('samlUserdata');
-
     $params = [];
-
     if (!empty($attributes)) {
         $content .= 'You have the following attributes:<br>';
         $content .= '<table class="table"><thead><th>Name</th><th>Values</th></thead><tbody>';

+ 3 - 0
src/Chamilo/CourseBundle/Component/CourseCopy/Course.php

@@ -144,6 +144,9 @@ class Course
                             $title = $resource->name;
                             $description = $resource->description;
                             break;
+                        case RESOURCE_LEARNPATH_CATEGORY:
+                            $title = $resource->name;
+                            break;
                         case RESOURCE_LINK:
                             $title = $resource->title;
                             $description = $resource->description;

+ 61 - 33
src/Chamilo/CourseBundle/Component/CourseCopy/CourseRecycler.php

@@ -66,6 +66,7 @@ class CourseRecycler
         $this->recycle_test_category();
         $this->recycle_surveys();
         $this->recycle_learnpaths();
+        $this->recycle_learnpath_categories();
         $this->recycle_cours_description();
         $this->recycle_wiki();
         $this->recycle_glossary();
@@ -305,49 +306,57 @@ class CourseRecycler
     }
 
     /**
-     * Delete forum-categories
-     * Deletes all forum-categories from current course without forums.
+     * Deletes all forum-categories without forum from the current course.
+     * Categories with forums in it are dealt with by recycle_forums()
+     * This requires a check on the status of the forum item in c_item_property
      */
     public function recycle_forum_categories()
     {
-        $table_forum = Database::get_course_table(TABLE_FORUM);
-        $table_forumcat = Database::get_course_table(TABLE_FORUM_CATEGORY);
-        $sql = "SELECT fc.cat_id FROM ".$table_forumcat." fc
-                LEFT JOIN ".$table_forum." f
-                ON
-                    fc.cat_id=f.forum_category AND
-                    fc.c_id = ".$this->course_id." AND
-                    f.c_id = ".$this->course_id."
-        		WHERE f.forum_id IS NULL";
-        $res = Database::query($sql);
-        while ($obj = Database::fetch_object($res)) {
-            $sql = "DELETE FROM ".$table_forumcat."
-                    WHERE c_id = ".$this->course_id." AND cat_id = ".$obj->cat_id;
-            Database::query($sql);
-        }
+        $forumTable = Database::get_course_table(TABLE_FORUM);
+        $forumCategoryTable = Database::get_course_table(TABLE_FORUM_CATEGORY);
+        $itemPropertyTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
+        $courseId = $this->course_id;
+        // c_forum_forum.forum_category points to c_forum_category.cat_id and
+        // has to be queried *with* the c_id to ensure a match
+        $subQuery = "SELECT distinct(f.forum_category) as categoryId
+              FROM $forumTable f, $itemPropertyTable i
+              WHERE
+                  f.c_id = $courseId AND
+                  i.c_id = f.c_id AND
+                  i.tool = 'forum' AND
+        	      f.iid = i.ref AND 
+                  i.visibility = 1";
+        $sql = "DELETE FROM $forumCategoryTable
+                    WHERE c_id = $courseId AND cat_id NOT IN ($subQuery)";
+        Database::query($sql);
     }
 
     /**
-     * Delete link-categories
      * Deletes all empty link-categories (=without links) from current course.
+     * Links are already dealt with by recycle_links() but if recycle is called
+     * on categories and not on link, then non-empty categories will survive
+     * the recycling.
      */
     public function recycle_link_categories()
     {
-        $link_cat_table = Database::get_course_table(TABLE_LINK_CATEGORY);
-        $link_table = Database::get_course_table(TABLE_LINK);
-        $sql = "SELECT lc.id FROM $link_cat_table lc
-                LEFT JOIN ".$link_table." l
-                ON
-                    lc.id=l.category_id AND
-                    lc.c_id = ".$this->course_id." AND
-                    l.c_id = ".$this->course_id."
-        		WHERE l.id IS NULL";
-        $res = Database::query($sql);
-        while ($obj = Database::fetch_object($res)) {
-            $sql = "DELETE FROM ".$link_cat_table."
-                    WHERE c_id = ".$this->course_id." AND id = ".$obj->id;
-            Database::query($sql);
-        }
+        $linkCategoryTable = Database::get_course_table(TABLE_LINK_CATEGORY);
+        $linkTable = Database::get_course_table(TABLE_LINK);
+        $itemPropertyTable = Database::get_course_table(TABLE_ITEM_PROPERTY);
+        $courseId = $this->course_id;
+        // c_link.category_id points to c_link_category.id and
+        // has to be queried *with* the c_id to ensure a match
+        $subQuery = "SELECT distinct(l.category_id) as categoryId
+              FROM $linkTable l, $itemPropertyTable i
+              WHERE
+                  l.c_id = $courseId AND
+                  i.c_id = l.c_id AND
+                  i.tool = 'link' AND
+        	      l.iid = i.ref AND 
+                  i.visibility = 1";
+        $sql = "DELETE FROM $linkCategoryTable
+                    WHERE c_id = $courseId AND id NOT IN ($subQuery)";
+        Database::query($sql);
+
     }
 
     /**
@@ -603,6 +612,25 @@ class CourseRecycler
         }
     }
 
+    /**
+     * Recycle selected learning path categories and dissociate learning paths
+     * that are associated with it.
+     */
+    public function recycle_learnpath_categories()
+    {
+        $learningPathTable = Database::get_course_table(TABLE_LP_MAIN);
+        $learningPathCategoryTable = Database::get_course_table(TABLE_LP_CATEGORY);
+        foreach ($this->course->resources[RESOURCE_LEARNPATH_CATEGORY] as $id => $learnpathCategory) {
+            $categoryId = $learnpathCategory->object->getId();
+            // Dissociate learning paths from categories that will be deleted
+            $sql = "UPDATE $learningPathTable SET category_id = 0 WHERE category_id = ".$categoryId;
+            Database::query($sql);
+            $sql = "DELETE FROM $learningPathCategoryTable WHERE iid = ".$categoryId;
+            Database::query($sql);
+        }
+
+    }
+
     /**
      * Delete course description.
      */

+ 1 - 0
src/Chamilo/CourseBundle/Component/CourseCopy/CourseSelectForm.php

@@ -37,6 +37,7 @@ class CourseSelectForm
         $list[RESOURCE_QUIZ] = get_lang('Tests');
         $list[RESOURCE_TEST_CATEGORY] = get_lang('QuestionCategory');
         $list[RESOURCE_LEARNPATH] = get_lang('ToolLearnpath');
+        $list[RESOURCE_LEARNPATH_CATEGORY] = get_lang('LearnpathCategory');
         $list[RESOURCE_SCORM] = 'SCORM';
         $list[RESOURCE_TOOL_INTRO] = get_lang('ToolIntro');
         $list[RESOURCE_SURVEY] = get_lang('Survey');

+ 61 - 0
tests/scripts/delete_items_from_csv.php

@@ -0,0 +1,61 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+exit;
+
+require_once __DIR__ . '/../../main/inc/global.inc.php';
+
+$file = 'delete.csv';
+if (!file_exists($file)) {
+    echo "File $file doesn't exists";
+    exit;
+}
+
+$data = Import::csv_reader($file);
+echo 'Number of lines '.count($data).PHP_EOL;
+$counter = 1;
+foreach ($data as $row) {
+    if (isset($row['SessionID'])) {
+        $sessionId = SessionManager::getSessionIdFromOriginalId($row['SessionID'], 'external_session_id');
+        if (!empty($sessionId)) {
+            $sessionInfo = api_get_session_info($sessionId);
+            if (!empty($sessionInfo)) {
+                $sessionId = $sessionInfo['id'];
+                $sessionName = $sessionInfo['name'];
+                echo "Line: $counter. Session will be deleted: $sessionName #$sessionId ".PHP_EOL;
+                //SessionManager::delete($sessionId, true);
+            } else {
+                echo "Line: $counter. Session not found: $sessionName".PHP_EOL;
+            }
+        }
+    }
+
+    // Course
+    $courseId = isset($row['CourseID']) ? $row['CourseID'] : '';
+    if (!empty($courseId)) {
+        $courseInfo = CourseManager::getCourseInfoFromOriginalId($courseId, 'external_course_id');
+        if (!empty($courseInfo) && isset($courseInfo['id'])) {
+            $courseCode = $courseInfo['code'];
+            $courseId = $courseInfo['id'];
+            CourseManager::delete_course($courseCode);
+            echo "Line: $counter. Course will be deleted: $courseCode #$courseId".PHP_EOL;
+        } else {
+            echo "Line: $counter. Course not found: $courseCode".PHP_EOL;
+        }
+    }
+
+    // User
+    $userName = isset($row['UserName']) ? $row['UserName'] : '';
+    if (!empty($userName)) {
+        $userInfo = api_get_user_info_from_username($userName);
+        if (!empty($userInfo) && isset($userInfo['id'])) {
+            $userId = $userInfo['id'];
+            //UserManager::delete_user($userId);
+            echo "Line: $counter. User will be deleted: $userId".PHP_EOL;
+        } else {
+            echo "Line: $counter. User not found: $userName".PHP_EOL;
+        }
+    }
+
+    $counter++;
+}

+ 34 - 0
tests/scripts/edit_course_html_files.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * Goes through all HTML files of the courses directory and replaces
+ * the first string by the second string.
+ * This is useful when a portal was installed under one URL and then
+ * changed URL (or port), to ensure documents are not pointing to the
+ * previous URL.
+ * This script is designed to be run from the browser, so maybe you
+ * need to move it to an executable folder and change the first require.
+ * @author Yannick Warnier <yannick.warnier@beeznest.com>
+ */
+require __DIR__.'/../../main/inc/global.inc.php';
+api_protect_admin_script();
+
+// Search string
+$search = 'be:8181';
+$replace = 'be';
+
+$dir = api_get_path(SYS_COURSE_PATH);
+$courses = scandir($dir);
+$i = 0;
+foreach ($courses as $courseDir) {
+    if (substr($courseDir, 0, 1) === '.') {
+        continue;
+    }
+    exec('find '.$dir.$courseDir.'/document/ -type f -name "*.html" -exec sed -i '."'s/hn:8181/hn/g' {} +");
+    //print('find '.$dir.$courseDir.'/document/ -type f -name "*.html" -exec sed -i '."'s/hn:8181/hn/g' {} +<br />");
+    $i++;
+    //if ($i == 2) {
+    //    exit;
+    //}
+    echo "Replaced all $search in ".$dir.$courseDir."<br />";
+}
+echo "Done";

+ 85 - 0
tests/scripts/restore_deleted_documents.php

@@ -0,0 +1,85 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * Script to restore some deleted documents
+ */
+
+use Chamilo\CourseBundle\Entity\CDocument;
+use Chamilo\CourseBundle\Entity\CItemProperty;
+
+exit;
+
+require __DIR__.'/../../main/inc/global.inc.php';
+
+api_protect_admin_script();
+
+$em = Database::getManager();
+
+$cId = 0;
+$path = '%_DELETED_%';
+
+//// --->
+
+$currentUserId = api_get_user_id();
+$course = api_get_course_entity($cId);
+$courseDirectory = api_get_path(SYS_COURSE_PATH).$course->getDirectory().'/document';
+
+$documents = $em
+    ->createQuery(
+        'SELECT d FROM ChamiloCourseBundle:CDocument d
+        WHERE d.cId = :cid AND d.path LIKE :path AND d.sessionId = 0'
+    )
+    ->setParameters(['cid' => (int) $cId, 'path' => $path])
+    ->getResult();
+
+header('Content-Type: text/plain');
+
+/** @var CDocument $document */
+foreach ($documents as $document) {
+    $properties = $em
+        ->createQuery(
+            'SELECT i FROM ChamiloCourseBundle:CItemProperty i
+            WHERE i.course = :course AND i.session IS NULL AND i.ref = :ref'
+        )
+        ->setParameters(['course' => $course, 'ref' => $document->getIid()])
+        ->getResult();
+
+    /** @var CItemProperty $property */
+    foreach ($properties as $property) {
+        if (!in_array($property->getLasteditType(), ['DocumentDeleted', 'delete'])) {
+            continue;
+        }
+        switch ($property->getLasteditType()) {
+            case 'DocumentDeleted':
+                echo "Changing 'deleted' log to 'added' log".PHP_EOL;
+                $property
+                    ->setLasteditType('DocumentAdded')
+                    ->setLasteditUserId($currentUserId)
+                    ->setLasteditDate(
+                        api_get_utc_datetime(null, false, true)
+                    )
+                    ->setVisibility(1);
+
+                $em->persist($property);
+                break;
+            case 'delete':
+                echo "Removing delete log".PHP_EOL;
+                $em->remove($property);
+                break;
+        }
+    }
+
+    $filePath = $courseDirectory.$document->getPath();
+    $newPath = str_replace('_DELETED_'.$document->getIid(), '', $document->getPath());
+    $newFilePath = $courseDirectory.$newPath;
+
+    $document->setPath($newPath);
+    $em->flush();
+
+    $renaming = rename($filePath, $newFilePath);
+
+    echo "Renaming $filePath to $newFilePath : ";
+    echo $renaming ? 'y' : 'n';
+    echo PHP_EOL;
+}