Jelajahi Sumber

Merge branch '1.11.x' into minimun_view_time

Julio Montoya 6 tahun lalu
induk
melakukan
1adbf3fa29
92 mengubah file dengan 1913 tambahan dan 1356 penghapusan
  1. 2 3
      .htaccess
  2. 1 0
      .travis.yml
  3. 1 1
      app/Migrations/Schema/V110/Version20150522222222.php
  4. 42 0
      documentation/changelog.html
  5. 20 20
      documentation/installation_guide.html
  6. 6 6
      documentation/installation_guide_es_ES.html
  7. 22 18
      documentation/installation_guide_fr_FR.html
  8. 1 2
      main/admin/configure_homepage.php
  9. 1 1
      main/admin/course_import.php
  10. 29 29
      main/admin/course_user_import.php
  11. 1 1
      main/admin/course_user_import_by_email.php
  12. 1 1
      main/admin/index.php
  13. 2 1
      main/admin/ldap_import_students.php
  14. 116 48
      main/admin/statistics/index.php
  15. 1 1
      main/admin/subscribe_user2course.php
  16. 5 3
      main/admin/user_import.php
  17. 12 2
      main/admin/user_list.php
  18. 1 1
      main/admin/user_update_import.php
  19. 94 34
      main/auth/courses.php
  20. 6 1
      main/auth/courses_categories.php
  21. 7 55
      main/auth/courses_controller.php
  22. 36 19
      main/auth/courses_list.php
  23. 1 1
      main/auth/external_login/functions.inc.php
  24. 1 1
      main/auth/external_login/newUser.php
  25. 2 2
      main/auth/inscription.php
  26. 8 12
      main/course_home/course_home.php
  27. 1 1
      main/course_info/infocours.php
  28. 10 4
      main/cron/import_csv.php
  29. 2 1
      main/exercise/exercise.class.php
  30. 1 1
      main/exercise/exercise_show.php
  31. 62 4
      main/inc/ajax/statistics.ajax.php
  32. 19 4
      main/inc/lib/api.lib.php
  33. 2 72
      main/inc/lib/auth.lib.php
  34. 212 174
      main/inc/lib/course.lib.php
  35. 10 20
      main/inc/lib/display.lib.php
  36. 1 1
      main/inc/lib/events.lib.php
  37. 27 0
      main/inc/lib/plugin.class.php
  38. 30 0
      main/inc/lib/plugin.lib.php
  39. 53 8
      main/inc/lib/sessionmanager.lib.php
  40. 81 5
      main/inc/lib/statistics.lib.php
  41. 1 0
      main/inc/lib/template.lib.php
  42. 2 2
      main/inc/lib/usergroup.lib.php
  43. 26 7
      main/inc/lib/usermanager.lib.php
  44. 35 11
      main/inc/lib/userportal.lib.php
  45. 1 1
      main/inc/lib/webservices/Rest.php
  46. 2 2
      main/inc/local.inc.php
  47. 27 15
      main/install/configuration.dist.php
  48. 30 17
      main/install/install.lib.php
  49. 132 129
      main/lang/english/trad4all.inc.php
  50. 64 62
      main/lang/french/trad4all.inc.php
  51. 163 155
      main/lang/spanish/trad4all.inc.php
  52. 23 0
      main/lp/embed.php
  53. 90 88
      main/lp/learnpath.class.php
  54. 1 2
      main/lp/lp_add_audio.php
  55. 1 2
      main/lp/lp_admin_view.php
  56. 1 2
      main/lp/lp_build.php
  57. 0 2
      main/lp/lp_edit_item.php
  58. 1 2
      main/lp/lp_edit_item_prereq.php
  59. 2 5
      main/lp/lp_view.php
  60. 1 2
      main/lp/lp_view_item.php
  61. 0 4
      main/lp/readout_text.php
  62. 3 2
      main/lp/scorm_api.php
  63. 6 6
      main/social/invitations.php
  64. 1 1
      main/social/personal_data.php
  65. 1 1
      main/template/default/auth/session_catalog.tpl
  66. 42 34
      main/template/default/course_home/about.tpl
  67. 66 61
      main/template/default/learnpath/view.tpl
  68. 1 1
      main/template/default/session/about.tpl
  69. 3 0
      main/template/default/social/personal_data.tpl
  70. 22 22
      main/template/default/tracking/tracking_course_log.tpl
  71. 67 67
      main/template/default/user_portal/classic_courses_with_category.tpl
  72. 13 1
      main/template/default/user_portal/classic_session.tpl
  73. 0 1
      main/template/default/user_portal/grid_courses_with_category.tpl
  74. 13 0
      main/template/default/user_portal/grid_session.tpl
  75. 6 9
      main/tracking/courseLog.php
  76. 5 5
      main/tracking/course_log_tools.php
  77. 1 1
      main/tracking/exams.php
  78. 64 46
      main/user/subscribe_user.php
  79. 2 2
      main/user/user_import.php
  80. 1 1
      main/webservices/cm_webservice_course.php
  81. 2 2
      main/webservices/registration.soap.php
  82. 1 1
      main/webservices/webservice_course.php
  83. 3 0
      plugin/buycourses/admin.php
  84. 3 1
      plugin/buycourses/src/buy_course_plugin.class.php
  85. 0 19
      plugin/buycourses/view/configuration.tpl
  86. 13 0
      plugin/ims_lti/ImsLtiPlugin.php
  87. 1 1
      plugin/search_course/lib/register_course_widget.class.php
  88. 11 0
      plugin/studentfollowup/StudentFollowUpPlugin.php
  89. 4 0
      plugin/whispeakauth/WhispeakAuthPlugin.php
  90. 2 2
      tests/behat/features/course_user_registration.feature
  91. 1 1
      tests/scripts/users_no_course.php
  92. 22 0
      user_portal.php

+ 2 - 3
.htaccess

@@ -32,6 +32,8 @@ RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_ur
 RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
 RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
 
+# Optimize load of custom per-course icons in courses (avoid download_uploaded_files.php)
+RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
 # Course upload files
 RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
 
@@ -41,9 +43,6 @@ RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir
 RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
 RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
 
-# Optimize load of custom per-course icons in courses (avoid download.php)
-RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
-
 # Redirect all courses/ to app/courses/
 RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L]
 

+ 1 - 0
.travis.yml

@@ -23,6 +23,7 @@ php:
   - 7.0
   - 7.1
   - 7.2
+  - 7.3
 
 env:
   global:

+ 1 - 1
app/Migrations/Schema/V110/Version20150522222222.php

@@ -663,7 +663,7 @@ class Version20150522222222 extends AbstractMigrationChamilo
             '',
             'radio',
             'Session',
-            ($value?$value:'false'),
+            ($value?'true':'false'),
             'SessionAdminPermissionsLimitTitle',
             'SessionAdminPermissionsLimitComment',
             null,

+ 42 - 0
documentation/changelog.html

@@ -106,6 +106,48 @@
     <table class="table" id="index">
 
     </table>
+
+    <div class="version" aria-role="article" aria-label="1.11.10">
+        <a name="1.11.10"></a>
+
+        <h1>Chamilo 1.11.10 - ?????,  ??/??/????</h1>
+        <h3>Release notes - summary</h3>
+        <p>Chamilo 1.11.10 is a minor, ...</p>
+        <h3>Release name</h3>
+        <p>...</p>
+        <h3>Security fixes</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h3>Possibly breaking changes</h3>
+        <ul aria-role="log" aria-live="off">
+            <li>[2018-12-12] (<a href="https://github.com/chamilo/chamilo-lms/commit/a681bf55eac1742d1e4fca865f52679286b745b8">a681bf55</a>) <a href="https://github.com/chamilo/chamilo-lms/issues/2708">GH#2708</a> Remove duplicate from limit_session_admin_role configuration setting</li>
+        </ul>
+        <h3>Notable new Features</h3>
+        <h4>For end-users, teachers and Chamilo admins</h4>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h4>For developers and sysadmins</h4>
+        <ul aria-role="log" aria-live="off">
+            <li>[2018-12-05] (<a href="https://github.com/chamilo/chamilo-lms/commit/74964fc204cb80f6a978b3ecb4d448861e928f36">74964fc2</a>) <a href="https://task.beeznest.com/issues/15095">BT#15095</a> Add session_admins_edit_courses_content conf setting</li>
+            <li>[2018-12-11] (<a href="https://github.com/chamilo/chamilo-lms/commit/dbc571c72b73e1a223699c2ed571e84fad65b210">dbc571c7</a>) <a href="https://task.beeznest.com/issues/15095">BT#15095</a> Allow to session admins login as teachers</li>
+        </ul>
+        <h3>Improvements (minor features) and debug</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h3>Stylesheets and theming</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h3>Web services</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h3>Removals</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+        <h3>Known issues</h3>
+        <ul aria-role="log" aria-live="off">
+        </ul>
+    </div>
+
     <div class="version" aria-role="article" aria-label="1.11.8">
     <a name="1.11.8"></a>
 

+ 20 - 20
documentation/installation_guide.html

@@ -716,29 +716,29 @@ If you have issues with files taking a long time to download, make sure you reco
   &lt;Directory "/var/www/chamilo"&gt;
 	RewriteEngine On
 	RewriteCond %{QUERY_STRING} ^id=(.*)$
-	RewriteRule ^certificates/$ certificates/index.php?id=%1 [L]
-	RewriteRule ^courses/([^/]+)/?$ main/course_home/course_home.php?cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/scorm/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/scorm/$2 [QSA,L] 
-	RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
-	RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
-	RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
-	RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
-	RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L] 
-	RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L]
-	RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L]
+    RewriteRule ^certificates/$ certificates/index.php?id=%1 [L]
+    RewriteRule ^courses/([^/]+)/?$ main/course_home/course_home.php?cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/scorm/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/scorm/$2 [QSA,L]
+    RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
+    RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
+    RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
+    RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
+    RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
+    RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L]
+    RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L]
+    RewriteRule ^course/(\d{1,})/about/?$ main/course_info/about.php?course_id=$1 [L]
     RewriteRule ^badge/(\d{1,}) main/badge/issued.php?issue=$1 [L]
-    RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued.php?skill=$1&user=$2 [L]
-    RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued.php?skill=$1&user=$2 [L]
+    RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
+    RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
     RewriteRule ^main/exercice/(.*)$ main/exercise/$1 [QSA,L]
     RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L]
-	RewriteRule ^main/upload/users/(.*)/(.*)/my_files/(.*)$ app/upload/users/$1/$2/my_files/$3 [QSA,L]
-	RewriteRule ^main/admin/$ main/admin/index.php [QSA,L]
-    RewriteRule ^service/(\d+)$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
-	RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
+    RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
+    RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L]
+    RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
     RewriteRule ^(tests|.git) - [F,L,NC]
   &lt;/Directory&gt;
 

+ 6 - 6
documentation/installation_guide_es_ES.html

@@ -868,21 +868,21 @@ servidor web y agregar esta línea. Debería ver una diferencia notable en el ti
   RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
   RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
   RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
+  RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
   RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
   RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
   RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
   RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
-  RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
   RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L]
   RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L]
+  RewriteRule ^course/(\d{1,})/about/?$ main/course_info/about.php?course_id=$1 [L]
   RewriteRule ^badge/(\d{1,}) main/badge/issued.php?issue=$1 [L]
-  RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued.php?skill=$1&user=$2 [L]
-  RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued.php?skill=$1&user=$2 [L]
+  RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
+  RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
   RewriteRule ^main/exercice/(.*)$ main/exercise/$1 [QSA,L]
   RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L]
-  RewriteRule ^main/upload/users/(.*)/(.*)/my_files/(.*)$ app/upload/users/$1/$2/my_files/$3 [QSA,L]
-  RewriteRule ^main/admin/$ main/admin/index.php [QSA,L]
-  RewriteRule ^service/(\d+)$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
+  RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
+  RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L]
   RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
   RewriteRule ^(tests|.git) - [F,L,NC]
 &lt;/Directory&gt;

+ 22 - 18
documentation/installation_guide_fr_FR.html

@@ -770,26 +770,30 @@ ou, si vous travaillez avec Apache 2.4, la syntaxe est légèrement différente
   &lt;Directory "/var/www/chamilo"&gt;
 	RewriteEngine On
 	RewriteCond %{QUERY_STRING} ^id=(.*)$
-	RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
-	RewriteRule ^certificates/$ certificates/index.php?id=%1 [L]
-	RewriteRule ^courses/([^/]+)/$ main/course_home/course_home.php?cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
-	RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
+    RewriteRule ^certificates/$ certificates/index.php?id=%1 [L]
+    RewriteRule ^courses/([^/]+)/?$ main/course_home/course_home.php?cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/index.php$ main/course_home/course_home.php?cDir=$1 [QSA,L]
     RewriteRule ^courses/([^/]+)/scorm/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/scorm/$2 [QSA,L]
-	RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
-	RewriteRule ^courses/([^/]+)/upload/(.*)$ app/courses/$1/upload/$2 [QSA,L]
-	RewriteRule ^courses/([^/]+)/course-pic85x85.png$ app/courses/$1/course-pic85x85.png [QSA,L]
-	RewriteRule ^courses/([^/]+)/course-pic.png$ app/courses/$1/course-pic.png [QSA,L]
-    RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
-	RewriteRule ^main/upload/users/(.*)/(.*)/my_files/(.*)$ app/upload/users/$1/$2/my_files/$3 [QSA,L]
-	RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L]
-    RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued.php?skill=$1&user=$2 [L]
-	RewriteRule ^main/admin/$ main/admin/index.php [QSA,L]
-    RewriteRule ^main/exercice/(.+)$ main/exercise/$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/scorm/(.*)$ main/document/download_scorm.php?doc_url=/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/document/certificates/(.*)$ app/courses/$1/document/certificates/$2 [QSA,L]
+    RewriteRule ^courses/([^/]+)/document/(.*)$ main/document/download.php?doc_url=/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/upload/course_home_icons/(.*([\.js|\.css|\.png|\.jpg|\.jpeg|\.gif]))$ app/courses/$1/upload/course_home_icons/$2 [QSA,L]
+    RewriteRule ^courses/([^/]+)/upload/([^/]+)/(.*)$ main/document/download_uploaded_files.php?code=$1&type=$2&file=$3 [QSA,L]
+    RewriteRule ^courses/([^/]+)/work/(.*)$ main/work/download.php?file=work/$2&cDir=$1 [QSA,L]
+    RewriteRule ^courses/([^/]+)/course-pic85x85.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_source [QSA,L]
+    RewriteRule ^courses/([^/]+)/course-pic.png$ main/inc/ajax/course.ajax.php?a=get_course_image&code=$1&image=course_image_large_source [QSA,L]
+    RewriteRule ^courses/([^/]+)/(.*)$ app/courses/$1/$2 [QSA,L]
+    RewriteRule ^session/(\d{1,})/about/?$ main/session/about.php?session_id=$1 [L]
+    RewriteRule ^course/(\d{1,})/about/?$ main/course_info/about.php?course_id=$1 [L]
+    RewriteRule ^badge/(\d{1,}) main/badge/issued.php?issue=$1 [L]
+    RewriteRule ^skill/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
+    RewriteRule ^badge/(\d{1,})/user/(\d{1,}) main/badge/issued_all.php?skill=$1&user=$2 [L]
+    RewriteRule ^main/exercice/(.*)$ main/exercise/$1 [QSA,L]
     RewriteRule ^main/newscorm/(.*)$ main/lp/$1 [QSA,L]
-	RewriteRule ^courses/(.*)$ app/courses/$1 [QSA,L]
+    RewriteRule ^service/(\d{1,})$ plugin/buycourses/src/service_information.php?service_id=$1 [L]
+    RewriteRule ^lti/os$ plugin/ims_lti/outcome_service.php [L]
+    RewriteRule ^([^/.]+)/?$ user.php?$1 [L]
+    RewriteRule ^(tests|.git) - [F,L,NC]
   &lt;/Directory&gt;
 </pre>
 <h3>Nginx</h3>

+ 1 - 2
main/admin/configure_homepage.php

@@ -1070,14 +1070,13 @@ switch ($action) {
                     </div>
                     <!-- insert link block -->
 
-
                     <div class="panel-group" id="links-block" role="tablist" aria-multiselectable="true">
                         <div class="panel panel-default">
                             <div class="panel-heading" role="tab" id="headingOne">
                                 <h4 class="panel-title">
                                     <a role="button" data-toggle="collapse" data-parent="#links-block"
                                        href="#links-list" aria-expanded="true" aria-controls="links-list">
-                                        <?php echo api_ucfirst(get_lang('General')); ?>
+                                        <?php echo api_ucfirst(get_lang('MenuGeneral')); ?>
                                     </a>
                                 </h4>
                             </div>

+ 1 - 1
main/admin/course_import.php

@@ -133,7 +133,7 @@ function save_courses_data($courses)
         if (!empty($courseInfo)) {
             if (!empty($teacherList)) {
                 foreach ($teacherList as $teacher) {
-                    CourseManager::add_user_to_course(
+                    CourseManager::subscribeUser(
                         $teacher['user_id'],
                         $courseInfo['code'],
                         COURSEMANAGER

+ 29 - 29
main/admin/course_user_import.php

@@ -31,11 +31,8 @@ function validate_data($users_courses)
             // 2.1 Check whethher code has been allready used by this CVS-file.
             if (!isset($coursecodes[$user_course['CourseCode']])) {
                 // 2.1.1 Check whether course with this code exists in the system.
-                $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
-                $sql = "SELECT * FROM $course_table
-                        WHERE code = '".Database::escape_string($user_course['CourseCode'])."'";
-                $res = Database::query($sql);
-                if (Database::num_rows($res) == 0) {
+                $courseInfo = api_get_course_info($user_course['CourseCode']);
+                if (empty($courseInfo)) {
                     $user_course['error'] = get_lang('CodeDoesNotExists');
                     $errors[] = $user_course;
                 } else {
@@ -69,7 +66,6 @@ function validate_data($users_courses)
  */
 function save_data($users_courses)
 {
-    $user_table = Database::get_main_table(TABLE_MAIN_USER);
     $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
     $csv_data = [];
     $inserted_in_course = [];
@@ -78,7 +74,9 @@ function save_data($users_courses)
     foreach ($users_courses as $user_course) {
         if (!in_array($user_course['CourseCode'], array_keys($courseListCache))) {
             $courseInfo = api_get_course_info($user_course['CourseCode']);
-            $courseListCache[$user_course['CourseCode']] = $courseInfo;
+            if ($courseInfo) {
+                $courseListCache[$user_course['CourseCode']] = $courseInfo;
+            }
         } else {
             $courseInfo = $courseListCache[$user_course['CourseCode']];
         }
@@ -87,11 +85,12 @@ function save_data($users_courses)
     }
 
     foreach ($csv_data as $username => $csv_subscriptions) {
-        $sql = "SELECT * FROM $user_table u
-                WHERE u.username = '".Database::escape_string($username)."'";
-        $res = Database::query($sql);
-        $obj = Database::fetch_object($res);
-        $user_id = $obj->user_id;
+        $userInfo = api_get_user_info_from_username($username);
+        if (empty($userInfo)) {
+            continue;
+        }
+
+        $user_id = $userInfo['user_id'];
         $sql = "SELECT * FROM $course_user_table cu
                 WHERE cu.user_id = $user_id AND cu.relation_type <> ".COURSE_RELATION_TYPE_RRHH." ";
         $res = Database::query($sql);
@@ -103,23 +102,28 @@ function save_data($users_courses)
         $to_subscribe = array_diff(array_keys($csv_subscriptions), array_keys($db_subscriptions));
         $to_unsubscribe = array_diff(array_keys($db_subscriptions), array_keys($csv_subscriptions));
 
-        if ($_POST['subscribe']) {
+        if (isset($_POST['subscribe']) && $_POST['subscribe']) {
             foreach ($to_subscribe as $courseId) {
                 $courseInfo = $courseListById[$courseId];
                 $courseCode = $courseInfo['code'];
-
-                CourseManager::subscribe_user(
-                        $user_id,
-                        $courseCode,
-                        $csv_subscriptions[$courseId]
-                    );
-                $inserted_in_course[$courseInfo['code']] = $courseInfo['title'];
+                $result = CourseManager::subscribeUser(
+                    $user_id,
+                    $courseCode,
+                    $csv_subscriptions[$courseId]
+                );
+                if ($result) {
+                    $inserted_in_course[$courseInfo['code']] = $courseInfo['title'];
+                }
             }
         }
 
-        if ($_POST['unsubscribe']) {
+        if (isset($_POST['unsubscribe']) && $_POST['unsubscribe']) {
             foreach ($to_unsubscribe as $courseId) {
-                $courseInfo = $courseListById[$courseId];
+                if (isset($courseListById[$courseId])) {
+                    $courseInfo = $courseListById[$courseId];
+                } else {
+                    $courseInfo = api_get_course_info_by_id($courseId);
+                }
                 $courseCode = $courseInfo['code'];
                 CourseManager::unsubscribe_user($user_id, $courseCode);
             }
@@ -138,7 +142,7 @@ function save_data($users_courses)
  */
 function parse_csv_data($file)
 {
-    $courses = Import :: csvToArray($file);
+    $courses = Import::csvToArray($file);
 
     return $courses;
 }
@@ -176,17 +180,13 @@ if ($form->validate()) {
         $inserted_in_course = save_data($users_courses);
         // Build the alert message in case there were visual codes subscribed to.
         if ($_POST['subscribe']) {
-            $warn = get_lang('UsersSubscribedToBecauseVisualCode').': ';
+            //$warn = get_lang('UsersSubscribedToBecauseVisualCode').': ';
         } else {
             $warn = get_lang('UsersUnsubscribedFromBecauseVisualCode').': ';
         }
 
         if (!empty($inserted_in_course)) {
-            $warn = $warn.' '.get_lang('FileImported');
-            // The users have been inserted in more than one course.
-            foreach ($inserted_in_course as $code => $info) {
-                $warn .= ' '.$info.' ('.$code.') ';
-            }
+            $warn = get_lang('FileImported');
         } else {
             $warn = get_lang('ErrorsWhenImportingFile');
         }

+ 1 - 1
main/admin/course_user_import_by_email.php

@@ -104,7 +104,7 @@ function save_data($users_courses)
                     $course_info = api_get_course_info($course_code);
                     $inserted_in_course[$course_code] = $course_info['title'];
 
-                    CourseManager::subscribe_user(
+                    CourseManager::subscribeUser(
                         $user_id,
                         $course_code,
                         $csv_subscriptions[$course_code]

+ 1 - 1
main/admin/index.php

@@ -122,7 +122,7 @@ if (api_is_platform_admin()) {
     ];
 
     if (api_is_session_admin()) {
-        if (true === api_get_configuration_value('limit_session_admin_role')) {
+        if ('true' === api_get_setting('limit_session_admin_role')) {
             $items = array_filter($items, function (array $item) {
                 $urls = ['user_list.php', 'user_add.php'];
 

+ 2 - 1
main/admin/ldap_import_students.php

@@ -140,9 +140,10 @@ if (empty($annee) && empty($course)) {
     }
     if (!empty($_POST['course'])) {
         foreach ($UserList as $user_id) {
-            CourseManager::add_user_to_course($user_id, $_POST['course']);
+            CourseManager::subscribeUser($user_id, $_POST['course']);
         }
         header('Location: course_information.php?code='.Security::remove_XSS($_POST['course']));
+        exit;
     } else {
         $message = get_lang('NoUserAdded');
         Display::addFlash(Display::return_message($message, 'normal', false));

+ 116 - 48
main/admin/statistics/index.php

@@ -16,54 +16,114 @@ $interbreadcrumb[] = ['url' => '../index.php', 'name' => get_lang('PlatformAdmin
 $report = isset($_REQUEST['report']) ? $_REQUEST['report'] : '';
 $sessionDuration = isset($_GET['session_duration']) ? (int) $_GET['session_duration'] : '';
 
-if ($report == 'recentlogins') {
+if (
+    in_array(
+        $report,
+        ['recentlogins', 'tools', 'courses', 'coursebylanguage', 'users']
+    )
+   ) {
     $htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
-    $htmlHeadXtra[] = '
-    <script>
-    $(document).ready(function() {
-        $.ajax({
-            url: "'.api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=recent_logins&session_duration='.$sessionDuration.'",
-            type: "POST",
-            success: function(data) {
-                Chart.defaults.global.responsive = true;
-                var ctx = document.getElementById("canvas").getContext("2d");
-                var myLoginChart = new Chart(ctx, {
-                    type: "line",
-                    data: data
-                });
-            }
-        });
-    });
-    </script>';
-}
-if ($report == 'tools') {
-    $htmlHeadXtra[] = api_get_js('chartjs/Chart.min.js');
-    $htmlHeadXtra[] = '
-    <script>
-    $(document).ready(function() {
-        $.ajax({
-            url: "'.api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=tools_usage",
-            type: "POST",
-            success: function(data) {
-                Chart.defaults.global.responsive = true;
-                var ctx = document.getElementById("canvas").getContext("2d");
-                var myLoginChart = new Chart(ctx, {
-                    type: "pie",
-                    data: data,
-                    options: {
-                      legend: {
-                        position: "left"
-                      },
-                      title: {
-                        text: "'.get_lang('PlatformToolAccess').'",
-                        display: true
-                      }
-                    }
-                });
-            }
-        });
-    });
-    </script>';
+    // Prepare variables for the JS charts
+    $url = $reportName = $reportType = $reportOptions = '';
+    switch ($report) {
+        case 'recentlogins':
+            $url = api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=recent_logins&session_duration='.$sessionDuration;
+            $reportName = '';
+            $reportType = 'line';
+            $reportOptions = '';
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate($url, $reportType, $reportOptions);
+            break;
+        case 'tools':
+            $url = api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=tools_usage';
+            $reportName = 'PlatformToolAccess';
+            $reportType = 'pie';
+            $reportOptions = '
+                legend: {
+                    position: "left"
+                },
+                title: {
+                    text: "'.get_lang($reportName).'",
+                    display: true
+                },
+                cutoutPercentage: 25
+                ';
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate($url, $reportType, $reportOptions);
+            break;
+        case 'courses':
+            $url = api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=courses';
+            $reportName = 'CountCours';
+            $reportType = 'pie';
+            $reportOptions = '
+                legend: {
+                    position: "left"
+                },
+                title: {
+                    text: "'.get_lang($reportName).'",
+                    display: true
+                },
+                cutoutPercentage: 25
+                ';
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate($url, $reportType, $reportOptions);
+            break;
+         case 'coursebylanguage':
+            $url = api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?a=courses_by_language';
+            $reportName = 'CountCourseByLanguage';
+            $reportType = 'pie';
+            $reportOptions = '
+                legend: {
+                    position: "left"
+                },
+                title: {
+                    text: "'.get_lang($reportName).'",
+                    display: true
+                },
+                cutoutPercentage: 25
+                ';
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate($url, $reportType, $reportOptions);
+            break;
+         case 'users':
+            $invisible = isset($_GET['count_invisible_courses']) ? intval($_GET['count_invisible_courses']) : null;
+            $urlBase = api_get_path(WEB_CODE_PATH).'inc/ajax/statistics.ajax.php?';
+            $url1 = $urlBase.'a=users&count_invisible='.$invisible;
+            $url2 = $urlBase.'a=users_teachers&count_invisible='.$invisible;
+            $url3 = $urlBase.'a=users_students&count_invisible='.$invisible;
+            $reportName1 = get_lang('NumberOfUsers');
+            $reportName2 = get_lang('Teachers');
+            $reportName3 = get_lang('Students');
+            $reportType = 'pie';
+            $reportOptions = '
+                legend: {
+                    position: "left"
+                },
+                title: {
+                    text: "%s",
+                    display: true
+                },
+                cutoutPercentage: 25
+                ';
+            $reportOptions1 = sprintf($reportOptions, $reportName1);
+            $reportOptions2 = sprintf($reportOptions, $reportName2);
+            $reportOptions3 = sprintf($reportOptions, $reportName3);
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate(
+                $url1,
+                $reportType,
+                $reportOptions1,
+                'canvas1'
+            );
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate(
+                $url2,
+                $reportType,
+                $reportOptions2,
+                'canvas2'
+            );
+            $htmlHeadXtra[] = Statistics::getJSChartTemplate(
+                $url3,
+                $reportType,
+                $reportOptions3,
+                'canvas3'
+            );
+            break;
+    }
 }
 
 if ($report == 'user_session') {
@@ -223,6 +283,7 @@ switch ($report) {
 
         break;
     case 'courses':
+        echo '<canvas class="col-md-12" id="canvas" height="300px" style="margin-bottom: 20px"></canvas>';
         // total amount of courses
         foreach ($course_categories as $code => $name) {
             $courses[$name] = Statistics::countCourses($code);
@@ -235,12 +296,19 @@ switch ($report) {
         Statistics::printToolStats();
         break;
     case 'coursebylanguage':
-        Statistics::printCourseByLanguageStats();
+        echo '<canvas class="col-md-12" id="canvas" height="300px" style="margin-bottom: 20px"></canvas>';
+        $result = Statistics::printCourseByLanguageStats();
+        Statistics::printStats(get_lang('CountCourseByLanguage'), $result, true);
         break;
     case 'courselastvisit':
         Statistics::printCourseLastVisit();
         break;
     case 'users':
+        echo '<div class="row">';
+        echo '<div class="col-md-4"><canvas id="canvas1" style="margin-bottom: 20px"></canvas></div>';
+        echo '<div class="col-md-4"><canvas id="canvas2" style="margin-bottom: 20px"></canvas></div>';
+        echo '<div class="col-md-4"><canvas id="canvas3" style="margin-bottom: 20px"></canvas></div>';
+        echo '</div>';
         // total amount of users
         $teachers = $students = [];
         $countInvisible = isset($_GET['count_invisible_courses']) ? intval($_GET['count_invisible_courses']) : null;

+ 1 - 1
main/admin/subscribe_user2course.php

@@ -97,7 +97,7 @@ if (isset($_POST['form_sent']) && $_POST['form_sent']) {
                 foreach ($users as $user_id) {
                     $user = api_get_user_info($user_id);
                     if ($user['status'] != DRH) {
-                        CourseManager::subscribe_user($user_id, $course_code);
+                        CourseManager::subscribeUser($user_id, $course_code);
                     } else {
                         $errorDrh = 1;
                     }

+ 5 - 3
main/admin/user_import.php

@@ -213,9 +213,11 @@ function save_data($users)
             if (isset($user['Courses']) && is_array($user['Courses'])) {
                 foreach ($user['Courses'] as $course) {
                     if (CourseManager::course_exists($course)) {
-                        CourseManager::subscribe_user($user_id, $course, $user['Status']);
-                        $course_info = CourseManager::get_course_information($course);
-                        $inserted_in_course[$course] = $course_info['title'];
+                        $result = CourseManager::subscribeUser($user_id, $course, $user['Status']);
+                        if ($result) {
+                            $course_info = api_get_course_info($course);
+                            $inserted_in_course[$course] = $course_info['title'];
+                        }
                     }
                 }
             }

+ 12 - 2
main/admin/user_list.php

@@ -578,9 +578,19 @@ function modify_filter($user_id, $url_params, $row)
     }
 
     //only allow platform admins to login_as, or session admins only for students (not teachers nor other admins)
-    if (api_is_platform_admin() || (api_is_session_admin() && $current_user_status_label == $statusname[STUDENT])) {
+    $loginAsStatusForSessionAdmins = [$statusname[STUDENT]];
+
+    //except when allow_session_admin_login_as_teacher is enabled, then can login_as teachers also
+    if (api_get_configuration_value('allow_session_admin_login_as_teacher')) {
+        $loginAsStatusForSessionAdmins[] = $statusname[COURSEMANAGER];
+    }
+
+    $sessionAdminCanLoginAs = api_is_session_admin() &&
+        in_array($current_user_status_label, $loginAsStatusForSessionAdmins);
+
+    if (api_is_platform_admin() || $sessionAdminCanLoginAs) {
         if (!$user_is_anonymous) {
-            if (api_global_admin_can_edit_admin($user_id)) {
+            if (api_global_admin_can_edit_admin($user_id, null, $sessionAdminCanLoginAs)) {
                 $result .= '<a href="user_list.php?action=login_as&user_id='.$user_id.'&sec_token='.Security::getTokenFromSession().'">'.
                     Display::return_icon('login_as.png', get_lang('LoginAs')).'</a>&nbsp;';
             } else {

+ 1 - 1
main/admin/user_update_import.php

@@ -199,7 +199,7 @@ function updateUsers($users)
             if (is_array($user['Courses'])) {
                 foreach ($user['Courses'] as $course) {
                     if (CourseManager::course_exists($course)) {
-                        CourseManager::subscribe_user($user_id, $course, $user['Status']);
+                        CourseManager::subscribeUser($user_id, $course, $user['Status']);
                         $course_info = CourseManager::get_course_information($course);
                         $inserted_in_course[$course] = $course_info['title'];
                     }

+ 94 - 34
main/auth/courses.php

@@ -29,9 +29,7 @@ if (api_get_setting('course_catalog_published') !== 'true') {
     api_block_anonymous_users();
 }
 
-$user_can_view_page = false;
-
-//For students
+// For students
 if (api_get_setting('allow_students_to_browse_courses') === 'false') {
     $user_can_view_page = false;
 } else {
@@ -56,6 +54,9 @@ $actions = [
     'subscribe_to_session',
     'search_tag',
     'search_session',
+    'set_collapsable',
+    'subscribe_course_validation',
+    'subscribe_course',
 ];
 
 $action = CoursesAndSessionsCatalog::is(CATALOG_SESSIONS) ? 'display_sessions' : 'display_courses';
@@ -126,14 +127,6 @@ if (isset($_POST['submit_edit_course_category']) &&
     }
 }
 
-// we are deleting a course category
-if ($action == 'deletecoursecategory' && isset($_GET['id'])) {
-    if (!empty($_GET['sec_token']) && $ctok == $_GET['sec_token']) {
-        $get_id_cat = intval($_GET['id']);
-        $courseController->delete_course_category($get_id_cat);
-    }
-}
-
 // We are creating a new user defined course category (= Create Course Category).
 if (isset($_POST['create_course_category']) &&
     isset($_POST['title_course_category']) &&
@@ -159,17 +152,6 @@ if (isset($_REQUEST['search_course'])) {
     }
 }
 
-// Subscribe user to course
-if (isset($_REQUEST['subscribe_course'])) {
-    if (!empty($_GET['sec_token']) && $ctok == $_GET['sec_token']) {
-        $courseController->subscribe_user(
-            $_GET['subscribe_course'],
-            $searchTerm,
-            $categoryCode
-        );
-    }
-}
-
 // We are unsubscribing from a course (=Unsubscribe from course).
 if (isset($_GET['unsubscribe'])) {
     if (!empty($_GET['sec_token']) && $ctok == $_GET['sec_token']) {
@@ -189,14 +171,67 @@ if (isset($_POST['unsubscribe'])) {
 }
 
 switch ($action) {
-    case 'subscribe_user_with_password':
-        $courseController->subscribe_user(
-            isset($_POST['subscribe_user_with_password']) ? $_POST['subscribe_user_with_password'] : '',
-            $searchTerm,
-            isset($_POST['category_code']) ? $_POST['category_code'] : ''
+    case 'deletecoursecategory':
+        // we are deleting a course category
+        if (isset($_GET['id'])) {
+            if (Security::check_token('get')) {
+                $courseController->delete_course_category($_GET['id']);
+                header('Location: '.api_get_self());
+                exit;
+            }
+        }
+        break;
+    case 'subscribe_course':
+        if (api_is_anonymous()) {
+            header('Location: '.api_get_path(WEB_CODE_PATH).'auth/inscription.php?c='.$courseCodeToSubscribe);
+            exit;
+        }
+        $courseCodeToSubscribe = isset($_GET['subscribe_course']) ? Security::remove_XSS($_GET['subscribe_course']) : '';
+        if (Security::check_token('get')) {
+            CourseManager::autoSubscribeToCourse($courseCodeToSubscribe);
+            header('Location: '.api_get_self());
+            exit;
+        }
+        break;
+    case 'subscribe_course_validation':
+        $courseCodeToSubscribe = isset($_GET['subscribe_course']) ? Security::remove_XSS($_GET['subscribe_course']) : '';
+        $courseInfo = api_get_course_info($courseCodeToSubscribe);
+        if (empty($courseInfo)) {
+            header('Location: '.api_get_self());
+            exit;
+        }
+        $message = get_lang('CourseRequiresPassword').' ';
+        $message .= $courseInfo['title'].' ('.$courseInfo['visual_code'].') ';
+
+        $action = api_get_self().
+            '?action=subscribe_course_validation&sec_token='.Security::getTokenFromSession().'&subscribe_course='.$courseCodeToSubscribe;
+        $form = new FormValidator(
+            'subscribe_user_with_password',
+            'post',
+            $action
         );
-        header('Location: '.api_get_self());
-        exit;
+        $form->addHeader($message);
+        $form->addElement('hidden', 'sec_token', Security::getTokenFromSession());
+        $form->addElement('hidden', 'subscribe_user_with_password', $courseInfo['code']);
+        $form->addElement('text', 'course_registration_code');
+        $form->addButtonSave(get_lang('SubmitRegistrationCode'));
+        $content = $form->returnForm();
+
+        if ($form->validate()) {
+            if (sha1($_POST['course_registration_code']) === $courseInfo['registration_code']) {
+                CourseManager::autoSubscribeToCourse($_POST['subscribe_user_with_password']);
+                header('Location: '.api_get_self());
+                exit;
+            } else {
+                Display::addFlash(Display::return_message(get_lang('CourseRegistrationCodeIncorrect')), 'warning');
+                header('Location: '.$action);
+                exit;
+            }
+        }
+
+        $template = new Template(get_lang('Subscribe'), true, true, false, false, false);
+        $template->assign('content', $content);
+        $template->display_one_col_template();
         break;
     case 'createcoursecategory':
         $courseController->categoryList();
@@ -249,7 +284,7 @@ switch ($action) {
 
         $userId = api_get_user_id();
         $confirmed = isset($_GET['confirm']);
-        $sessionId = intval($_GET['session_id']);
+        $sessionId = (int) $_GET['session_id'];
 
         if (empty($userId)) {
             api_not_allowed();
@@ -290,13 +325,13 @@ switch ($action) {
             }
 
             SessionManager::subscribeUsersToSession(
-                $_GET['session_id'],
+                $sessionId,
                 [$userId],
                 SESSION_VISIBLE_READ_ONLY,
                 false
             );
 
-            $coursesList = SessionManager::get_course_list_by_session_id($_GET['session_id']);
+            $coursesList = SessionManager::get_course_list_by_session_id($sessionId);
             $count = count($coursesList);
             $url = '';
 
@@ -306,10 +341,10 @@ switch ($action) {
             } elseif ($count == 1) {
                 // only one course, so redirect directly to this course
                 foreach ($coursesList as $course) {
-                    $url = api_get_path(WEB_COURSE_PATH).$course['directory'].'/index.php?id_session='.intval($_GET['session_id']);
+                    $url = api_get_path(WEB_COURSE_PATH).$course['directory'].'/index.php?id_session='.$sessionId;
                 }
             } else {
-                $url = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.intval($_GET['session_id']);
+                $url = api_get_path(WEB_CODE_PATH).'session/index.php?session_id='.$sessionId;
             }
             header('Location: '.$url);
             exit;
@@ -330,4 +365,29 @@ switch ($action) {
 
         $courseController->sessionListBySearch($limit);
         break;
+    case 'set_collapsable':
+        api_block_anonymous_users();
+
+        if (!api_get_configuration_value('allow_user_course_category_collapsable')) {
+            api_not_allowed(true);
+        }
+
+        $userId = api_get_user_id();
+        $categoryId = isset($_REQUEST['categoryid']) ? (int) $_REQUEST['categoryid'] : 0;
+        $option = isset($_REQUEST['option']) ? (int) $_REQUEST['option'] : 0;
+
+        if (empty($userId) || empty($categoryId)) {
+            api_not_allowed(true);
+        }
+
+        $table = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
+        $sql = "UPDATE $table 
+                SET collapsed = $option
+                WHERE user_id = $userId AND id = $categoryId";
+        Database::query($sql);
+        Display::addFlash(Display::return_message(get_lang('Updated')));
+        $url = api_get_path(WEB_CODE_PATH).'auth/courses.php?action=sortmycourses';
+        header('Location: '.$url);
+        exit;
+        break;
 }

+ 6 - 1
main/auth/courses_categories.php

@@ -503,9 +503,14 @@ function return_already_registered_label($in_status)
 function return_register_button($course, $stok, $code, $search_term)
 {
     $title = get_lang('Subscribe');
+    $action = 'subscribe_course';
+    if (!empty($course['registration_code'])) {
+        $action = 'subscribe_course_validation';
+    }
+
     $html = Display::url(
         Display::returnFontAwesomeIcon('check').' '.$title,
-        api_get_self().'?action=subscribe_course&sec_token='.$stok.
+        api_get_self().'?action='.$action.'&sec_token='.$stok.
         '&subscribe_course='.$course['code'].'&search_term='.$search_term.'&category_code='.$code,
         ['class' => 'btn btn-success btn-sm', 'title' => $title, 'aria-label' => $title]
     );

+ 7 - 55
main/auth/courses_controller.php

@@ -225,58 +225,6 @@ class CoursesController
         $this->view->render();
     }
 
-    /**
-     * Auto user subscription to a course.
-     */
-    public function subscribe_user($course_code, $search_term, $category_code)
-    {
-        $courseInfo = api_get_course_info($course_code);
-
-        if (empty($courseInfo)) {
-            return false;
-        }
-
-        // The course must be open in order to access the auto subscription
-        if (in_array(
-            $courseInfo['visibility'],
-            [
-                COURSE_VISIBILITY_CLOSED,
-                COURSE_VISIBILITY_REGISTERED,
-                COURSE_VISIBILITY_HIDDEN,
-            ]
-        )
-        ) {
-            Display::addFlash(
-                Display::return_message(
-                    get_lang('SubscribingNotAllowed'),
-                    'warning'
-                )
-            );
-        } else {
-            // Redirect to subscription
-            if (api_is_anonymous()) {
-                header('Location: '.api_get_path(WEB_CODE_PATH).'auth/inscription.php?c='.$course_code);
-                exit;
-            }
-            $result = $this->model->subscribe_user($course_code);
-            if (!$result) {
-                Display::addFlash(
-                    Display::return_message(
-                        get_lang('CourseRegistrationCodeIncorrect'),
-                        'warning'
-                    )
-                );
-            } else {
-                Display::addFlash(
-                    Display::return_message($result['message'], 'normal', false)
-                );
-                if (isset($result['content'])) {
-                    Display::addFlash($result['content']);
-                }
-            }
-        }
-    }
-
     /**
      * Create a category
      * render to listing view.
@@ -914,8 +862,10 @@ class CoursesController
                 $catName = $cat->getName();
             }
 
-            $coachId = $session->getGeneralCoach()->getId();
-            $coachName = $session->getGeneralCoach()->getCompleteName();
+            $generalCoach = $session->getGeneralCoach();
+            $coachId = $generalCoach ? $generalCoach->getId() : 0;
+            $coachName = $generalCoach ? $session->getGeneralCoach()->getCompleteName() : '';
+
             $actions = null;
             if (api_is_platform_admin()) {
                 $actions = api_get_path(WEB_CODE_PATH).'session/resume_session.php?id_session='.$session->getId();
@@ -931,7 +881,9 @@ class CoursesController
                 'nbr_courses' => $session->getNbrCourses(),
                 'nbr_users' => $session->getNbrUsers(),
                 'coach_id' => $coachId,
-                'coach_url' => api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId,
+                'coach_url' => $generalCoach
+                    ? api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId
+                    : '',
                 'coach_name' => $coachName,
                 'coach_avatar' => UserManager::getUserPicture(
                     $coachId,

+ 36 - 19
main/auth/courses_list.php

@@ -15,20 +15,19 @@
 api_block_anonymous_users();
 $stok = Security::get_token();
 $courses_without_category = isset($courses_in_category[0]) ? $courses_in_category[0] : null;
-?>
-<!-- Actions: The menu with the different options in cathe course management -->
-<div id="actions" class="actions">
-    <?php if ($action != 'createcoursecategory') {
-    ?>
-	<a href="<?php echo api_get_self(); ?>?action=createcoursecategory">
-        <?php echo Display::return_icon('new_folder.png', get_lang('CreateCourseCategory'), '', '32'); ?></a>
-    <?php
-} ?>
-</div>
-<?php
+echo '<div id="actions" class="actions">';
+if ($action != 'createcoursecategory') {
+    echo '<a href="'.api_get_self().'?action=createcoursecategory">';
+    echo Display::return_icon('new_folder.png', get_lang('CreateCourseCategory'), '', '32');
+    echo '</a>';
+}
+echo '</div>';
+
 if (!empty($message)) {
-        echo Display::return_message($message, 'confirm', false);
-    }
+    echo Display::return_message($message, 'confirm', false);
+}
+
+$allowCollapsable = api_get_configuration_value('allow_user_course_category_collapsable');
 
 // COURSES WITH CATEGORIES
 if (!empty($user_course_categories)) {
@@ -37,6 +36,7 @@ if (!empty($user_course_categories)) {
     foreach ($user_course_categories as $row) {
         echo Display::page_subheader($row['title']);
         echo '<a name="category'.$row['id'].'"></a>';
+        $url = api_get_path(WEB_CODE_PATH).'auth/courses.php?categoryid='.$row['id'].'&sec_token='.$stok;
         if (isset($_GET['categoryid']) && $_GET['categoryid'] == $row['id']) {
             ?>
         <!-- We display the edit form for the category -->
@@ -48,9 +48,20 @@ if (!empty($user_course_categories)) {
         </form>
         <?php
         }
-        $max_category_key = count($user_course_categories);
         if ($action != 'unsubscribe') {
-            ?>
+            if ($allowCollapsable) {
+                if ($row['collapsed'] == 0) {
+                    echo Display::url(
+                        '<i class="fa fa-folder-open"></i>',
+                        $url.'&action=set_collapsable&option=1'
+                    );
+                } else {
+                    echo Display::url(
+                        '<i class="fa fa-folder"></i>',
+                        $url.'&action=set_collapsable&option=0'
+                    );
+                }
+            } ?>
             <a href="courses.php?action=sortmycourses&amp;categoryid=<?php echo $row['id']; ?>&amp;sec_token=<?php echo $stok; ?>#category<?php echo $row['id']; ?>">
             <?php echo Display::display_icon('edit.png', get_lang('Edit'), '', 22); ?>
             </a>
@@ -78,10 +89,16 @@ if (!empty($user_course_categories)) {
             <?php
             } ?>
             <a href="courses.php?action=deletecoursecategory&amp;id=<?php echo $row['id']; ?>&amp;sec_token=<?php echo $stok; ?>">
-            <?php echo Display::display_icon('delete.png', get_lang('Delete'), ['onclick' => "javascript: if (!confirm('".addslashes(api_htmlentities(get_lang("CourseCategoryAbout2bedeleted"), ENT_QUOTES, api_get_system_encoding()))."')) return false;"], 22); ?>
+            <?php echo Display::display_icon(
+                    'delete.png',
+                    get_lang('Delete'),
+                    ['onclick' => "javascript: if (!confirm('".addslashes(api_htmlentities(get_lang('CourseCategoryAbout2bedeleted'), ENT_QUOTES, api_get_system_encoding()))."')) return false;"],
+                    22
+            ); ?>
             </a>
         <?php
         }
+
         $counter++;
         echo '<br /><br />';
         // Show the courses inside this category
@@ -103,7 +120,7 @@ if (!empty($user_course_categories)) {
                 if (api_get_setting('display_coursecode_in_courselist') === 'true' &&
                     api_get_setting('display_teacher_in_courselist') === 'true'
                 ) {
-                    echo " - ";
+                    echo ' - ';
                 }
 
                 if (api_get_setting('display_teacher_in_courselist') === 'true') {
@@ -120,7 +137,7 @@ if (!empty($user_course_categories)) {
                     <input type="hidden" name="sec_token" value="<?php echo $stok; ?>">
                     <input type="hidden" name="course_2_edit_category" value="<?php echo $edit_course; ?>" />
                     <select name="course_categories">
-                    <option value="0"><?php echo get_lang("NoCourseCategory"); ?></option>
+                    <option value="0"><?php echo get_lang('NoCourseCategory'); ?></option>
                     <?php foreach ($user_course_categories as $row) {
                         ?>
                         <option value="<?php echo $row['id']; ?>"><?php echo $row['title']; ?></option>
@@ -221,7 +238,7 @@ if (!empty($courses_without_category)) {
         if (api_get_setting('display_coursecode_in_courselist') === 'true' &&
                 api_get_setting('display_teacher_in_courselist') === 'true'
             ) {
-            echo " - ";
+            echo ' - ';
         }
         if (api_get_setting('display_teacher_in_courselist') === 'true') {
             echo $course['tutor'];

+ 1 - 1
main/auth/external_login/functions.inc.php

@@ -216,7 +216,7 @@ function external_update_user($new_user)
         $autoSubscribe = explode('|', $u['courses']);
         foreach ($autoSubscribe as $code) {
             if (CourseManager::course_exists($code)) {
-                CourseManager::subscribe_user($u['user_id'], $code);
+                CourseManager::subscribeUser($u['user_id'], $code, STUDENT);
             }
         }
     }

+ 1 - 1
main/auth/external_login/newUser.php

@@ -34,7 +34,7 @@ if ($user !== false && ($chamilo_uid = external_add_user($user)) !== false) {
         $autoSubscribe = explode('|', $user['courses']);
         foreach ($autoSubscribe as $code) {
             if (CourseManager::course_exists($code)) {
-                CourseManager::subscribe_user($_user['user_id'], $code);
+                CourseManager::subscribeUser($_user['user_id'], $code);
             }
         }
     }

+ 2 - 2
main/auth/inscription.php

@@ -812,7 +812,7 @@ if ($form->validate()) {
                         ]
                     )
                     ) {
-                        CourseManager::subscribe_user(
+                        CourseManager::subscribeUser(
                             $user_id,
                             $course_info['code']
                         );
@@ -1116,7 +1116,7 @@ if ($form->validate()) {
                         ]
                     )
                     ) {
-                        CourseManager::subscribe_user(
+                        CourseManager::subscribeUser(
                             $user_id,
                             $course_info['code']
                         );

+ 8 - 12
main/course_home/course_home.php

@@ -132,7 +132,7 @@ $isSpecialCourse = CourseManager::isSpecialCourse($courseId);
 
 if ($isSpecialCourse) {
     if (isset($_GET['autoreg']) && $_GET['autoreg'] == 1) {
-        if (CourseManager::subscribe_user($user_id, $course_code, STUDENT)) {
+        if (CourseManager::subscribeUser($user_id, $course_code, STUDENT)) {
             Session::write('is_allowed_in_course', true);
         }
     }
@@ -141,18 +141,14 @@ if ($isSpecialCourse) {
 if (isset($_GET['action']) && $_GET['action'] == 'subscribe') {
     if (Security::check_token('get')) {
         Security::clear_token();
-        $auth = new Auth();
-        $msg = $auth->subscribe_user($course_code);
-        if (CourseManager::is_user_subscribed_in_course($user_id, $course_code)) {
-            Session::write('is_allowed_in_course', true);
-        }
-        if (!empty($msg)) {
-            $show_message .= Display::return_message(
-                get_lang($msg['message']),
-                'info',
-                false
-            );
+        $result = CourseManager::autoSubscribeToCourse($course_code);
+        if ($result) {
+            if (CourseManager::is_user_subscribed_in_course($user_id, $course_code)) {
+                Session::write('is_allowed_in_course', true);
+            }
         }
+        header('Location: '.api_get_self());
+        exit;
     }
 }
 

+ 1 - 1
main/course_info/infocours.php

@@ -129,7 +129,7 @@ $form->applyFilter('department_url', 'html_filter');
 $extra_field = new ExtraField('course');
 
 $extraFieldAdminPermissions = false;
-$showOnlyTheseFields = ['tags', 'video_url', 'course_hours_duration'];
+$showOnlyTheseFields = ['tags', 'video_url', 'course_hours_duration', 'max_subscribed_students'];
 $extra = $extra_field->addElements(
     $form,
     $courseId,

+ 10 - 4
main/cron/import_csv.php

@@ -2419,7 +2419,7 @@ class ImportCsv
                     $userCourseCategory = $courseUserData['user_course_cat'];
                 }
 
-                CourseManager::subscribe_user(
+                $result = CourseManager::subscribeUser(
                     $userId,
                     $courseInfo['code'],
                     $status,
@@ -2427,9 +2427,15 @@ class ImportCsv
                     $userCourseCategory
                 );
 
-                $this->logger->addInfo(
-                    "User $userId added to course ".$courseInfo['code']." with status '$status' with course category: '$userCourseCategory'"
-                );
+                if ($result) {
+                    $this->logger->addInfo(
+                        "User $userId added to course ".$courseInfo['code']." with status '$status' with course category: '$userCourseCategory'"
+                    );
+                } else {
+                    $this->logger->addInfo(
+                        "User $userId was NOT ADDED to course ".$courseInfo['code']." with status '$status' with course category: '$userCourseCategory'"
+                    );
+                }
             }
         }
 

+ 2 - 1
main/exercise/exercise.class.php

@@ -3772,7 +3772,8 @@ class Exercise
                         $str = $answerFromDatabase = Database::result($result, 0, 'answer');
                     }
 
-                    if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
+                    // if ($saved_results == false && strpos($answerFromDatabase, 'font color') !== false) {
+                    if (false) {
                         // the question is encoded like this
                         // [A] B [C] D [E] F::10,10,10@1
                         // number 1 before the "@" means that is a switchable fill in blank question

+ 1 - 1
main/exercise/exercise_show.php

@@ -809,7 +809,7 @@ foreach ($questionList as $questionId) {
                 $formMark->addSelect('marks', get_lang('AssignMarks'), $options);
                 $formMark->display();*/
                 echo '<form name="marksform_'.$questionId.'" method="post" action="">';
-                echo get_lang("AssignMarks");
+                echo get_lang('AssignMarks');
                 echo "&nbsp;<select name='marks' id='select_marks_".$questionId."' class='selectpicker exercise_mark_select'>";
                 $model = ExerciseLib::getCourseScoreModel();
                 if (empty($model)) {

+ 62 - 4
main/inc/ajax/statistics.ajax.php

@@ -145,18 +145,76 @@ switch ($action) {
         echo json_encode($list);
         break;
     case 'tools_usage':
+    case 'courses':
+    case 'courses_by_language':
+    case 'users':
+    case 'users_teachers':
+    case 'users_students':
         // Give a JSON array to the stats page main/admin/statistics/index.php
         // for global tools usage (number of clicks)
         header('Content-type: application/json');
         $list = [];
-        $all = Statistics::getToolsStats();
+        $palette = ChamiloApi::getColorPalette(true, true);
+        if ($action == 'tools_usage') {
+            $statsName = 'Tools';
+            $all = Statistics::getToolsStats();
+        } elseif ($action == 'courses') {
+            $statsName = 'CountCours';
+            $course_categories = Statistics::getCourseCategories();
+            // total amount of courses
+            $all = [];
+            foreach ($course_categories as $code => $name) {
+                $all[$name] = Statistics::countCourses($code);
+            }
+        } elseif ($action == 'courses_by_language') {
+            $statsName = 'CountCourseByLanguage';
+            $all = Statistics::printCourseByLanguageStats();
+            // use slightly different colors than previous chart
+            for ($k = 0; $k < 3; $k++) {
+                $item = array_shift($palette);
+                array_push($palette, $item);
+            }
+        } elseif ($action == 'users') {
+            $statsName = 'NumberOfUsers';
+            $countInvisible = isset($_GET['count_invisible']) ? (int) $_GET['count_invisible'] : null;
+            $all = [
+                get_lang('Teachers') => Statistics::countUsers(COURSEMANAGER, null, $countInvisible),
+                get_lang('Students') => Statistics::countUsers(STUDENT, null, $countInvisible),
+            ];
+        } elseif ($action == 'users_teachers') {
+            $statsName = 'Teachers';
+            $course_categories = Statistics::getCourseCategories();
+            $countInvisible = isset($_GET['count_invisible']) ? (int) $_GET['count_invisible'] : null;
+            $all = [];
+            foreach ($course_categories as $code => $name) {
+                $name = str_replace(get_lang('Department'), "", $name);
+                $all[$name] = Statistics::countUsers(COURSEMANAGER, $code, $countInvisible);
+            }
+            // use slightly different colors than previous chart
+            for ($k = 0; $k < 3; $k++) {
+                $item = array_shift($palette);
+                array_push($palette, $item);
+            }
+        } elseif ($action == 'users_students') {
+            $statsName = 'Students';
+            $course_categories = Statistics::getCourseCategories();
+            $countInvisible = isset($_GET['count_invisible']) ? (int) $_GET['count_invisible'] : null;
+            $all = [];
+            foreach ($course_categories as $code => $name) {
+                $name = str_replace(get_lang('Department'), "", $name);
+                $all[$name] = Statistics::countUsers(STUDENT, $code, $countInvisible);
+            }
+            // use slightly different colors than previous chart
+            for ($k = 0; $k < 6; $k++) {
+                $item = array_shift($palette);
+                array_push($palette, $item);
+            }
+        }
         foreach ($all as $tick => $tock) {
             $list['labels'][] = $tick;
         }
 
-        $palette = ChamiloApi::getColorPalette(true, true);
-
-        $list['datasets'][0]['label'] = get_lang('Tools');
+        $list['datasets'][0]['label'] = get_lang($statsName);
         $list['datasets'][0]['borderColor'] = 'rgba(255,255,255,1)';
 
         $i = 0;

+ 19 - 4
main/inc/lib/api.lib.php

@@ -1631,7 +1631,12 @@ function api_get_user_info(
         $userFromSession = Session::read('_user');
 
         if (isset($userFromSession)) {
-            if ($cacheAvailable === true) {
+            if ($cacheAvailable === true &&
+                (
+                    empty($userFromSession['is_anonymous']) &&
+                    (isset($userFromSession['status']) && $userFromSession['status'] != ANONYMOUS)
+                )
+            ) {
                 $apcVar = api_get_configuration_value('apc_prefix').'userinfo_'.$userFromSession['user_id'];
                 if (apcu_exists($apcVar)) {
                     if ($updateCache) {
@@ -3303,8 +3308,10 @@ function api_is_allowed_to_edit(
     $session_coach = false,
     $check_student_view = true
 ) {
+    $allowSessionAdminEdit = api_get_configuration_value('session_admins_edit_courses_content') === true;
+
     // Admins can edit anything.
-    if (api_is_platform_admin(false)) {
+    if (api_is_platform_admin($allowSessionAdminEdit)) {
         //The student preview was on
         if ($check_student_view && api_is_student_view_active()) {
             return false;
@@ -8269,7 +8276,7 @@ function api_can_login_as($loginAsUserId, $userId = null)
         }
     }
 
-    $userInfo = api_get_user_info($userId);
+    $userInfo = api_get_user_info($loginAsUserId);
     $isDrh = function () use ($loginAsUserId) {
         if (api_is_drh()) {
             if (api_drh_can_access_all_session_content()) {
@@ -8298,7 +8305,15 @@ function api_can_login_as($loginAsUserId, $userId = null)
         return false;
     };
 
-    return api_is_platform_admin() || (api_is_session_admin() && $userInfo['status'] == 5) || $isDrh();
+    $loginAsStatusForSessionAdmins = [STUDENT];
+
+    if (api_get_configuration_value('allow_session_admin_login_as_teacher')) {
+        $loginAsStatusForSessionAdmins[] = COURSEMANAGER;
+    }
+
+    return api_is_platform_admin() ||
+        (api_is_session_admin() && in_array($userInfo['status'], $loginAsStatusForSessionAdmins)) ||
+        $isDrh();
 }
 
 /**

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

@@ -35,7 +35,7 @@ class Auth
 
         // Secondly we select the courses that are in a category (user_course_cat<>0) and
         // sort these according to the sort of the category
-        $user_id = intval($user_id);
+        $user_id = (int) $user_id;
         $sql = "SELECT
                     course.code k,
                     course.visual_code vc,
@@ -51,7 +51,7 @@ class Auth
                 FROM $TABLECOURS course, $TABLECOURSUSER  course_rel_user
                 WHERE
                     course.id = course_rel_user.c_id AND
-                    course_rel_user.relation_type<>".COURSE_RELATION_TYPE_RRHH." AND
+                    course_rel_user.relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
                     course_rel_user.user_id = '".$user_id."' 
                     $avoidCoursesCondition
                 ORDER BY course_rel_user.sort ASC";
@@ -414,74 +414,4 @@ class Auth
 
         return $result;
     }
-
-    /**
-     * Subscribe the user to a given course.
-     *
-     * @param string $course_code Course code
-     *
-     * @return string Message about results
-     */
-    public function subscribe_user($course_code)
-    {
-        $user_id = api_get_user_id();
-        $all_course_information = CourseManager::get_course_information($course_code);
-
-        if ($all_course_information['registration_code'] == '' ||
-            (
-                isset($_POST['course_registration_code']) &&
-                $_POST['course_registration_code'] == $all_course_information['registration_code']
-            )
-        ) {
-            if (api_is_platform_admin()) {
-                $status_user_in_new_course = COURSEMANAGER;
-            } else {
-                $status_user_in_new_course = null;
-            }
-            if (CourseManager::add_user_to_course($user_id, $course_code, $status_user_in_new_course)) {
-                $send = api_get_course_setting('email_alert_to_teacher_on_new_user_in_course', $course_code);
-                if ($send == 1) {
-                    CourseManager::email_to_tutor(
-                        $user_id,
-                        $all_course_information['real_id'],
-                        $send_to_tutor_also = false
-                    );
-                } elseif ($send == 2) {
-                    CourseManager::email_to_tutor(
-                        $user_id,
-                        $all_course_information['real_id'],
-                        $send_to_tutor_also = true
-                    );
-                }
-                $url = Display::url($all_course_information['title'], api_get_course_url($course_code));
-                $message = sprintf(get_lang('EnrollToCourseXSuccessful'), $url);
-            } else {
-                $message = get_lang('ErrorContactPlatformAdmin');
-            }
-
-            return ['message' => $message];
-        } else {
-            if (isset($_POST['course_registration_code']) &&
-                $_POST['course_registration_code'] != $all_course_information['registration_code']
-            ) {
-                return false;
-            }
-            $message = get_lang('CourseRequiresPassword').'<br />';
-            $message .= $all_course_information['title'].' ('.$all_course_information['visual_code'].') ';
-
-            $action = api_get_path(WEB_CODE_PATH)."auth/courses.php?action=subscribe_user_with_password&sec_token=".Security::getTokenFromSession();
-            $form = new FormValidator(
-                'subscribe_user_with_password',
-                'post',
-                $action
-            );
-            $form->addElement('hidden', 'sec_token', Security::getTokenFromSession());
-            $form->addElement('hidden', 'subscribe_user_with_password', $all_course_information['code']);
-            $form->addElement('text', 'course_registration_code');
-            $form->addButtonSave(get_lang('SubmitRegistrationCode'));
-            $content = $form->returnForm();
-
-            return ['message' => $message, 'content' => $content];
-        }
-    }
 }

+ 212 - 174
main/inc/lib/course.lib.php

@@ -371,19 +371,22 @@ class CourseManager
      * @param   mixed   user_id or an array with user ids
      * @param   string  course code
      * @param   int     session id
+     *
+     * @return bool
+     *
      * @assert ('', '') === false
      */
     public static function unsubscribe_user($user_id, $course_code, $session_id = 0)
     {
         if (empty($user_id)) {
-            return;
+            return false;
         }
         if (!is_array($user_id)) {
             $user_id = [$user_id];
         }
 
         if (count($user_id) == 0) {
-            return;
+            return false;
         }
 
         if (!empty($session_id)) {
@@ -392,8 +395,11 @@ class CourseManager
             $session_id = api_get_session_id();
         }
 
-        $userList = [];
+        if (empty($course_code)) {
+            return false;
+        }
 
+        $userList = [];
         // Cleaning the $user_id variable
         if (is_array($user_id)) {
             $new_user_id_list = [];
@@ -404,7 +410,7 @@ class CourseManager
             $userList = $new_user_id_list;
             $user_ids = implode(',', $new_user_id_list);
         } else {
-            $user_ids = intval($user_id);
+            $user_ids = (int) $user_id;
             $userList[] = $user_id;
         }
 
@@ -420,7 +426,6 @@ class CourseManager
         Database::query($sql);
 
         // Erase user student publications (works) in the course - by André Boivin
-
         if (!empty($userList)) {
             require_once api_get_path(SYS_CODE_PATH).'work/work.lib.php';
             foreach ($userList as $userId) {
@@ -550,76 +555,107 @@ class CourseManager
         }
     }
 
+    /**
+     * @param string $courseCode
+     * @param int    $status
+     *
+     * @return bool
+     */
+    public static function autoSubscribeToCourse($courseCode, $status = STUDENT)
+    {
+        $courseInfo = api_get_course_info($courseCode);
+
+        if (empty($courseInfo)) {
+            return false;
+        }
+
+        if (api_is_anonymous()) {
+            return false;
+        }
+
+        if (in_array(
+            $courseInfo['visibility'],
+            [
+                COURSE_VISIBILITY_CLOSED,
+                COURSE_VISIBILITY_REGISTERED,
+                COURSE_VISIBILITY_HIDDEN,
+            ]
+        )
+        ) {
+            Display::addFlash(
+                Display::return_message(
+                    get_lang('SubscribingNotAllowed'),
+                    'warning'
+                )
+            );
+
+            return false;
+        }
+
+        return self::subscribeUser(api_get_user_id(), $courseInfo['code'], $status);
+    }
+
     /**
      * Subscribe a user to a course. No checks are performed here to see if
      * course subscription is allowed.
      *
-     * @param int    $user_id
-     * @param string $course_code
-     * @param int    $status               (STUDENT, COURSEMANAGER, COURSE_ADMIN, NORMAL_COURSE_MEMBER)
-     * @param int    $session_id
+     * @param int    $userId
+     * @param string $courseCode
+     * @param int    $status                 (STUDENT, COURSEMANAGER, COURSE_ADMIN, NORMAL_COURSE_MEMBER)
+     * @param int    $sessionId
      * @param int    $userCourseCategoryId
+     * @param bool   $checkTeacherPermission
      *
      * @return bool True on success, false on failure
      *
-     * @see add_user_to_course
      * @assert ('', '') === false
      */
-    public static function subscribe_user(
-        $user_id,
-        $course_code,
+    public static function subscribeUser(
+        $userId,
+        $courseCode,
         $status = STUDENT,
-        $session_id = 0,
-        $userCourseCategoryId = 0
+        $sessionId = 0,
+        $userCourseCategoryId = 0,
+        $checkTeacherPermission = true
     ) {
-        if ($user_id != strval(intval($user_id))) {
-            return false; //detected possible SQL injection
+        $userId = (int) $userId;
+        $status = (int) $status;
+
+        if (empty($userId) || empty($courseCode)) {
+            return false;
         }
 
-        $courseInfo = api_get_course_info($course_code);
-        $courseId = $courseInfo['real_id'];
-        $courseCode = $courseInfo['code'];
-        $userCourseCategoryId = intval($userCourseCategoryId);
+        $courseInfo = api_get_course_info($courseCode);
+
+        if (empty($courseInfo)) {
+            Display::addFlash(Display::return_message(get_lang('CourseDoesNotExist'), 'warning'));
 
-        if (empty($user_id) || empty($courseCode)) {
             return false;
         }
 
-        if (!empty($session_id)) {
-            $session_id = intval($session_id);
-        } else {
-            $session_id = api_get_session_id();
-        }
+        $userInfo = api_get_user_info($userId);
 
-        $status = ($status == STUDENT || $status == COURSEMANAGER) ? $status : STUDENT;
+        if (empty($userInfo)) {
+            Display::addFlash(Display::return_message(get_lang('UserDoesNotExist'), 'warning'));
 
-        // A preliminary check whether the user has bben already registered on the platform.
-        $sql = "SELECT status FROM ".Database::get_main_table(TABLE_MAIN_USER)."
-                WHERE user_id = $user_id";
-        if (Database::num_rows(Database::query($sql)) == 0) {
-            return false; // The user has not been registered to the platform.
+            return false;
         }
 
-        // Check whether the user has not been already subscribed to the course.
-        $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."                    
-                WHERE 
-                    user_id = $user_id AND 
-                    relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND 
-                    c_id = $courseId
-                ";
-        if (empty($session_id)) {
-            if (Database::num_rows(Database::query($sql)) > 0) {
-                // The user has been already subscribed to the course.
-                return false;
-            }
-        }
+        $courseId = $courseInfo['real_id'];
+        $courseCode = $courseInfo['code'];
+        $userCourseCategoryId = (int) $userCourseCategoryId;
 
-        if (!empty($session_id)) {
+        $sessionId = empty($sessionId) ? api_get_session_id() : (int) $sessionId;
+        $status = $status === STUDENT || $status === COURSEMANAGER ? $status : STUDENT;
+        $courseUserTable = Database::get_main_table(TABLE_MAIN_COURSE_USER);
+
+        if (!empty($sessionId)) {
             SessionManager::subscribe_users_to_session_course(
-                [$user_id],
-                $session_id,
+                [$userId],
+                $sessionId,
                 $courseCode
             );
+
             // Add event to the system log
             Event::addEvent(
                 LOG_SUBSCRIBE_USER_TO_COURSE,
@@ -628,48 +664,131 @@ class CourseManager
                 api_get_utc_datetime(),
                 api_get_user_id(),
                 $courseId,
-                $session_id
+                $sessionId
             );
-            $user_info = api_get_user_info($user_id);
             Event::addEvent(
                 LOG_SUBSCRIBE_USER_TO_COURSE,
                 LOG_USER_OBJECT,
-                $user_info,
+                $userInfo,
                 api_get_utc_datetime(),
                 api_get_user_id(),
                 $courseId,
-                $session_id
+                $sessionId
             );
+
+            return true;
         } else {
-            self::add_user_to_course(
-                $user_id,
-                $courseCode,
-                $status,
-                $userCourseCategoryId
-            );
+            // Check whether the user has not been already subscribed to the course.
+            $sql = "SELECT * FROM ".Database::get_main_table(TABLE_MAIN_COURSE_USER)."                    
+                    WHERE 
+                        user_id = $userId AND 
+                        relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND 
+                        c_id = $courseId
+                    ";
+            if (Database::num_rows(Database::query($sql)) > 0) {
+                Display::addFlash(Display::return_message(get_lang('AlreadyRegisteredToCourse'), 'warning'));
 
-            // Add event to the system log
-            Event::addEvent(
-                LOG_SUBSCRIBE_USER_TO_COURSE,
-                LOG_COURSE_CODE,
-                $courseCode,
-                api_get_utc_datetime(),
-                api_get_user_id(),
-                $courseId
-            );
+                return false;
+            }
 
-            $user_info = api_get_user_info($user_id);
-            Event::addEvent(
-                LOG_SUBSCRIBE_USER_TO_COURSE,
-                LOG_USER_OBJECT,
-                $user_info,
-                api_get_utc_datetime(),
-                api_get_user_id(),
-                $courseId
-            );
-        }
+            if ($checkTeacherPermission && !api_is_course_admin()) {
+                // Check in advance whether subscription is allowed or not for this course.
+                if ((int) $courseInfo['subscribe'] === SUBSCRIBE_NOT_ALLOWED) {
+                    Display::addFlash(Display::return_message(get_lang('SubscriptionNotAllowed'), 'warning'));
 
-        return true;
+                    return false;
+                }
+            }
+
+            if ($status === STUDENT) {
+                // Check if max students per course extra field is set
+                $extraFieldValue = new ExtraFieldValue('course');
+                $value = $extraFieldValue->get_values_by_handler_and_field_variable($courseId, 'max_subscribed_students');
+                if (!empty($value) && isset($value['value'])) {
+                    $maxStudents = $value['value'];
+                    if ($maxStudents !== '') {
+                        $maxStudents = (int) $maxStudents;
+                        $count = self::get_user_list_from_course_code(
+                            $courseCode,
+                            0,
+                            null,
+                            null,
+                            STUDENT,
+                            true,
+                            false
+                        );
+
+                        if ($count >= $maxStudents) {
+                            Display::addFlash(Display::return_message(get_lang('MaxNumberSubscribedStudentsReached'), 'warning'));
+
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            $maxSort = api_max_sort_value('0', $userId);
+            $params = [
+                'c_id' => $courseId,
+                'user_id' => $userId,
+                'status' => $status,
+                'sort' => $maxSort + 1,
+                'relation_type' => 0,
+                'user_course_cat' => (int) $userCourseCategoryId,
+            ];
+            $insertId = Database::insert($courseUserTable, $params);
+
+            if ($insertId) {
+                Display::addFlash(
+                    Display::return_message(
+                        sprintf(
+                            get_lang('UserXAddedToCourseX'),
+                            $userInfo['complete_name_with_username'],
+                            $courseInfo['title']
+                        )
+                    )
+                );
+
+                $send = api_get_course_setting('email_alert_to_teacher_on_new_user_in_course', $courseCode);
+
+                if ($send == 1) {
+                    self::email_to_tutor(
+                        $userId,
+                        $courseInfo['real_id'],
+                        false
+                    );
+                } elseif ($send == 2) {
+                    self::email_to_tutor(
+                        $userId,
+                        $courseInfo['real_id'],
+                        true
+                    );
+                }
+
+                // Add event to the system log
+                Event::addEvent(
+                    LOG_SUBSCRIBE_USER_TO_COURSE,
+                    LOG_COURSE_CODE,
+                    $courseCode,
+                    api_get_utc_datetime(),
+                    api_get_user_id(),
+                    $courseId
+                );
+
+                Event::addEvent(
+                    LOG_SUBSCRIBE_USER_TO_COURSE,
+                    LOG_USER_OBJECT,
+                    $userInfo,
+                    api_get_utc_datetime(),
+                    api_get_user_id(),
+                    $courseId
+                );
+
+                return true;
+            }
+
+            return false;
+        }
     }
 
     /**
@@ -733,94 +852,6 @@ class CourseManager
         }
     }
 
-    /**
-     * Subscribe a user $user_id to a course defined by $courseCode.
-     *
-     * @author Hugues Peeters
-     * @author Roan Embrechts
-     *
-     * @param int    $user_id                the id of the user
-     * @param string $courseCode             the course code
-     * @param int    $status                 (optional) The user's status in the course
-     * @param int    $userCourseCategoryId   The user category in which this subscription will be classified
-     * @param bool   $checkTeacherPermission
-     *
-     * @return false|string true if subscription succeeds, boolean false otherwise
-     * @assert ('', '') === false
-     */
-    public static function add_user_to_course(
-        $user_id,
-        $courseCode,
-        $status = STUDENT,
-        $userCourseCategoryId = 0,
-        $checkTeacherPermission = true
-    ) {
-        $debug = false;
-        $user_table = Database::get_main_table(TABLE_MAIN_USER);
-        $course_table = Database::get_main_table(TABLE_MAIN_COURSE);
-        $course_user_table = Database::get_main_table(TABLE_MAIN_COURSE_USER);
-
-        $status = ($status == STUDENT || $status == COURSEMANAGER) ? $status : STUDENT;
-        if (empty($user_id) || empty($courseCode) || ($user_id != strval(intval($user_id)))) {
-            return false;
-        }
-
-        $courseCode = Database::escape_string($courseCode);
-        $courseInfo = api_get_course_info($courseCode);
-        $courseId = $courseInfo['real_id'];
-
-        // Check in advance whether the user has already been registered on the platform.
-        $sql = "SELECT status FROM $user_table WHERE user_id = $user_id ";
-        if (Database::num_rows(Database::query($sql)) == 0) {
-            if ($debug) {
-                error_log('The user has not been registered to the platform');
-            }
-
-            return false; // The user has not been registered to the platform.
-        }
-
-        // Check whether the user has already been subscribed to this course.
-        $sql = "SELECT * FROM $course_user_table
-                WHERE
-                    user_id = $user_id AND
-                    relation_type <> ".COURSE_RELATION_TYPE_RRHH." AND
-                    c_id = $courseId";
-        if (Database::num_rows(Database::query($sql)) > 0) {
-            if ($debug) {
-                error_log('The user has been already subscribed to the course');
-            }
-
-            return false; // The user has been subscribed to the course.
-        }
-
-        if ($checkTeacherPermission && !api_is_course_admin()) {
-            // Check in advance whether subscription is allowed or not for this course.
-            $sql = "SELECT code, visibility FROM $course_table
-                    WHERE id = $courseId AND subscribe = '".SUBSCRIBE_NOT_ALLOWED."'";
-            if (Database::num_rows(Database::query($sql)) > 0) {
-                if ($debug) {
-                    error_log('Subscription is not allowed for this course');
-                }
-
-                return false; // Subscription is not allowed for this course.
-            }
-        }
-
-        // Ok, subscribe the user.
-        $max_sort = api_max_sort_value('0', $user_id);
-        $params = [
-            'c_id' => $courseId,
-            'user_id' => $user_id,
-            'status' => $status,
-            'sort' => $max_sort + 1,
-            'relation_type' => 0,
-            'user_course_cat' => (int) $userCourseCategoryId,
-        ];
-        $insertId = Database::insert($course_user_table, $params);
-
-        return $insertId;
-    }
-
     /**
      * Add the user $userId visibility to the course $courseCode in the catalogue.
      *
@@ -2381,6 +2412,9 @@ class CourseManager
             // To prevent fK mix up on some tables
             GroupManager::deleteAllGroupsFromCourse($courseId);
 
+            $app_plugin = new AppPlugin();
+            $app_plugin->performActionsWhenDeletingItem('course', $courseId);
+
             // Delete the course from the database
             $sql = "DELETE FROM $table_course WHERE id = $courseId";
             Database::query($sql);
@@ -2570,10 +2604,8 @@ class CourseManager
      */
     public static function email_to_tutor($user_id, $courseId, $send_to_tutor_also = false)
     {
-        if ($user_id != strval(intval($user_id))) {
-            return false;
-        }
-        $courseId = intval($courseId);
+        $user_id = (int) $user_id;
+        $courseId = (int) $courseId;
         $information = api_get_course_info_by_id($courseId);
         $course_code = $information['code'];
         $student = api_get_user_info($user_id);
@@ -2586,9 +2618,9 @@ class CourseManager
         //if ($send_to_tutor_also = true)
         // Proposed change:
         if ($send_to_tutor_also) {
-            $sql .= " AND is_tutor = 1";
+            $sql .= ' AND is_tutor = 1';
         } else {
-            $sql .= " AND status = 1";
+            $sql .= ' AND status = 1';
         }
 
         $result = Database::query($sql);
@@ -3802,7 +3834,7 @@ class CourseManager
         }
         // Step 1: We get all the categories of the user
         $table = Database::get_main_table(TABLE_USER_COURSE_CATEGORY);
-        $sql = "SELECT id, title FROM $table
+        $sql = "SELECT * FROM $table
                 WHERE user_id = '".$user_id."'
                 ORDER BY sort ASC";
 
@@ -3811,6 +3843,7 @@ class CourseManager
             'in_category' => [],
             'not_category' => [],
         ];
+        $collapsable = api_get_configuration_value('allow_user_course_category_collapsable');
         while ($row = Database::fetch_array($result)) {
             // We simply display the title of the category.
             $courseInCategory = self::returnCoursesCategories(
@@ -3820,9 +3853,14 @@ class CourseManager
                 $useUserLanguageFilterIfAvailable
             );
 
+            $collapsed = 0;
+            if ($collapsable) {
+                $collapsed = isset($row['collapsed']) && $row['collapsed'] ? 1 : 0;
+            }
             $params = [
                 'id_category' => $row['id'],
                 'title_category' => $row['title'],
+                'collapsed' => $collapsed,
                 'courses' => $courseInCategory,
             ];
             $listItems['in_category'][] = $params;

+ 10 - 20
main/inc/lib/display.lib.php

@@ -1691,27 +1691,20 @@ class Display
         }
         $output = [];
         if (!$nosession) {
-            $main_user_table = Database::get_main_table(TABLE_MAIN_USER);
-            $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
-            // Request for the name of the general coach
-            $sql = 'SELECT tu.lastname, tu.firstname, ts.*
-                    FROM '.$tbl_session.' ts
-                    LEFT JOIN '.$main_user_table.' tu
-                    ON ts.id_coach = tu.user_id
-                    WHERE ts.id = '.intval($session_id);
-            $rs = Database::query($sql);
-            $session_info = Database::store_result($rs, 'ASSOC');
-            $session_info = $session_info[0];
+            $session_info = api_get_session_info($session_id);
+            $coachInfo = [];
+            if (!empty($session['id_coach'])) {
+                $coachInfo = api_get_user_info($session['id_coach']);
+            }
 
             $session = [];
             $session['category_id'] = $session_info['session_category_id'];
             $session['title'] = $session_info['name'];
             $session['id_coach'] = $session_info['id_coach'];
-            $session['coach'] = '';
             $session['dates'] = '';
-
-            if (api_get_setting('show_session_coach') === 'true') {
-                $session['coach'] = get_lang('GeneralCoach').': '.api_get_person_name($session_info['firstname'], $session_info['lastname']);
+            $session['coach'] = '';
+            if (api_get_setting('show_session_coach') === 'true' && isset($coachInfo['complete_name'])) {
+                $session['coach'] = get_lang('GeneralCoach').': '.$coachInfo['complete_name'];
             }
 
             if (($session_info['access_end_date'] == '0000-00-00 00:00:00' &&
@@ -1728,11 +1721,8 @@ class Display
             } else {
                 $dates = SessionManager::parseSessionDates($session_info, true);
                 $session['dates'] = $dates['access'];
-                if (api_get_setting('show_session_coach') === 'true') {
-                    $session['coach'] = api_get_person_name(
-                        $session_info['firstname'],
-                        $session_info['lastname']
-                    );
+                if (api_get_setting('show_session_coach') === 'true' && isset($coachInfo['complete_name'])) {
+                    $session['coach'] = $coachInfo['complete_name'];
                 }
                 $active = $date_start <= $now && $date_end >= $now;
             }

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

@@ -89,7 +89,7 @@ class Event
             $autoSubscribe = explode('|', $autoSubscribe);
             foreach ($autoSubscribe as $code) {
                 if (CourseManager::course_exists($code)) {
-                    CourseManager::subscribe_user($userId, $code);
+                    CourseManager::subscribeUser($userId, $code);
                 }
             }
         }

+ 27 - 0
main/inc/lib/plugin.class.php

@@ -933,6 +933,33 @@ class Plugin
         $this->hasPersonalEvents = $value;
     }
 
+    /**
+     * Overwrite to perform some actions when deleting a user.
+     *
+     * @param int $userId
+     */
+    public function doWhenDeletingUser($userId)
+    {
+    }
+
+    /**
+     * Overwrite to perform some actions when deleting a course.
+     *
+     * @param int $courseId
+     */
+    public function doWhenDeletingCourse($courseId)
+    {
+    }
+
+    /**
+     * Overwrite to perform some actions when deleting a session.
+     *
+     * @param int $sessionId
+     */
+    public function doWhenDeletingSession($sessionId)
+    {
+    }
+
     /**
      * Add an link for a course tool.
      *

+ 30 - 0
main/inc/lib/plugin.lib.php

@@ -536,6 +536,36 @@ class AppPlugin
         }
     }
 
+    /**
+     * Trigger for Plugin::doWhenDeleting[Item] functions.
+     *
+     * @param string $itemType
+     * @param int    $itemId
+     */
+    public function performActionsWhenDeletingItem($itemType, $itemId)
+    {
+        $pluginList = $this->getInstalledPluginListObject();
+
+        if (empty($pluginList)) {
+            return;
+        }
+
+        /** @var Plugin $pluginObj */
+        foreach ($pluginList as $pluginObj) {
+            switch ($itemType) {
+                case 'course':
+                    $pluginObj->doWhenDeletingCourse($itemId);
+                    break;
+                case 'session':
+                    $pluginObj->doWhenDeletingSession($itemId);
+                    break;
+                case 'user':
+                    $pluginObj->doWhenDeletingUser($itemId);
+                    break;
+            }
+        }
+    }
+
     /**
      * Add the course settings to the course settings form.
      *

+ 53 - 8
main/inc/lib/sessionmanager.lib.php

@@ -1808,6 +1808,9 @@ class SessionManager
         $sql = "UPDATE $ticket SET session_id = NULL WHERE session_id IN ($id_checked)";
         Database::query($sql);
 
+        $app_plugin = new AppPlugin();
+        $app_plugin->performActionsWhenDeletingItem('session', $id_checked);
+
         $sql = "DELETE FROM $tbl_session WHERE id IN ($id_checked)";
         Database::query($sql);
 
@@ -2316,11 +2319,11 @@ class SessionManager
             return false;
         }
 
-        $session_id = intval($session_id);
+        $session_id = (int) $session_id;
+        $session_visibility = (int) $session_visibility;
         $course_code = Database::escape_string($course_code);
         $courseInfo = api_get_course_info($course_code);
         $courseId = $courseInfo['real_id'];
-        $session_visibility = intval($session_visibility);
 
         if ($removeUsersNotInList) {
             $currentUsers = self::getUsersByCourseSession($session_id, $courseInfo, 0);
@@ -2344,7 +2347,7 @@ class SessionManager
 
         $nbr_users = 0;
         foreach ($user_list as $enreg_user) {
-            $enreg_user = intval($enreg_user);
+            $enreg_user = (int) $enreg_user;
             // Checking if user exists in session - course - user table.
             $sql = "SELECT count(user_id) as count
                     FROM $tbl_session_rel_course_rel_user
@@ -3404,7 +3407,7 @@ class SessionManager
         $noCoach = false
     ) {
         // Definition of variables
-        $userId = intval($userId);
+        $userId = (int) $userId;
 
         $sessionId = !empty($sessionId) ? intval($sessionId) : api_get_session_id();
         $courseId = !empty($courseId) ? intval($courseId) : api_get_course_id();
@@ -5505,7 +5508,7 @@ SQL;
                                         $userCourseCategory = $courseUserData['user_course_cat'];
                                     }
 
-                                    CourseManager::subscribe_user(
+                                    CourseManager::subscribeUser(
                                         $teacherToAdd,
                                         $course_code,
                                         COURSEMANAGER,
@@ -5628,7 +5631,7 @@ SQL;
                                             $userCourseCategory = $courseUserData['user_course_cat'];
                                         }
 
-                                        CourseManager::subscribe_user(
+                                        CourseManager::subscribeUser(
                                             $teacherId,
                                             $course_code,
                                             COURSEMANAGER,
@@ -6851,8 +6854,8 @@ SQL;
      */
     public static function getUserSession($userId, $sessionId)
     {
-        $userId = intval($userId);
-        $sessionId = intval($sessionId);
+        $userId = (int) $userId;
+        $sessionId = (int) $sessionId;
 
         if (empty($userId) || empty($sessionId)) {
             return false;
@@ -9123,6 +9126,48 @@ SQL;
         return $courseIds;
     }
 
+    /**
+     * @param int             $userId
+     * @param int             $sessionId
+     * @param ExtraFieldValue $extraFieldValue
+     * @param string          $collapsableLink
+     *
+     * @return array
+     */
+    public static function getCollapsableData($userId, $sessionId, $extraFieldValue, $collapsableLink)
+    {
+        $collapsed = 0;
+
+        // Get default collapsed value in extra field
+        $value = $extraFieldValue->get_values_by_handler_and_field_variable($sessionId, 'collapsed');
+        if (!empty($value) && isset($value['value'])) {
+            $collapsed = $value['value'];
+        }
+
+        $userRelSession = Sessionmanager::getUserSession($userId, $sessionId);
+        if ($userRelSession) {
+            if (isset($userRelSession['collapsed']) && $userRelSession['collapsed'] != '') {
+                $collapsed = $userRelSession['collapsed'];
+            }
+        } else {
+            return ['collapsed' => $collapsed, 'collapsable_link' => '&nbsp;'];
+        }
+
+        $link = $collapsableLink.'&session_id='.$sessionId.'&value=1';
+        $image = '<i class="fa fa-folder-open"></i>';
+        if ($collapsed == 1) {
+            $link = $collapsableLink.'&session_id='.$sessionId.'&value=0';
+            $image = '<i class="fa fa-folder"></i>';
+        }
+
+        $link = Display::url(
+            $image,
+            $link
+        );
+
+        return ['collapsed' => $collapsed, 'collapsable_link' => $link];
+    }
+
     /**
      * @param int $id
      *

+ 81 - 5
main/inc/lib/statistics.lib.php

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

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

@@ -139,6 +139,7 @@ class Template
 
         // Twig filters setup
         $filters = [
+            'var_dump',
             'get_plugin_lang',
             'get_lang',
             'api_get_path',

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

@@ -881,7 +881,7 @@ class UserGroup extends Model
                 if ($course_info) {
                     if (!empty($user_list)) {
                         foreach ($user_list as $user_id) {
-                            CourseManager::subscribe_user(
+                            CourseManager::subscribeUser(
                                 $user_id,
                                 $course_info['code']
                             );
@@ -1031,7 +1031,7 @@ class UserGroup extends Model
                 if (!empty($course_list)) {
                     foreach ($course_list as $course_id) {
                         $course_info = api_get_course_info_by_id($course_id);
-                        CourseManager::subscribe_user($user_id, $course_info['code']);
+                        CourseManager::subscribeUser($user_id, $course_info['code']);
                     }
                 }
                 $params = [

+ 26 - 7
main/inc/lib/usermanager.lib.php

@@ -917,6 +917,9 @@ class UserManager
             Database::query($sql);
         }
 
+        $app_plugin = new AppPlugin();
+        $app_plugin->performActionsWhenDeletingItem('user', $user_id);
+
         // Delete user from database
         $sql = "DELETE FROM $table_user WHERE id = '".$user_id."'";
         Database::query($sql);
@@ -3040,8 +3043,8 @@ class UserManager
         // sessions, BT#14115) but executing a similar query twice and grouping
         // the results afterwards in PHP takes about 1/1000th of the time
         // (0.1s + 0.0s) for the same set of data, so we do it this way...
-        $dqlStudent = $dql." WHERE scu.user = :user AND url.accessUrlId = :url ";
-        $dqlCoach = $dql." WHERE s.generalCoach = :user AND url.accessUrlId = :url ";
+        $dqlStudent = $dql.' WHERE scu.user = :user AND url.accessUrlId = :url ';
+        $dqlCoach = $dql.' WHERE s.generalCoach = :user AND url.accessUrlId = :url ';
 
         // Default order
         $order = 'ORDER BY sc.name, s.name';
@@ -3071,7 +3074,7 @@ class UserManager
                     if ($orderSetting == 'asc') {
                         // Put null values at the end
                         // https://stackoverflow.com/questions/12652034/how-can-i-order-by-null-in-dql
-                        $order = " ORDER BY _isFieldNull asc, s.accessEndDate asc";
+                        $order = ' ORDER BY _isFieldNull asc, s.accessEndDate asc';
                     }
                     break;
             }
@@ -3109,6 +3112,10 @@ class UserManager
             $sessionData[$row['id']] = $row;
         }
 
+        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
+        $extraField = new ExtraFieldValue('session');
+        $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
+
         $categories = [];
         foreach ($sessionData as $row) {
             $session_id = $row['id'];
@@ -3192,10 +3199,7 @@ class UserManager
                         $ignore_visibility_for_admins
                     );
 
-                    $courseIsVisible = !in_array(
-                        $course['visibility'],
-                        $closedVisibilityList
-                    );
+                    $courseIsVisible = !in_array($course['visibility'], $closedVisibilityList);
                     if ($courseIsVisible === false || $sessionCourseVisibility == SESSION_INVISIBLE) {
                         $blockedCourseCount++;
                     }
@@ -3220,6 +3224,19 @@ class UserManager
                     }
             }
 
+            $collapsed = '';
+            $collapsedAction = '';
+            if ($collapsable) {
+                $collapsableData = Sessionmanager::getCollapsableData(
+                    $user_id,
+                    $session_id,
+                    $extraField,
+                    $collapsableLink
+                );
+                $collapsed = $collapsableData['collapsed'];
+                $collapsedAction = $collapsableData['collapsable_link'];
+            }
+
             $categories[$row['session_category_id']]['sessions'][] = [
                 'session_name' => $row['name'],
                 'session_id' => $row['id'],
@@ -3228,6 +3245,8 @@ class UserManager
                 'coach_access_start_date' => $row['coach_access_start_date'] ? $row['coach_access_start_date']->format('Y-m-d H:i:s') : null,
                 'coach_access_end_date' => $row['coach_access_end_date'] ? $row['coach_access_end_date']->format('Y-m-d H:i:s') : null,
                 'courses' => $courseList,
+                'collapsed' => $collapsed,
+                'collapsable_link' => $collapsedAction,
             ];
         }
 

+ 35 - 11
main/inc/lib/userportal.lib.php

@@ -1339,15 +1339,10 @@ class IndexManager
 
             if ($specialCourses) {
                 if ($categoryCodeFilter) {
-                    $specialCourses = self::filterByCategory(
-                        $specialCourses,
-                        $categoryCodeFilter
-                    );
+                    $specialCourses = self::filterByCategory($specialCourses, $categoryCodeFilter);
                 }
                 $this->tpl->assign('courses', $specialCourses);
-                $specialCourseList = $this->tpl->fetch(
-                    $this->tpl->get_template($coursesWithoutCategoryTemplate)
-                );
+                $specialCourseList = $this->tpl->fetch($this->tpl->get_template($coursesWithoutCategoryTemplate));
                 $courseCompleteList = array_merge($courseCompleteList, $specialCourses);
             }
 
@@ -1385,6 +1380,14 @@ class IndexManager
 
         $sessions_with_category = '';
         $sessions_with_no_category = '';
+
+        $collapsable = api_get_configuration_value('allow_user_session_collapsable');
+        $collapsableLink = '';
+        if ($collapsable) {
+            $collapsableLink = api_get_path(WEB_PATH).'user_portal.php?action=collapse_session';
+        }
+
+        $extraFieldValue = new ExtraFieldValue('session');
         if ($showSessions) {
             $coursesListSessionStyle = api_get_configuration_value('courses_list_session_title_link');
             $coursesListSessionStyle = $coursesListSessionStyle === false ? 1 : $coursesListSessionStyle;
@@ -1558,7 +1561,6 @@ class IndexManager
                                 ];
                                 $session_box = Display::getSessionTitleBox($session_id);
                                 $coachId = $session_box['id_coach'];
-                                $extraFieldValue = new ExtraFieldValue('session');
                                 $imageField = $extraFieldValue->get_values_by_handler_and_field_variable(
                                     $session_id,
                                     'image'
@@ -1567,9 +1569,8 @@ class IndexManager
                                 $params['category_id'] = $session_box['category_id'];
                                 $params['title'] = $session_box['title'];
                                 $params['id_coach'] = $coachId;
-                                $params['coach_url'] = api_get_path(
-                                        WEB_AJAX_PATH
-                                    ).'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId;
+                                $params['coach_url'] = api_get_path(WEB_AJAX_PATH).
+                                    'user_manager.ajax.php?a=get_user_popup&user_id='.$coachId;
                                 $params['coach_name'] = !empty($session_box['coach']) ? $session_box['coach'] : null;
                                 $params['coach_avatar'] = UserManager::getUserPicture(
                                     $coachId,
@@ -1579,6 +1580,18 @@ class IndexManager
                                 $params['image'] = isset($imageField['value']) ? $imageField['value'] : null;
                                 $params['duration'] = isset($session_box['duration']) ? ' '.$session_box['duration'] : null;
                                 $params['show_actions'] = SessionManager::cantEditSession($session_id);
+
+                                if ($collapsable) {
+                                    $collapsableData = Sessionmanager::getCollapsableData(
+                                        $user_id,
+                                        $session_id,
+                                        $extraFieldValue,
+                                        $collapsableLink
+                                    );
+                                    $params['collapsed'] = $collapsableData['collapsed'];
+                                    $params['collapsable_link'] = $collapsableData['collapsable_link'];
+                                }
+
                                 $params['show_description'] = $session_box['show_description'] == 1 && $portalShowDescription;
                                 $params['description'] = $session_box['description'];
                                 $params['visibility'] = $session_box['visibility'];
@@ -1722,6 +1735,17 @@ class IndexManager
                                     $sessionParams[0]['is_old'] = $markAsOld;
                                     $sessionParams[0]['is_future'] = $markAsFuture;
 
+                                    if ($collapsable) {
+                                        $collapsableData = Sessionmanager::getCollapsableData(
+                                            $user_id,
+                                            $session_id,
+                                            $extraFieldValue,
+                                            $collapsableLink
+                                        );
+                                        $sessionParams[0]['collapsable_link'] = $collapsableData['collapsable_link'];
+                                        $sessionParams[0]['collapsed'] = $collapsableData['collapsed'];
+                                    }
+
                                     if ($showSimpleSessionInfo) {
                                         $sessionParams[0]['subtitle'] = self::getSimpleSessionDetails(
                                             $session_box['coach'],

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

@@ -1289,7 +1289,7 @@ class Rest extends WebService
         if (!$course_code) {
             $course_code = CourseManager::get_course_code_from_course_id($course_id);
         }
-        if (CourseManager::subscribe_user($user_id, $course_code)) {
+        if (CourseManager::subscribeUser($user_id, $course_code)) {
             return [true];
         } else {
             return [false];

+ 2 - 2
main/inc/local.inc.php

@@ -834,8 +834,8 @@ if (!empty($_SESSION['_user']['user_id']) && !($login || $logout)) {
         }
     }
     $uidReset = true;
-    //    $cidReset = true;
-    //    $gidReset = true;
+    $cidReset = true;
+    $gidReset = true;
 } // end else
 
 $maxAnons = api_get_configuration_value('max_anonymous_users');

+ 27 - 15
main/install/configuration.dist.php

@@ -407,10 +407,10 @@ ALTER TABLE portfolio_category CHANGE title title LONGTEXT NOT NULL;
 //$_configuration['session_list_show_count_users'] = false;
 // Session admin access to all course content
 //$_configuration['session_admins_access_all_content'] = false;
+// Session admin allowed to edit all courses content
+//$_configuration['session_admins_edit_courses_content'] = false;
 // Adds roles to the system announcements (requires DB change BT#12476)
 //$_configuration['system_announce_extra_roles'] = false;
-// Limits the features that a session admin has access to from the main admin panel (removes users import and usergroups)
-//$_configuration['limit_session_admin_role'] = false;
 // Limits that a session admin has access to list users
 //$_configuration['limit_session_admin_list_users'] = false;
 // Course tools visibility edition in sessions
@@ -912,6 +912,19 @@ VALUES (2, 13, 'session_courses_read_only_mode', 'Lock Course In Session', 1, 1,
 // Show pending survey link in user menu
 // $_configuration['show_pending_survey_in_menu'] = false;
 
+// GDPR: European's General Data Protection Rules activation option
+// Set to true to disable the new personal data page inside the social network menu
+// $_configuration['disable_gdpr'] = true;
+
+// GDPR requires users to be informed of the Data Protection Officer name and contact point
+// These can only be defined here for now, but will be moved to web settings in the future.
+// Name of the person or organization that is responsible for the treatment of personal info
+//$_configuration['data_protection_officer_name'] = '';
+// A description of the role of the DP Officer in this context
+//$_configuration['data_protection_officer_role'] = '';
+// An e-mail address where to contact the data protection officer for queries
+//$_configuration['data_protection_officer_email'] = '';
+
 // Show multiple conditions to user during sign up process
 // Example with a GDPR condition
 /*$_configuration['show_conditions_to_user'] = [
@@ -943,19 +956,6 @@ VALUES (2, 13, 'session_courses_read_only_mode', 'Lock Course In Session', 1, 1,
 // ALTER TABLE usergroup ADD author_id INT DEFAULT NULL;
 //$_configuration['allow_teachers_to_classes'] = false;
 
-// GDPR: European's General Data Protection Rules activation option
-// Set to true to disable the new personal data page inside the social network menu
-// $_configuration['disable_gdpr'] = true;
-
-// GDPR requires users to be informed of the Data Protection Officer name and contact point
-// These can only be defined here for now, but will be moved to web settings in the future.
-// Name of the person or organization that is responsible for the treatment of personal info
-//$_configuration['data_protection_officer_name'] = '';
-// A description of the role of the DP Officer in this context
-//$_configuration['data_protection_officer_role'] = '';
-// An e-mail address where to contact the data protection officer for queries
-//$_configuration['data_protection_officer_email'] = '';
-
 // Validate user login via a webservice, Chamilo will send a "login" and "password" parameters
 // to the "myWebServiceFunctionToLogin" function, the result should be "1" if the user have access.
 /*$_configuration['webservice_validation'] = [
@@ -1056,6 +1056,18 @@ VALUES (2, 13, 'session_courses_read_only_mode', 'Lock Course In Session', 1, 1,
 // CREATE TABLE track_e_access_complete (id int(11) NOT NULL AUTO_INCREMENT, user_id int(11) NOT NULL, date_reg datetime NOT NULL, tool varchar(255) NOT NULL,  tool_id int(11) NOT NULL,   tool_id_detail int(11) NOT NULL,  action varchar(255) NOT NULL,   action_details varchar(255) NOT NULL, current_id int(11) NOT NULL,  ip_user varchar(255) NOT NULL,  user_agent varchar(255) NOT NULL,   session_id int(11) NOT NULL,   c_id int(11) NOT NULL,   ch_sid varchar(255) NOT NULL,   login_as int(11) NOT NULL,   info longtext NOT NULL,   url text NOT NULL,   PRIMARY KEY (id) ) ENGINE=InnoDB AUTO_INCREMENT=13989 DEFAULT CHARSET=utf8;
 //$_configuration['allow_track_complete'] = false;
 
+// Add collapsable option for user course categories
+// ALTER TABLE user_course_category ADD collapsed TINYINT(1) DEFAULT NULL;
+// $_configuration['allow_user_course_category_collapsable'] = false;
+
+// Add collapsable option when showing the course list inside a session in userportal.php
+// ALTER TABLE session_rel_user ADD collapsed TINYINT(1) DEFAULT NULL;
+// Create a new session extra field called "collapsed" (checkbox yes/no - option)
+// $_configuration['allow_user_session_collapsable'] = false;
+
+// Allow to session admins login as teachers
+//$_configuration['allow_session_admin_login_as_teacher'] = false;
+
 // ------ Custom DB changes (keep this at the end)
 // Add user activation by confirmation email
 // This option prevents the new user to login in the platform if your account is not confirmed via email

+ 30 - 17
main/install/install.lib.php

@@ -2279,12 +2279,11 @@ function fixIds(EntityManager $em)
     $result = $connection->fetchAll($sql);
     foreach ($result as $item) {
         $courseId = $item['c_id'];
-        $iid = isset($item['iid']) ? intval($item['iid']) : 0;
-        $ref = isset($item['ref']) ? intval($item['ref']) : 0;
+        $iid = isset($item['iid']) ? (int) $item['iid'] : 0;
+        $ref = isset($item['ref']) ? (int) $item['ref'] : 0;
         $sql = null;
 
         $newId = '';
-
         switch ($item['item_type']) {
             case TOOL_LINK:
                 $sql = "SELECT * FROM c_link WHERE c_id = $courseId AND id = $ref";
@@ -2447,9 +2446,13 @@ function fixIds(EntityManager $em)
             }*/
 
             $sql = '';
-            $newId = '';
+            //$newId = '';
             switch ($item['tool']) {
-                case TOOL_LINK:
+                case TOOL_LEARNPATH:
+                    $sql = "SELECT * FROM c_lp WHERE c_id = $courseId AND id = $ref ";
+                    break;
+                // already fixed in c_lp_item
+                /*case TOOL_LINK:
                     $sql = "SELECT * FROM c_link WHERE c_id = $courseId AND id = $ref ";
                     break;
                 case TOOL_STUDENTPUBLICATION:
@@ -2466,17 +2469,16 @@ function fixIds(EntityManager $em)
                     break;
                 case 'thread':
                     $sql = "SELECT * FROM c_forum_thread WHERE c_id = $courseId AND id = $ref";
-                    break;
+                    break;*/
             }
 
-            if (!empty($sql) && !empty($newId)) {
+            if (!empty($sql)) {
                 $data = $connection->fetchAssoc($sql);
-                if (isset($data['iid'])) {
+                if (isset($data['iid']) && !empty($data['iid'])) {
                     $newId = $data['iid'];
+                    $sql = "UPDATE c_item_property SET ref = $newId WHERE iid = $iid";
+                    $connection->executeQuery($sql);
                 }
-                $sql = "UPDATE c_item_property SET ref = $newId WHERE iid = $iid";
-                error_log($sql);
-                $connection->executeQuery($sql);
             }
         }
 
@@ -2911,7 +2913,7 @@ function fixLpId($connection, $debug)
                         }
                     }
 
-                    if ($item['item_type'] == 'document' && !empty($item['path'])) {
+                    if ($item['item_type'] === 'document' && !empty($item['path'])) {
                         $oldDocumentId = $item['path'];
                         $sql = "SELECT * FROM c_document WHERE c_id = $courseId AND id = $oldDocumentId";
                         $result = $connection->query($sql);
@@ -2920,12 +2922,23 @@ function fixLpId($connection, $debug)
                             $newDocumentId = $document['iid'];
                             if (!empty($newDocumentId)) {
                                 $sql = "UPDATE $tblCLpItem SET path = $newDocumentId 
-                                    WHERE iid = $itemIid AND c_id = $courseId";
+                                        WHERE iid = $itemIid AND c_id = $courseId";
+                                $connection->query($sql);
+                            }
+                        }
+                    }
+
+                    if ($item['item_type'] === 'link' && !empty($item['path'])) {
+                        $oldLinkId = $item['path'];
+                        $sql = "SELECT * FROM c_link WHERE c_id = $courseId AND id = $oldLinkId";
+                        $result = $connection->query($sql);
+                        $document = $result->fetch();
+                        if (!empty($document)) {
+                            $newLinkId = $document['iid'];
+                            if (!empty($newLinkId)) {
+                                $sql = "UPDATE $tblCLpItem SET path = $newLinkId 
+                                        WHERE iid = $itemIid AND c_id = $courseId";
                                 $connection->query($sql);
-                                if ($debug) {
-                                    //error_log("Fix document: ");
-                                    //error_log($sql);
-                                }
                             }
                         }
                     }

+ 132 - 129
main/lang/english/trad4all.inc.php

@@ -325,18 +325,18 @@ $DeleteUsersNotInList = "Unsubscribe students which are not in the imported list
 $IfSessionExistsUpdate = "If a session exists, update it";
 $CreatedByXYOnZ = "Create by <a href=\"%s\">%s</a> on %s";
 $LoginWithExternalAccount = "Login without an institutional account";
-$ImportAikenQuizExplanationExample = "This is the text for question 1
-A. Answer 1
-B. Answer 2
-C. Answer 3
-ANSWER: B
-
-This is the text for question 2
-A. Answer 1
-B. Answer 2
-C. Answer 3
-D. Answer 4
-ANSWER: D
+$ImportAikenQuizExplanationExample = "This is the text for question 1
+A. Answer 1
+B. Answer 2
+C. Answer 3
+ANSWER: B
+
+This is the text for question 2
+A. Answer 1
+B. Answer 2
+C. Answer 3
+D. Answer 4
+ANSWER: D
 ANSWER_EXPLANATION: this is an optional feedback comment that will appear next to the correct answer.";
 $ImportAikenQuizExplanation = "The Aiken format comes in a simple text (.txt) file, with several question blocks, each separated by a blank line. The first line is the question, the answer lines are prefixed by a letter and a dot, and the correct answer comes next with the ANSWER: prefix. See example below.";
 $ExerciseAikenErrorNoAnswerOptionGiven = "The imported file has at least one question without any answer (or the answers do not include the required prefix letter). Please make sure each question has at least one answer and that it is prefixed by a letter and a dot or a parenthesis, like this: A. answer one";
@@ -425,18 +425,18 @@ $VersionUpToDate = "Your version is up-to-date";
 $LatestVersionIs = "The latest version is";
 $YourVersionNotUpToDate = "Your version is not up-to-date";
 $Hotpotatoes = "Hotpotatoes";
-$ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected = "-1 = All questions will be selected.
+$ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected = "-1 = All questions will be selected.
  0 = No questions will be selected.";
-$EmailNotificationTemplateDescription = "You can customize the email sent to users when they finished the exercise. You can use tags like these:
-
-1. {{ student.username }}
-2. {{ student.firstname }}
-3. {{ student.lastname }}
-4. {{ student.official_code }}
-5. {{ exercise.title }}
-6. {{ exercise.start_time }}
-7. {{ exercise.end_time }}
-8. {{ course.title }}
+$EmailNotificationTemplateDescription = "You can customize the email sent to users when they finished the exercise. You can use tags like these:
+
+1. {{ student.username }}
+2. {{ student.firstname }}
+3. {{ student.lastname }}
+4. {{ student.official_code }}
+5. {{ exercise.title }}
+6. {{ exercise.start_time }}
+7. {{ exercise.end_time }}
+8. {{ course.title }}
 9. {{ course.code }}";
 $EmailNotificationTemplate = "E-mail notification template";
 $ExerciseEndButtonDisconnect = "Logout";
@@ -844,10 +844,10 @@ $AllowVisitors = "Allow visitors";
 $EnableIframeInclusionComment = "Allowing arbitrary iframes in the HTML Editor will enhance the edition capabilities of the users, but it can represent a security risk. Please make sure you can rely on your users (i.e. you know who they are) before enabling this feature.";
 $AddedToLPCannotBeAccessed = "This exercise has been included in a learning path, so it cannot be accessed by students directly from here. If you want to put the same exercise available through the exercises tool, please make a copy of the current exercise using the copy icon.";
 $EnableIframeInclusionTitle = "Allow iframes in HTML Editor";
-$MailTemplateRegistrationMessage = "Dear ((firstname)) ((lastname)),\n\nYou are registered on
-((sitename)) with the following settings:\n\nUsername :
-((username))\nPass : ((password))\n\nThe address of ((sitename)) is :
-((url))\n\nIn case of trouble, contact us.\n\nYours sincerely
+$MailTemplateRegistrationMessage = "Dear ((firstname)) ((lastname)),\n\nYou are registered on
+((sitename)) with the following settings:\n\nUsername :
+((username))\nPass : ((password))\n\nThe address of ((sitename)) is :
+((url))\n\nIn case of trouble, contact us.\n\nYours sincerely
 \n((admin_name)) ((admin_surname)).";
 $Explanation = "Once you click on \"Create a course\", a course is created with a section for Tests, Project based learning, Assessments, Courses, Dropbox, Agenda and much more. Logging in as teacher provides you with editing privileges for this course.";
 $CodeTaken = "This course code is already in use.<br>Use the <b>Back</b> button on your browser and try again.";
@@ -2640,16 +2640,16 @@ $NoPosts = "No posts";
 $WithoutAchievedSkills = "Without achieved skills";
 $TypeMessage = "Please type your message!";
 $ConfirmReset = "Do you really want to delete all messages?";
-$MailCronCourseExpirationReminderBody = "Dear %s,
-
-It has come to our attention that you have not completed the course %s although its expiration date had been set on %s, remaining %s days to finish it.
-
-We remind you that you have only the possibility to follow this course once a year, that is why we invite you insistently to complete your course on time.
-
-You can return to the course connecting to the platform through this address: %s
-
-Best Regards,
-
+$MailCronCourseExpirationReminderBody = "Dear %s,
+
+It has come to our attention that you have not completed the course %s although its expiration date had been set on %s, remaining %s days to finish it.
+
+We remind you that you have only the possibility to follow this course once a year, that is why we invite you insistently to complete your course on time.
+
+You can return to the course connecting to the platform through this address: %s
+
+Best Regards,
+
 %s Team";
 $MailCronCourseExpirationReminderSubject = "Urgent: %s course expiration reminder";
 $ExerciseAndLearningPath = "Exercise and learning path";
@@ -5777,8 +5777,8 @@ $CheckThatYouHaveEnoughQuestionsInYourCategories = "Make sure you have enough qu
 $PortalCoursesLimitReached = "Sorry, this installation has a courses limit, which has now been reached. To increase the number of courses allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.";
 $PortalTeachersLimitReached = "Sorry, this installation has a teachers limit, which has now been reached. To increase the number of teachers allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.";
 $PortalUsersLimitReached = "Sorry, this installation has a users limit, which has now been reached. To increase the number of users allowed on this Chamilo installation, please contact your hosting provider or, if available, upgrade to a superior hosting plan.";
-$GenerateSurveyAccessLinkExplanation = "By copying the link below and pasting it in an e-mail or on a website, you will allow any anonymous person to enter and answer this survey.
-You can test this feature by clicking the link above and answering the survey.
+$GenerateSurveyAccessLinkExplanation = "By copying the link below and pasting it in an e-mail or on a website, you will allow any anonymous person to enter and answer this survey.
+You can test this feature by clicking the link above and answering the survey.
 This is particularly useful if you want to allow anyone on a forum to answer you survey and you don't know their e-mail addresses.";
 $LinkOpenSelf = "Open self";
 $LinkOpenBlank = "Open blank";
@@ -5831,8 +5831,8 @@ $Item = "Item";
 $ConfigureDashboardPlugin = "Configure Dashboard Plugin";
 $EditBlocks = "Edit blocks";
 $Never = "Never";
-$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Dear user, 
-
+$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Dear user, 
+
 Your account has now been activated on the platform. Please login and enjoy your courses.";
 $SessionFields = "Session fields";
 $CopyLabelSuffix = "Copy";
@@ -5894,7 +5894,7 @@ $CourseSettingsRegisterDirectLink = "If your course is public or open, you can u
 $DirectLink = "Direct link";
 $here = "here";
 $GoAheadAndBrowseOurCourseCatalogXOnceRegisteredYouWillSeeTheCourseHereX = "<p>Go ahead and browse our course catalog %s to register to any course you like. Once registered, you will see the course appear right %s, instead of this message.</p>";
-$HelloXAsYouCanSeeYourCourseListIsEmpty = "<p>Hello <strong>%s</strong> and welcome,</p>
+$HelloXAsYouCanSeeYourCourseListIsEmpty = "<p>Hello <strong>%s</strong> and welcome,</p>
 <p>As you can see, your courses list is still empty. That's because you are not registered to any course yet! </p>";
 $UnsubscribeUsersAlreadyAddedInCourse = "Unsubscribe users already added";
 $ImportUsers = "Import users";
@@ -6156,7 +6156,7 @@ $AverageScore = "Average score";
 $LastConnexionDate = "Last connexion date";
 $ToolVideoconference = "Videoconference";
 $BigBlueButtonEnableTitle = "BigBlueButton videoconference tool";
-$BigBlueButtonEnableComment = "Choose whether you want to enable the BigBlueButton videoconference tool. Once enabled, it will show as an additional course tool in all courses' homepage, and teachers will be able to launch a conference at any time. Learners will not be able to launch a conference, only join one. If you don't have a BigBlueButton server, please <a href=\"http://bigbluebutton.org/\" target=\"_blank\">set one up</a> or ask the <a href=\"http://www.chamilo.org/en/providers\" target=\"_blank\">Chamilo official providers</a> for a quote.
+$BigBlueButtonEnableComment = "Choose whether you want to enable the BigBlueButton videoconference tool. Once enabled, it will show as an additional course tool in all courses' homepage, and teachers will be able to launch a conference at any time. Learners will not be able to launch a conference, only join one. If you don't have a BigBlueButton server, please <a href=\"http://bigbluebutton.org/\" target=\"_blank\">set one up</a> or ask the <a href=\"http://www.chamilo.org/en/providers\" target=\"_blank\">Chamilo official providers</a> for a quote.
 BigBlueButton is a free (as in freedom *and* beer), but its installation requires a set of technical skills that might not be immediately available to all. You can install it on your own or seek professional help to assist you or do it for you. This help, however, will generate a certain cost. In the pure logic of the free software, we offer you the tools to make your work easier and recommend professionals (the Chamilo Official Providers) that will be able to help you if this were too difficult.";
 $BigBlueButtonHostTitle = "BigBlueButton server host";
 $BigBlueButtonHostComment = "This is the name of the server where your BigBlueButton server is running. Might be <i>localhost</i>, an IP address (e.g. 192.168.13.54) or a domain name (e.g. my.video.com).";
@@ -6167,14 +6167,14 @@ $OnlyAccessFromYourGroup = "Only accessible from your group";
 $CreateAssignmentPage = "This will create a special wiki page in which the teacher can describe the task and which will be automatically linked to the wiki pages where learners perform the task. Both the teacher's and the learners' pages are created automatically. In these tasks, learners can only edit and view theirs pages, but this can be changed easily if you need to.";
 $UserFolders = "Folders of users";
 $UserFolder = "User folder";
-$HelpUsersFolder = "INFORMATION VISIBLE TO THE TEACHER ONLY:\nThe users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.
-<br /><br />
-The users folder and each of the included folders will be hidden by default in for all students, but each student can see the contents of his/her directory through the online editor. However, if a student knows the address of a file or folder of another student, he may be able to access it.
-<br /><br />
-If the folder of a student is visible, other students can see what it contains. In this case, the student that owns the folder can also (from the documents tool and only in his/her folder): create and edit web documents, convert a document into a template for personal use, create and edit drawings in SVG and PNG formats, record audio files in WAV format, make audio files in MP3 from a text, make snapshops from a webcam, send documents, create folders, move folders and files, delete folders and files, and download backup of his/her folder.
-<br /><br />
-Moreover, the documents tool is synchronized with the file manager of the online editor, so changes in the documents triggered in any one of these will affect both.
-<br /><br />
+$HelpUsersFolder = "INFORMATION VISIBLE TO THE TEACHER ONLY:\nThe users folder contains a folder for each user who has accessed it through the documents tool, or when any file has been sent in the course through the online editor. If neither circumstances has occurred, then no user folder will have been created. In the case of groups, files that are sent through the editor will be added in the folder of each group, which is only accessible by students from this group.
+<br /><br />
+The users folder and each of the included folders will be hidden by default in for all students, but each student can see the contents of his/her directory through the online editor. However, if a student knows the address of a file or folder of another student, he may be able to access it.
+<br /><br />
+If the folder of a student is visible, other students can see what it contains. In this case, the student that owns the folder can also (from the documents tool and only in his/her folder): create and edit web documents, convert a document into a template for personal use, create and edit drawings in SVG and PNG formats, record audio files in WAV format, make audio files in MP3 from a text, make snapshops from a webcam, send documents, create folders, move folders and files, delete folders and files, and download backup of his/her folder.
+<br /><br />
+Moreover, the documents tool is synchronized with the file manager of the online editor, so changes in the documents triggered in any one of these will affect both.
+<br /><br />
 As such, the user folder is not only a place to deposit files, it becomes a complete manager of the documents students use during the course. Also, remember that any user can copy a file that is visible from any folder in the documents tool (whether or not he is the owner) to his/her portfolios or personal documents area of social network, which will be available to him/her for use in other courses.";
 $HelpFolderChat = "INFORMATION VISIBLE TO THE TEACHER ONLY:\nThis folder contains all sessions that have been opened in the chat. Although the chat sessions can often be trivial, others can be really interesting and worthy of being incorporated as an additional work document. To do this without changing the visibility of this folder, make the file visible and link it from where you deem appropriate. It is not recommended to make this folder visible to all.";
 $HelpFolderCertificates = "INFORMATION VISIBLE TO THE TEACHER ONLY:\nThis folder contains the various certificates templates that have been created for the rating tool. It is not recommended to make this folder visible to all.";
@@ -6223,8 +6223,8 @@ $Pediaphon = "Use Pediaphon audio services";
 $HelpPediaphon = "Supports text with several thousands characters, in various types of male and female voices (depending on the language). Audio files will be generated and automatically saved to the Chamilo directory in which you are.";
 $FirstSelectALanguage = "Please select a language";
 $MoveUserStats = "Move users results from/to a session";
-$CompareUserResultsBetweenCoursesAndCoursesInASession = "This advanced tool allows you to manually improve the tracking of users results when moving from courses methodology to sessions methodology. In most cases, you won't need to use it.<br />
-On this screen, you can compare results of users between the context of a standalone course, and the context of the same course inside a session.<br />
+$CompareUserResultsBetweenCoursesAndCoursesInASession = "This advanced tool allows you to manually improve the tracking of users results when moving from courses methodology to sessions methodology. In most cases, you won't need to use it.<br />
+On this screen, you can compare results of users between the context of a standalone course, and the context of the same course inside a session.<br />
 Once you are sure about what to do, you can choose to move the tracking data of the students (exercises results and learning paths tracking) from a course to a session.";
 $PDFExportWatermarkEnableTitle = "Enable watermark in PDF export";
 $PDFExportWatermarkEnableComment = "By enabling this option, you can upload an image or a text that will be automatically added as watermark to all PDF exports of documents on the system.";
@@ -6656,7 +6656,7 @@ $DisableEndDate = "Disable end date";
 $ForumCategories = "Forum Categories";
 $Copy = "Copy";
 $ArchiveDirCleanup = "Cleanup of cache and temporary files";
-$ArchiveDirCleanupDescr = "Chamilo keeps a copy of most of the temporary files it generates (for backups, exports, copies, etc) into its app/cache/ directory. After a while, this can add up to a very large amount of disk space being used for nothing. Click the button below to clean your archive directory up. This operation should be automated by a cron process, but if this is not possible, you can come to this page regularly to remove all temporary files from the directory.
+$ArchiveDirCleanupDescr = "Chamilo keeps a copy of most of the temporary files it generates (for backups, exports, copies, etc) into its app/cache/ directory. After a while, this can add up to a very large amount of disk space being used for nothing. Click the button below to clean your archive directory up. This operation should be automated by a cron process, but if this is not possible, you can come to this page regularly to remove all temporary files from the directory.
 This feature also cleans up the theme cache files.";
 $ArchiveDirCleanupProceedButton = "Proceed with cleanup";
 $ArchiveDirCleanupSucceeded = "The app/cache/ directory cleanup has been executed successfully.";
@@ -7005,45 +7005,45 @@ $ResourceLockedByGradebook = "This option is not available because this activity
 $GradebookLockedAlert = "This assessment has been locked. You cannot unlock it. If you really need to unlock it, please contact the platform administrator, explaining the reason why you would need to do that (it might otherwise be considered as fraud attempt).";
 $GradebookEnableLockingTitle = "Enable locking of assessments by teachers";
 $GradebookEnableLockingComment = "Once enabled, this option will enable locking of any assessment by the teachers of the corresponding course. This, in turn, will prevent any modification of results by the teacher inside the resources used in the assessment: exams, learning paths, tasks, etc. The only role authorized to unlock a locked assessment is the administrator. The teacher will be informed of this possibility. The locking and unlocking of gradebooks will be registered in the system's report of important activities";
-$LdapDescriptionComment = "    <div class=\"alert alert-info\">
-        <ul>
-            <li>LDAP authentication : <br>
-                See I. below to configure LDAP <br>
-                See II. below to activate LDAP authentication
-            </li>
-            <li>Update user attributes, with LDAP data, after CAS authentication(see <a href=\"settings.php?category=CAS\">CAS configuration </a>) : <br>
-                See I. below to configure LDAP <br>
-                CAS manage user authentication, LDAP activation isn't required.
-            </li>
-        </ul>
-    </div>
-    <h4>I. LDAP configuration</h4>
-    <h5>Edit file app/config/auth.conf.php </h5>
-    <p>-&gt; Edit values of array <code>\$extldap_config</code></p>
-    <ul>
-        <li>base domain string (ex : 'base_dn' =&gt; 'DC=cblue,DC=be')</li>
-        <li>admin distinguished name (ex : 'admin_dn' =&gt;'CN=admin,dc=cblue,dc=be')</li>
-        <li>admin password (ex : 'admin_password' =&gt; '123456') </li>
-        <li>ldap host (ex : 'host' =&gt; array('1.2.3.4', '2.3.4.5', '3.4.5.6'))</li>
-        <li>filter (ex : 'filter' =&gt; '') </li>
-        <li>port (ex : 'port' =&gt; 389) </li>
-        <li>protocol version (2 or 3) (ex : 'protocol_version' =&gt; 3)</li>
-        <li>user_search (ex : 'user_search' =&gt; 'sAMAccountName=%username%') </li>
-        <li>encoding (ex : 'encoding' =&gt; 'UTF-8')</li>
-        <li>update_userinfo (ex : 'update_userinfo' =&gt; true) </li>
-    </ul>
-    <p>-&gt; To update correspondences between user and LDAP attributes, edit array <code>\$extldap_user_correspondance</code></p>
-    <p>Array values are &lt;chamilo_field&gt; =&gt; &gt;ldap_field&gt;</p><p>
-    </p>
-    <h4>II. Activate LDAP authentication </h4>
-    <h5>Edit file app/config/configuration.php </h5>
-    <p>-&gt; Uncomment lines:</p>
-    <ul>
-        <li>
-    \$extAuthSource[\"extldap\"][\"login\"] = \$_configuration['root_sys'].\"main/auth/external_login/login.ldap.php\";</li>
-        <li>\$extAuthSource[\"extldap\"][\"newUser\"] = \$_configuration['root_sys'].\"main/auth/external_login/newUser.ldap.php\";</li>
-    </ul>
-    <p>N.B.: LDAP users use same fields than platform users to login. <br>
+$LdapDescriptionComment = "    <div class=\"alert alert-info\">
+        <ul>
+            <li>LDAP authentication : <br>
+                See I. below to configure LDAP <br>
+                See II. below to activate LDAP authentication
+            </li>
+            <li>Update user attributes, with LDAP data, after CAS authentication(see <a href=\"settings.php?category=CAS\">CAS configuration </a>) : <br>
+                See I. below to configure LDAP <br>
+                CAS manage user authentication, LDAP activation isn't required.
+            </li>
+        </ul>
+    </div>
+    <h4>I. LDAP configuration</h4>
+    <h5>Edit file app/config/auth.conf.php </h5>
+    <p>-&gt; Edit values of array <code>\$extldap_config</code></p>
+    <ul>
+        <li>base domain string (ex : 'base_dn' =&gt; 'DC=cblue,DC=be')</li>
+        <li>admin distinguished name (ex : 'admin_dn' =&gt;'CN=admin,dc=cblue,dc=be')</li>
+        <li>admin password (ex : 'admin_password' =&gt; '123456') </li>
+        <li>ldap host (ex : 'host' =&gt; array('1.2.3.4', '2.3.4.5', '3.4.5.6'))</li>
+        <li>filter (ex : 'filter' =&gt; '') </li>
+        <li>port (ex : 'port' =&gt; 389) </li>
+        <li>protocol version (2 or 3) (ex : 'protocol_version' =&gt; 3)</li>
+        <li>user_search (ex : 'user_search' =&gt; 'sAMAccountName=%username%') </li>
+        <li>encoding (ex : 'encoding' =&gt; 'UTF-8')</li>
+        <li>update_userinfo (ex : 'update_userinfo' =&gt; true) </li>
+    </ul>
+    <p>-&gt; To update correspondences between user and LDAP attributes, edit array <code>\$extldap_user_correspondance</code></p>
+    <p>Array values are &lt;chamilo_field&gt; =&gt; &gt;ldap_field&gt;</p><p>
+    </p>
+    <h4>II. Activate LDAP authentication </h4>
+    <h5>Edit file app/config/configuration.php </h5>
+    <p>-&gt; Uncomment lines:</p>
+    <ul>
+        <li>
+    \$extAuthSource[\"extldap\"][\"login\"] = \$_configuration['root_sys'].\"main/auth/external_login/login.ldap.php\";</li>
+        <li>\$extAuthSource[\"extldap\"][\"newUser\"] = \$_configuration['root_sys'].\"main/auth/external_login/newUser.ldap.php\";</li>
+    </ul>
+    <p>N.B.: LDAP users use same fields than platform users to login. <br>
     N.B.: LDAP activation adds a menu External authentication [LDAP] in \"add or modify\" user pages.</p>";
 $ShibbolethMainActivateTitle = "<h3>Shibboleth authentication</h3>";
 $ShibbolethMainActivateComment = "<p>First of all, you have to configure Shibboleth for your web server.</p>To configure it for Chamilo<h5>edit file main/auth/shibboleth/config/aai.class.php</h5><p>Modify object &#36;result values with the name of your Shibboleth attributes</p><ul><li>&#36;result-&gt;unique_id = 'mail';</li><li>&#36;result-&gt;firstname = 'cn';</li><li>&#36;result-&gt;lastname = 'uid';</li><li>&#36;result-&gt;email = 'mail';</li><li>&#36;result-&gt;language = '-';</li><li>&#36;result-&gt;gender = '-';</li><li>&#36;result-&gt;address = '-';</li><li>&#36;result-&gt;staff_category = '-';</li><li>&#36;result-&gt;home_organization_type = '-'; </li><li>&#36;result-&gt;home_organization = '-';</li><li>&#36;result-&gt;affiliation = '-';</li><li>&#36;result-&gt;persistent_id = '-';</li><li>...</li></ul><br/>Go to <a href='settings.php?category=Shibboleth'>Plugin</a> to add a configurable 'Shibboleth Login' button for your Chamilo campus.";
@@ -7474,7 +7474,7 @@ $AreYouSureToSubscribe = "Are you sure to subscribe?";
 $CheckYourEmailAndFollowInstructions = "Check your e-mail and follow the instructions.";
 $LinkExpired = "Link expired, please try again.";
 $ResetPasswordInstructions = "Instructions for the password change procedure";
-$ResetPasswordCommentWithUrl = "You are receiving this message because you (or someone pretending to be you) have requested a new password to be generated for you.<br/>
+$ResetPasswordCommentWithUrl = "You are receiving this message because you (or someone pretending to be you) have requested a new password to be generated for you.<br/>
 To set a the new password you need to activate it. To do this, please click this link:<br/>%s<br/>If you did not request this procedure, then please ignore this message. If you keep receiving it, please contact the portal administrator.";
 $CronRemindCourseExpirationActivateTitle = "Remind Course Expiration cron";
 $CronRemindCourseExpirationActivateComment = "Enable the Remind Course Expiration cron";
@@ -7483,8 +7483,8 @@ $CronRemindCourseExpirationFrequencyComment = "Number of days before the expirat
 $CronCourseFinishedActivateText = "Course Finished cron";
 $CronCourseFinishedActivateComment = "Activate the Course Finished cron";
 $MailCronCourseFinishedSubject = "End of course %s";
-$MailCronCourseFinishedBody = "Dear %s,<br/>Thank you for your participation to course %s. We hope you've acquired new relevant knowledge and enjoyed the course.<br/>
-You can check your performance in the course through the My Progress section.<br/>Best regards,<br/>
+$MailCronCourseFinishedBody = "Dear %s,<br/>Thank you for your participation to course %s. We hope you've acquired new relevant knowledge and enjoyed the course.<br/>
+You can check your performance in the course through the My Progress section.<br/>Best regards,<br/>
 %s Team";
 $GenerateDefaultContent = "Generate default content";
 $ThanksForYourSubscription = "Thanks for your subscription";
@@ -7856,7 +7856,7 @@ $EditCourseCategoryToURL = "Edit course categories for one URL";
 $VisibleToSelf = "Visible to self";
 $VisibleToOthers = "Visible to others";
 $UpgradeVersion = "Upgrade Chamilo LMS version";
-$CRSTablesIntro = "The install script has detected tables left over from previous versions that could cause problems during the upgrade process.
+$CRSTablesIntro = "The install script has detected tables left over from previous versions that could cause problems during the upgrade process.
 Please click on the button below to delete them. We heavily recommend you do a full backup of them before confirming this last install step.";
 $Removing = "Removing";
 $CheckForCRSTables = "Check for tables from previous versions";
@@ -7867,7 +7867,7 @@ $TooManyRepetitions = "Too many repetitions";
 $YourPasswordContainsSequences = "Your password contains sequences";
 $PasswordVeryWeak = "Very weak";
 $UserXHasBeenAssignedToBoss = "You have been assigned the learner %s";
-$UserXHasBeenAssignedToBossWithUrlX = "You have been assigned as tutor for the learner %s.<br/>
+$UserXHasBeenAssignedToBossWithUrlX = "You have been assigned as tutor for the learner %s.<br/>
 You can access his profile here: %s";
 $ShortName = "Short name";
 $Portal = "Portal";
@@ -7937,8 +7937,8 @@ $InstallMultiURLDetectedNotMainURL = "You are currently using the multi-URL feat
 $OnlyXQuestionsPickedRandomly = "Only %s questions will be picked randomly following the quiz configuration.";
 $AllowDownloadDocumentsByApiKeyTitle = "Allow download course documents by API Key";
 $AllowDownloadDocumentsByApiKeyComment = "Download documents verifying the REST API key for a user";
-$UploadCorrectionsExplanationWithDownloadLinkX = "First you have to download the corrections <a href='%s'> here </a>.
-After that you have to unzip that file and edit the files as you wanted without changing the file names.
+$UploadCorrectionsExplanationWithDownloadLinkX = "First you have to download the corrections <a href='%s'> here </a>.
+After that you have to unzip that file and edit the files as you wanted without changing the file names.
 Then create a zip file with those modified files and upload it in this form.";
 $PostsPendingModeration = "Posts pending moderation";
 $OnlyUsersFromCourseSession = "Only users from one course in a session";
@@ -8005,12 +8005,12 @@ $SpecificDate = "Specific dispatch date";
 $BaseDate = "Dispatch based on the session's start/end dates";
 $AfterOrBefore = "After or before";
 $Before = "Before";
-$ScheduleAnnouncementDescription = "This form allows scheduling announcements to be sent automatically to the students who are taking a course in a session. 
-
-There are two types of announcements that can be sent: 
-
-Specific date: In this case a specific day is selected to make the announcement. 
-
+$ScheduleAnnouncementDescription = "This form allows scheduling announcements to be sent automatically to the students who are taking a course in a session. 
+
+There are two types of announcements that can be sent: 
+
+Specific date: In this case a specific day is selected to make the announcement. 
+
 Based on the start / end date of the session: in this case the number of days to pass before sending the announcement must be indicated. And those days can be associated to before or after the start / end date. For example: 3 days after the start date.";
 $MandatorySurveyNoAnswered = "A mandatory survey is waiting your answer. To enter the course, you must first complete the survey.";
 $ShowPreviousButton = "Show previous button";
@@ -8031,18 +8031,18 @@ $AddHrmToUser = "Add Human Resources Manager to user";
 $HrmAssignedUsersCourseList = "Human Resources Manager assigned users course list";
 $GoToSurvey = "Go to Survey";
 $NotificationCertificateSubject = "Certificate notification";
-$NotificationCertificateTemplate = "((user_first_name)),
-
-Congratulations on the completion of ((course_title)). Your final grade received for this class is ((score)). Please allow a few days for it to reflect in their system. We look forward to seeing you in future classes. If we can assist you further in any way, please feel free to contact us.
-
-Sincerely,
-((author_first_name)), ((author_last_name))
+$NotificationCertificateTemplate = "((user_first_name)),
+
+Congratulations on the completion of ((course_title)). Your final grade received for this class is ((score)). Please allow a few days for it to reflect in their system. We look forward to seeing you in future classes. If we can assist you further in any way, please feel free to contact us.
+
+Sincerely,
+((author_first_name)), ((author_last_name))
 ((portal_name))";
 $SendCertificateNotifications = "Send certificate notification to all users";
 $MailSubjectForwardShort = "Fwd";
 $ForwardedMessage = "Forwarded message";
 $ForwardMessage = "Forward message";
-$MyCoursePageCategoryIntroduction = "You will find below the list of course categories.
+$MyCoursePageCategoryIntroduction = "You will find below the list of course categories.
 Click on one of them to see the list of courses it has.";
 $FeatureDisabledByAdministrator = "Feature disabled by administrator";
 $SubscribeUsersToLpCategory = "Subscribe users to category";
@@ -8147,10 +8147,10 @@ $RedirectToTheDocumentList = "Redirect to the document list";
 $TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToTheExerciseList = "The exercises auto-launch feature configuration is enabled. Learners will be automatically redirected to exercise list.";
 $PostedExpirationDate = "Posted deadline for sending the work (Visible to the learner)";
 $BossAlertMsgSentToUserXTitle = "Follow up message about student %s";
-$BossAlertUserXSentMessageToUserYWithLinkZ = "Hi,<br/><br/>
-
-User %s sent a follow up message about student %s.<br/><br/>
-
+$BossAlertUserXSentMessageToUserYWithLinkZ = "Hi,<br/><br/>
+
+User %s sent a follow up message about student %s.<br/><br/>
+
 The message can be seen here %s";
 $include_services = "Include services";
 $culqi_enable = "Enable culqi";
@@ -8182,8 +8182,8 @@ $PersonalDataLimitationTitle = "Personal data limitation";
 $PersonalDataDeletionTitle = "Personal data deletion";
 $PersonalDataDestructionTitle = "Personal data destruction";
 $PersonalDataProfilingTitle = "Personal data profiling";
-$PersonalDataIntroductionText = "We respect your privacy!
-This page unites all aspects of the personal data we might be keeping on you, how we treat it, what you have authorized us to do with it and who we are, in an effort to comply with most data privacy laws available.
+$PersonalDataIntroductionText = "We respect your privacy!
+This page unites all aspects of the personal data we might be keeping on you, how we treat it, what you have authorized us to do with it and who we are, in an effort to comply with most data privacy laws available.
 Please read this information carefully and, if you have any question, check the contact details below to ask us for more details.";
 $YourDegreeOfCertainty = "Your degree of certainty";
 $DegreeOfCertaintyThatMyAnswerIsCorrect = "Degree of certainty that my answer will be considered correct";
@@ -8194,11 +8194,11 @@ $ResultAccomplishedTest = "Results for the accomplished test";
 $YourResultsByDiscipline = "Your results by discipline";
 $ForComparisonYourLastResultToThisTest = "In comparison, your latest results for this test";
 $YourOverallResultForTheTest = "Your overall results for the test";
-$QuestionDegreeCertaintyHTMLMail = "You will find your results for test %s below.<br />
-To see the details of these results:
-<br /><br />
-1. Connect to the platform (login/password): <a href='%s'>To the platform</a>.
-<br /><br />
+$QuestionDegreeCertaintyHTMLMail = "You will find your results for test %s below.<br />
+To see the details of these results:
+<br /><br />
+1. Connect to the platform (login/password): <a href='%s'>To the platform</a>.
+<br /><br />
 2. Click this link: <a href='%s'>See detailed results</a>.";
 $DegreeOfCertaintyVerySure = "Very sure";
 $DegreeOfCertaintyVerySureDescription = "Your answer was correct and you were 80% sure about it. Congratulations!";
@@ -8260,8 +8260,8 @@ $SubSkill = "Sub-skill";
 $AddMultipleUsersToCalendar = "Add multiple users to calendar";
 $UpdateCalendar = "Update calendar";
 $ControlPoint = "Control point";
-$MessageQuestionCertainty = "Please follow the instructions below to check your results for test %s.<br /><br />
-1. Connect to the platform (username/password) at: %s<br />
+$MessageQuestionCertainty = "Please follow the instructions below to check your results for test %s.<br /><br />
+1. Connect to the platform (username/password) at: %s<br />
 2. Click the following link: %s<br />";
 $SessionMinDuration = "Session min duration";
 $CanNotTranslate = "Could not translate";
@@ -8313,11 +8313,14 @@ $ProgressObtainedFromLPProgressAndTestsAverage = "Note: This progress is obtaine
 $CreateNewSurveyDoodle = "Create a new Doodle type survey";
 $RemoveMultiplicateQuestions = "Remove multiplicated questions";
 $MultiplicateQuestions = "Multiplicate questions";
-$QuestionTags = "You can use the tags {{class_name}} and {{student_full_name}} in the question to be able to multiplicate questions.
-On the survey's list page in the action field you have a button to multiplicate question that will look for the {{class_name}} tag and duplicate the question for all the class subscribed to the course and rename it with the name of the class.
-It will also add a page ending to make a new page for each class.
+$QuestionTags = "You can use the tags {{class_name}} and {{student_full_name}} in the question to be able to multiplicate questions.
+On the survey's list page in the action field you have a button to multiplicate question that will look for the {{class_name}} tag and duplicate the question for all the class subscribed to the course and rename it with the name of the class.
+It will also add a page ending to make a new page for each class.
 Then it will look for the {{student_full_name}} tag and duplicate the question for all the student in the class (for each class) and rename it with the student's full name.";
 $CreateMeeting = "Create meeting poll";
 $QuestionForNextClass = "Question for next class";
 $NewExerciseAttemptDisabled = "The portal do not allowed to start new test for the moment, please come back later.";
+$PersonalDataOfficer = "Personal data officer";
+$MaxNumberSubscribedStudentsReached = "The maximum number of student has already been reached, it is not possible to subscribe more student.";
+$UserXAddedToCourseX = "User %s has been registered to course %s";
 ?>

+ 64 - 62
main/lang/french/trad4all.inc.php

@@ -239,7 +239,7 @@ $WithoutCategory = "Sans catégorie";
 $IncorrectScore = "Score réponse incorrecte";
 $CorrectScore = "Score réponse correcte";
 $UseCustomScoreForAllQuestions = "Utiliser le score sur mesure pour toutes les questions";
-$YouShouldAddItemsBeforeAttachAudio = "
+$YouShouldAddItemsBeforeAttachAudio = "
 Vous devez ajouter des étapes à votre parcours avant de pouvoir leur affecter des fichiers audio";
 $InactiveDays = "Jours inactifs";
 $FollowedHumanResources = "DRH suivis";
@@ -326,23 +326,23 @@ $DeleteUsersNotInList = "Désinscrite les apprenants qui ne sont pas dans la lis
 $IfSessionExistsUpdate = "Si une session existe, la mettre à jour";
 $CreatedByXYOnZ = "Créé(e) par <a href=\"%s\">%s</a>, le %s";
 $LoginWithExternalAccount = "S'authentifier avec un compte extérieur à l'établissement";
-$ImportAikenQuizExplanationExample = "Ceci est le texte de la question 1
-A. Réponse 1
-B. Réponse 2
-C. Réponse 3
-ANSWER: B
-
-Ceci est le texte de la question 2
-A. Réponse 1
-B. Réponse 2
-C. Réponse 3
-D. Réponse 4
-ANSWER: D
-
+$ImportAikenQuizExplanationExample = "Ceci est le texte de la question 1
+A. Réponse 1
+B. Réponse 2
+C. Réponse 3
+ANSWER: B
+
+Ceci est le texte de la question 2
+A. Réponse 1
+B. Réponse 2
+C. Réponse 3
+D. Réponse 4
+ANSWER: D
+
 ANSWER_EXPLANATION: C'est un commentaire facultatif de retour qui apparaîtra à côté de la bonne réponse.";
-$ImportAikenQuizExplanation = "Le format Aiken est un fichier (.txt) avec un texte simple, avec plusieurs blocs de questions, chacune séparée par une ligne blanche. La première ligne est la question, les lignes de réponse sont préfixés par une lettre et un point, et la bonne réponse vient avec le préfixe 'ANSWER'.
+$ImportAikenQuizExplanation = "Le format Aiken est un fichier (.txt) avec un texte simple, avec plusieurs blocs de questions, chacune séparée par une ligne blanche. La première ligne est la question, les lignes de réponse sont préfixés par une lettre et un point, et la bonne réponse vient avec le préfixe 'ANSWER'.
 Voir l'exemple ci-dessous.";
-$ExerciseAikenErrorNoAnswerOptionGiven = "Le fichier importé comporte au moins une question sans réponse (ou les réponses ne comprennent pas la lettre de préfixe requis). Assurez-vous que chaque question a au moins une réponse et qu'elle est précédée par une lettre et un point ou une parenthèse, comme ceci: 
+$ExerciseAikenErrorNoAnswerOptionGiven = "Le fichier importé comporte au moins une question sans réponse (ou les réponses ne comprennent pas la lettre de préfixe requis). Assurez-vous que chaque question a au moins une réponse et qu'elle est précédée par une lettre et un point ou une parenthèse, comme ceci: 
 A. Réponse 1";
 $ExerciseAikenErrorNoCorrectAnswerDefined = "Le fichier importé comporte au moins une question sans réponse correcte définie. Assurez-vous que toutes les questions comprennent la réponse: [Lettre] ligne.";
 $SearchCourseBySession = "Recherche de cours par session";
@@ -837,10 +837,10 @@ $AllowVisitors = "Permettre des visiteurs externes";
 $EnableIframeInclusionComment = "Autoriser les balises iframe dans l'éditeur HTML améliore l'édition de documents mais peut représenter un risque pour la sécurité.";
 $AddedToLPCannotBeAccessed = "Cet exercice fait partie d'un parcours d'apprentissage, il n'est donc pas accessible par les étudiants depuis cette page. Si vous voulez rendre cet exercice disponible depuis l'outil exercice, vous devez en faire une copie en utilisant l'icône \"Copie\"";
 $EnableIframeInclusionTitle = "Autoriser les balises iframe dans l'éditeur HTML.";
-$MailTemplateRegistrationMessage = "Cher(ère) ((firstname)) ((lastname)), Vous êtes inscrit(e) sur
-((sitename) avec les paramètres suivants: Nom d'utilisateur :
-((username)) Mot de passe : ((password)) L'adresse de ((sitename)) est :
-((url)) En cas de problème, n'hésitez pas à prendre contact avec nous
+$MailTemplateRegistrationMessage = "Cher(ère) ((firstname)) ((lastname)), Vous êtes inscrit(e) sur
+((sitename) avec les paramètres suivants: Nom d'utilisateur :
+((username)) Mot de passe : ((password)) L'adresse de ((sitename)) est :
+((url)) En cas de problème, n'hésitez pas à prendre contact avec nous
 Cordialement, le responsable ((admin_name)) ((admin_surname)).";
 $Explanation = "Une fois que vous aurez cliqué sur OK, un cours contenant Tests, Documents, Parcours d'Apprentissage SCORM... sera créé. Grâce à votre identifiant de responsable du cours vous pourrez en modifier le contenu";
 $CodeTaken = "Ce code est déjà pris.<br />Utilisez le bouton de retour en arrière de votre navigateur et recommencez";
@@ -2633,14 +2633,14 @@ $NoPosts = "Aucune publication";
 $WithoutAchievedSkills = "Aucune compétence acquise";
 $TypeMessage = "Veuillez introduire votre message !";
 $ConfirmReset = "Êtes-vous sûr de vouloir supprimer tous les messages ?";
-$MailCronCourseExpirationReminderBody = "Cher/Chère %s,
-
-Nous avons remarqué que vous n'avez pas terminé le cours %s alors que sa date de fin a été établie au %s, vous laissant %s jour(s) pour le terminer. Nous vous rappelons que vous ne disposez de la possibilité de suivre ce cours qu'une fois par an. Nous vous invitons donc avec insistance à le compléter dans le délai qu'il vous reste. Vous pouvez retrouver le cours en vous connectant à la plate-forme à cette adresse: %s
-
--- 
-
-Cordialement,
-
+$MailCronCourseExpirationReminderBody = "Cher/Chère %s,
+
+Nous avons remarqué que vous n'avez pas terminé le cours %s alors que sa date de fin a été établie au %s, vous laissant %s jour(s) pour le terminer. Nous vous rappelons que vous ne disposez de la possibilité de suivre ce cours qu'une fois par an. Nous vous invitons donc avec insistance à le compléter dans le délai qu'il vous reste. Vous pouvez retrouver le cours en vous connectant à la plate-forme à cette adresse: %s
+
+-- 
+
+Cordialement,
+
 L'équipe de support de %s";
 $MailCronCourseExpirationReminderSubject = "Urgent: Rappel d'expiration prochaine du cours %s";
 $ExerciseAndLearningPath = "Exercices et parcours";
@@ -5547,7 +5547,7 @@ $AssignCoursesToHumanResourcesManager = "Assigner des cours au directeur RH";
 $TimezoneValueTitle = "Fuseau horaire";
 $TimezoneValueComment = "Le fuseau horaire de ce portail devrait être réglé sur celui du siège de l'organisation. Si vous ne configurez pas de fuseau horaire, celui du serveur sera utilisé. Si vous en configurez un, toutes les heures de cette plateforme seront affichées sur base de ce fuseau. Ce paramètre a une priorité inférieure à celui de l'utilisateur, s'il en a activé et sélectionné personnellement dans son profil étendu.";
 $UseUsersTimezoneTitle = "Utiliser les fuseaux horaires utilisateurs";
-$UseUsersTimezoneComment = "Activer la possibilité pour les utilisateurs de sélectionner leur fuseau horaire. Le champ de fuseau horaire doit être rendu visible et modifiable dans les options de profiling du panneau d'administration avant que les utilisateurs ne puissent choisir leur propre fuseau.
+$UseUsersTimezoneComment = "Activer la possibilité pour les utilisateurs de sélectionner leur fuseau horaire. Le champ de fuseau horaire doit être rendu visible et modifiable dans les options de profiling du panneau d'administration avant que les utilisateurs ne puissent choisir leur propre fuseau.
 Une fois configuré, les utilisateurs pourront voir toutes les heures du portail (heure de remise des travaux, événements, etc) converties dans leur fuseau horaire personnel.";
 $FieldTypeTimezone = "Fuseau horaire";
 $Timezone = "Fuseaux horaires";
@@ -5821,8 +5821,8 @@ $Item = "Élément";
 $ConfigureDashboardPlugin = "Configurer le plugin de tableau de bord";
 $EditBlocks = "Éditer les blocs";
 $Never = "Jamais";
-$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Cher utilisateur,
-
+$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Cher utilisateur,
+
 Votre compte a été activé. Vous pouvez à présent vous connecter et consulter vos cours.";
 $SessionFields = "Champs de session";
 $CopyLabelSuffix = "Copie";
@@ -5884,7 +5884,7 @@ $CourseSettingsRegisterDirectLink = "Si votre cours est public ou ouvert, utilis
 $DirectLink = "Lien direct";
 $here = "ici";
 $GoAheadAndBrowseOurCourseCatalogXOnceRegisteredYouWillSeeTheCourseHereX = "Révisez notre catalogue de cours %s pour vous inscrire à votre cours préféré. Une fois inscrit(e), votre cours apparaîtra %s, à la place de ce message.";
-$HelloXAsYouCanSeeYourCourseListIsEmpty = "Bonjour %s, nous vous souhaitons la bienvenue,<br />
+$HelloXAsYouCanSeeYourCourseListIsEmpty = "Bonjour %s, nous vous souhaitons la bienvenue,<br />
 Comme vous pouvez le voir, votre liste de cours est vide. C'est parce que vous ne vous êtes pas encore inscrit à un cours!";
 $UnsubscribeUsersAlreadyAddedInCourse = "Désinscrire les utilisateurs actuellement inscrits";
 $ImportUsers = "Importer des utilisateurs";
@@ -5966,11 +5966,11 @@ $ContactInformationHasBeenSent = "L'information de contact a été envoyée. Mer
 $EditExtraFieldOptions = "Modifier les options de champs";
 $ExerciseDescriptionLabel = "Description";
 $UserInactivedSinceX = "Utilisateur inactif depuis %s";
-$ContactInformationDescription = "Cher utilisateur,<br />
-<br />
-Vous êtes sur le point de commencer à utiliser l'une des meilleures plateformes e-learning de logiciel libre du marché. Comme beaucoup d'autres projets de logiciel libre, celui-ci est supporté par une grande communauté d'étudiants, d'enseignants, de développeurs et de créateurs de contenu qui aimeraient pouvoir promouvoir le projet dans les meilleures conditions.<br /><br />
-Au travers d'une meilleure connaissance de notre public et de l'un de nos plus importants utilisateurs, vous, qui gèrerez ce système e-learning, nous pourrons nous assurer de faire savoir au plus grand nombre que notre logiciel est utilisé, et nous pourrons vous informer directement sur les événements importants à vos yeux.<br /><br />
-En complétant ce formulaire, vous acceptez que l'Association Chamilo ou ses membres puissent vous envoyer des informations par courriel au sujet d'événements importants ou de mises à jours du logiciel ou de la communauté. Ceci aidera la communauté à croître comme une entité organisée au sein de laquelle l'information se propage, au travers d'un respect permanent de votre temps et de votre vie privée.<br /><br />
+$ContactInformationDescription = "Cher utilisateur,<br />
+<br />
+Vous êtes sur le point de commencer à utiliser l'une des meilleures plateformes e-learning de logiciel libre du marché. Comme beaucoup d'autres projets de logiciel libre, celui-ci est supporté par une grande communauté d'étudiants, d'enseignants, de développeurs et de créateurs de contenu qui aimeraient pouvoir promouvoir le projet dans les meilleures conditions.<br /><br />
+Au travers d'une meilleure connaissance de notre public et de l'un de nos plus importants utilisateurs, vous, qui gèrerez ce système e-learning, nous pourrons nous assurer de faire savoir au plus grand nombre que notre logiciel est utilisé, et nous pourrons vous informer directement sur les événements importants à vos yeux.<br /><br />
+En complétant ce formulaire, vous acceptez que l'Association Chamilo ou ses membres puissent vous envoyer des informations par courriel au sujet d'événements importants ou de mises à jours du logiciel ou de la communauté. Ceci aidera la communauté à croître comme une entité organisée au sein de laquelle l'information se propage, au travers d'un respect permanent de votre temps et de votre vie privée.<br /><br />
 Veuillez prendre en considération que vous n'êtes <b>pas obligé</b> de compléter ce formulaire. Si vous désirez rester anonyme, nous perdrons la possibilité de vous offrir les privilèges d'être un administrateur de portail enregistré, mais nous respecterons votre décision. Laissez simplement ce formulaire vide et cliquez sur \"Suivant\". De même, une fois l'envoi de l'information du formulaire ci-dessous confirmé, vous devrez cliquer sur \"Suivant\".";
 $CompanyActivity = "Activité de votre entreprise";
 $PleaseAllowUsALittleTimeToSubscribeYouToOneOfOurCourses = "Merci de nous donner un moment pour vous inscrire à l'un de nos cours. Si vous pensez avoir été oublié, merci de contacter les administrateurs du site. Vous pouvez généralement trouver leurs informations de contact dans le pied de page.";
@@ -6127,7 +6127,7 @@ $SSOServerUnAuthURIComment = "L'adresse de la page qui se charge de la déconnex
 $SSOServerProtocolTitle = "Protocole du serveur Single Sign On";
 $SSOServerProtocolComment = "Le protocole à préfixer au domaine du serveur Single Sign On (nous recommandons l'usage de https:// si votre serveur le permet, car tout protocole non sécurisé engendre des risques au niveau du mécanisme d'authentification).";
 $EnabledWirisTitle = "Éditeur mathématique WIRIS";
-$EnabledWirisComment = "Activer l'éditeur mathématique WIRIS. En installant ce plugin, vous obtenez l'éditeur WIRIS et WIRIS CAS.<br/>Cette activation n'est totalement réalisée que si le <a href=\"http://www.wiris.com/es/plugins3/ckeditor/download\">plugin PHP pour CKeditor WIRIS</a> a été préalablement téléchargé et décompressé dans le répertoire de Chamilo main/inc/lib/javascript/ckeditor/plugins/.<br/>
+$EnabledWirisComment = "Activer l'éditeur mathématique WIRIS. En installant ce plugin, vous obtenez l'éditeur WIRIS et WIRIS CAS.<br/>Cette activation n'est totalement réalisée que si le <a href=\"http://www.wiris.com/es/plugins3/ckeditor/download\">plugin PHP pour CKeditor WIRIS</a> a été préalablement téléchargé et décompressé dans le répertoire de Chamilo main/inc/lib/javascript/ckeditor/plugins/.<br/>
 Cela est nécessaire car Wiris est un logiciel propriétaire et ses services sont de nature <a href=\"http://www.wiris.com/store/who-pays\">commerciale</a>. Pour faire des ajustements pour le plug-in, éditer le fichier configuration.ini ou remplacer son contenu par le fichier configuration.ini.default de Chamilo.";
 $FileSavedAs = "Le fichier a été enregistré sous";
 $FileExportAs = "Le fichier a été exporté sous";
@@ -6152,9 +6152,9 @@ $AverageScore = "Score moyen";
 $LastConnexionDate = "Date de dernière connexion";
 $ToolVideoconference = "Vidéoconférence";
 $BigBlueButtonEnableTitle = "Outil de vidéoconférence BigBlueButton";
-$BigBlueButtonEnableComment = "Choisissez si vous désirez activer l'outil de vidéoconférence BigBlueButton. Une fois activé, il apparaît comme un outil de cours additionnel sur toutes les pages d'accueil de cours, et les enseignants peuvent lancer une conférence à tout moment. Les étudiants ne peuvent pas lancer de conférence, seulement en rejoindre.
-Si vous n'avez pas de serveur BigBlueButton fonctionnel, veuillez <a target=\"_blank\" href=\"http://bigbluebutton.org/\">en installer un</a> ou vous adresser aux <a target=\"_blank\" href=\"http://www.chamilo.org/en/providers\">fournisseurs officiels de Chamilo</a> pour pouvoir bénéficier de cette fonctionnalité.
-BigBlueButton est un logiciel libre et gratuit. Son installation requiert des compétences techniques particulières, ce qui demande un travail considérable et peut résulter coûteux si vous ne disposez pas desdites compétences.
+$BigBlueButtonEnableComment = "Choisissez si vous désirez activer l'outil de vidéoconférence BigBlueButton. Une fois activé, il apparaît comme un outil de cours additionnel sur toutes les pages d'accueil de cours, et les enseignants peuvent lancer une conférence à tout moment. Les étudiants ne peuvent pas lancer de conférence, seulement en rejoindre.
+Si vous n'avez pas de serveur BigBlueButton fonctionnel, veuillez <a target=\"_blank\" href=\"http://bigbluebutton.org/\">en installer un</a> ou vous adresser aux <a target=\"_blank\" href=\"http://www.chamilo.org/en/providers\">fournisseurs officiels de Chamilo</a> pour pouvoir bénéficier de cette fonctionnalité.
+BigBlueButton est un logiciel libre et gratuit. Son installation requiert des compétences techniques particulières, ce qui demande un travail considérable et peut résulter coûteux si vous ne disposez pas desdites compétences.
 Dans la logique du développement durable de notre projet, nous vous offrons la possibilité d'installer vous-même la solution ou de vous faire aider par des professionnels à l'expérience démontrée.";
 $BigBlueButtonHostTitle = "Adresse du serveur BigBlueButton";
 $BigBlueButtonHostComment = "Veuillez indiquer l'adresse du serveur BigBlueButton. Ceci peut être <i>localhost</i>, une adresse IP (par exemple <i>192.168.13.54</i> ou un nom de domaine (par exemple <i>my.video.com</i>).";
@@ -6644,7 +6644,7 @@ $DisableEndDate = "Désactiver date de fin";
 $ForumCategories = "Catégories de forums";
 $Copy = "Copie";
 $ArchiveDirCleanup = "Vidange du cache et des fichiers temporaires";
-$ArchiveDirCleanupDescr = "Chamilo garde une copie de la plupart des fichiers temporaires qu'il génère (pour les exports, copies et sauvegardes diverses) dans son répertoire app/cache/. Après un certain temps, ces fichiers peuvent s'accumuler et occuper un espace disque assez important et sans intérêt particulier. Cliquez sur le bouton ci-dessous pour vider ce répertoire immédiatement. Idéalement, cette opération devrait être exécutée automatiquement sur base régulière (cron), mais si cela représente une difficulté importante dans votre contexte, vous pouvez visiter cette page de temps en temps pour éliminer tous les fichiers temporaires du répertoire.
+$ArchiveDirCleanupDescr = "Chamilo garde une copie de la plupart des fichiers temporaires qu'il génère (pour les exports, copies et sauvegardes diverses) dans son répertoire app/cache/. Après un certain temps, ces fichiers peuvent s'accumuler et occuper un espace disque assez important et sans intérêt particulier. Cliquez sur le bouton ci-dessous pour vider ce répertoire immédiatement. Idéalement, cette opération devrait être exécutée automatiquement sur base régulière (cron), mais si cela représente une difficulté importante dans votre contexte, vous pouvez visiter cette page de temps en temps pour éliminer tous les fichiers temporaires du répertoire.
 Cette fonctionnalité nettoie également le cache des thèmes graphiques.";
 $ArchiveDirCleanupProceedButton = "Vidanger";
 $ArchiveDirCleanupSucceeded = "Le répertoire app/cache/ a été vidangé.";
@@ -6998,10 +6998,10 @@ $ShibbolethMainActivateTitle = "<h3>Configuration de l'authentification Shibbole
 $ShibbolethMainActivateComment = "<p>Vous devez, en premier lieu, configurer Shibboleth pour votre serveur web. Pour le configurer pour Chamilo.</p><h5>éditez le fichier main/auth/shibboleth/config/aai.class.php</h5><p>Modifiez les valeurs de l'objet &#36;result avec les nom des attributs retourné par votre serveur Shibboleth.</p>Les valeurs à modifier sont<ul><li>&#36;result-&gt;unique_id = 'mail';</li><li>&#36;result-&gt;firstname = 'cn';</li><li>&#36;result-&gt;lastname = 'uid';</li><li>&#36;result-&gt;email = 'mail';</li><li>&#36;result-&gt;language = '-';</li><li>&#36;result-&gt;gender = '-';</li><li>&#36;result-&gt;address = '-';</li><li>&#36;result-&gt;staff_category = '-';</li><li>&#36;result-&gt;home_organization_type = '-'; </li><li>&#36;result-&gt;home_organization = '-';</li><li>&#36;result-&gt;affiliation = '-';</li><li>&#36;result-&gt;persistent_id = '-';</li><li>...</li></ul><br/>Vous trouverez dans les <a href='settings.php?category=Shibboleth'>Plugin</a> un bouton 'Login Shibboleth', paramétrable, qui s'ajoutera sur la page d'accueil de votre campus Chamilo.";
 $LdapDescriptionTitle = "Identification LDAP";
 $FacebookMainActivateTitle = "<h3>Configuration de l'authentification via Facebook</h3>";
-$FacebookMainActivateComment = "<p><h5>Créez votre application Facebook</h5>Vous devez, d'abord, créer une application Facebook (cf. <a href='https://developers.facebook.com/apps'>https://developers.facebook.com/apps</a>) avec votre compte Facebook.<br/>
-<h5>Éditez le fichier app/config/configuration.php</h5>Et décommentez la ligne &#36;_configuration['facebook_auth'] = 1;<br/>
-<h5>Éditez le fichier app/config/auth.conf.php<br/></h5>Entrez les valeurs 'appId' et 'secret', fournies par Facebook, pour la variable &#36;facebook_config.<br/>
-<h5>Éditez le fichier main/inc/conf/auth.conf.php</h5>Et décommentez les lignes &#36;facebook_config<br/>
+$FacebookMainActivateComment = "<p><h5>Créez votre application Facebook</h5>Vous devez, d'abord, créer une application Facebook (cf. <a href='https://developers.facebook.com/apps'>https://developers.facebook.com/apps</a>) avec votre compte Facebook.<br/>
+<h5>Éditez le fichier app/config/configuration.php</h5>Et décommentez la ligne &#36;_configuration['facebook_auth'] = 1;<br/>
+<h5>Éditez le fichier app/config/auth.conf.php<br/></h5>Entrez les valeurs 'appId' et 'secret', fournies par Facebook, pour la variable &#36;facebook_config.<br/>
+<h5>Éditez le fichier main/inc/conf/auth.conf.php</h5>Et décommentez les lignes &#36;facebook_config<br/>
 <h5>Activez le plugin Facebook de Chamilo<br/></h5>Et placez-le dans la région login_top ou login_bottom.<br/>Vous pouvez changer l'image de connexion dans les options de configuration du plugin.</p>";
 $AnnouncementForGroup = "Annonce pour un groupe";
 $AllGroups = "Tous les groupes";
@@ -7435,7 +7435,7 @@ $CronRemindCourseExpirationFrequencyComment = "Nombre de jours avant la fin du c
 $CronCourseFinishedActivateText = "Cron de fin du cours";
 $CronCourseFinishedActivateComment = "Activez pour envoyer un courriel lorsque le cours est terminé";
 $MailCronCourseFinishedSubject = "Fin du cours: %s";
-$MailCronCourseFinishedBody = "Cher/Chère %s,<br/>Merci d'avoir participé au cours %s. Nous espérons que vous avez acquis de nouvelles connaissances et que vous avez pleinement profité du cours. Vous pouvez réviser votre progrès et votre développement personnel au sein du cours dans la section Mon Suivi.<br/>Cordialement,<br/>
+$MailCronCourseFinishedBody = "Cher/Chère %s,<br/>Merci d'avoir participé au cours %s. Nous espérons que vous avez acquis de nouvelles connaissances et que vous avez pleinement profité du cours. Vous pouvez réviser votre progrès et votre développement personnel au sein du cours dans la section Mon Suivi.<br/>Cordialement,<br/>
 %s";
 $GenerateDefaultContent = "Générer du contenu par défaut";
 $ThanksForYourSubscription = "Merci pour votre inscription";
@@ -7817,7 +7817,7 @@ $TooManyRepetitions = "Trop de répétitions";
 $YourPasswordContainsSequences = "Votre mot de passe contient des séquences";
 $PasswordVeryWeak = "Très faible";
 $UserXHasBeenAssignedToBoss = "L'apprenant %s vous a été assigné";
-$UserXHasBeenAssignedToBossWithUrlX = "Vous avez été assigné comme tuteur pour l'apprenant %s.<br/>
+$UserXHasBeenAssignedToBossWithUrlX = "Vous avez été assigné comme tuteur pour l'apprenant %s.<br/>
 Vous pouvez accéder à sa fiche ici: %s";
 $ShortName = "Nom court";
 $Portal = "Portail";
@@ -7973,13 +7973,13 @@ $AddHrmToUser = "Ajouter Directeur des Ressources Humaines";
 $HrmAssignedUsersCourseList = "Liste des cours des utilisateurs assignés au directeur des ressources humaines";
 $GoToSurvey = "Aller à l'enquête";
 $NotificationCertificateSubject = "Avis d'obtention de certificat";
-$NotificationCertificateTemplate = "Cher/Chère ((user_first_name)), vous avez terminé ((course_title)). Votre note finale est de ((score)). Merci de laisser passer quelques jours avant l'apparition de le visualiser sur notre système. Nous espérons que vous en ayez pleinement profité et que nous vous reverrons bientôt dans l'un de vos prochains cours. Si nous pouvons vous être utile d'une quelconque manière, n'hésitez pas à nous contacter.
+$NotificationCertificateTemplate = "Cher/Chère ((user_first_name)), vous avez terminé ((course_title)). Votre note finale est de ((score)). Merci de laisser passer quelques jours avant l'apparition de le visualiser sur notre système. Nous espérons que vous en ayez pleinement profité et que nous vous reverrons bientôt dans l'un de vos prochains cours. Si nous pouvons vous être utile d'une quelconque manière, n'hésitez pas à nous contacter.
 Cordialement, ((author_first_name)) ((author_last_name)), ((portal_name))";
 $SendCertificateNotifications = "Envoyer les avis de certificat à tous les utilisateurs";
 $MailSubjectForwardShort = "Tr";
 $ForwardedMessage = "Message transféré";
 $ForwardMessage = "Transférer message";
-$MyCoursePageCategoryIntroduction = "Vous trouverez ci-dessous la liste des catégories de cours.
+$MyCoursePageCategoryIntroduction = "Vous trouverez ci-dessous la liste des catégories de cours.
 Cliquez sur l'une pour voir la liste des cours qu'elle contient.";
 $FeatureDisabledByAdministrator = "Fonctionnalité désactivée par l'administrateur";
 $SubscribeUsersToLpCategory = "Inscrire les utilisateurs à la catégorie";
@@ -8084,10 +8084,10 @@ $RedirectToTheDocumentList = "Rediriger vers la liste des documents";
 $TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToTheExerciseList = "La fonctionnalité d'auto-démarrage des exercices est activée. Les apprenants seront automatiquement redirigés vers la liste des exercices.";
 $PostedExpirationDate = "Date limite affichée d'envoi du travail (visible par l'apprenant)";
 $BossAlertMsgSentToUserXTitle = "Message de suivi concernant l'apprenant %s";
-$BossAlertUserXSentMessageToUserYWithLinkZ = "Bonjour,<br/><br/>
-
-L'utilisateur %s a envoyé un message de suivi concernant l'apprenant %s.<br/><br/>
-
+$BossAlertUserXSentMessageToUserYWithLinkZ = "Bonjour,<br/><br/>
+
+L'utilisateur %s a envoyé un message de suivi concernant l'apprenant %s.<br/><br/>
+
 Le message est visible sur %s";
 $include_services = "Inclure les services";
 $culqi_enable = "Activé culqi";
@@ -8190,8 +8190,8 @@ $SubSkill = "Sous-compétence";
 $AddMultipleUsersToCalendar = "Ajouter plusieurs utilisateurs au calendrier";
 $UpdateCalendar = "Mettre le calendrier à jour";
 $ControlPoint = "Point de contrôle";
-$MessageQuestionCertainty = "Suivez les instructions ci-dessous pour visualiser vos résultats pour le test %s:<br /><br />
-1. Connectez-vous à la plateforme (utilisateur/mot de passe) sur: %s<br />
+$MessageQuestionCertainty = "Suivez les instructions ci-dessous pour visualiser vos résultats pour le test %s:<br /><br />
+1. Connectez-vous à la plateforme (utilisateur/mot de passe) sur: %s<br />
 2. Suivez le lien suivant: %s<br />";
 $SessionMinDuration = "Durée minimum de session";
 $CanNotTranslate = "Impossible de traduire";
@@ -8208,7 +8208,7 @@ $ScoreModel = "Modèle d'évaluation";
 $SyncDatabaseWithSchema = "Synchroniser la base de données avec le schéma";
 $SkillRelCourses = "Associations cours-compétences";
 $UserRequestWaitingForAction = "Un(e) utilisateur/trice attend une action de votre part à propos d'une demande au sujet de ses données personnelles";
-$TheUserXIsWaitingForAnActionGoHereX = "L'utilisateur/trice %s attend une action au sujet de sa demande de données personnelles.\n\n
+$TheUserXIsWaitingForAnActionGoHereX = "L'utilisateur/trice %s attend une action au sujet de sa demande de données personnelles.\n\n
 Pour gérer les demandes de données personnelles, suivez ce lien: %s";
 $RequestType = "Type de demande";
 $DeleteAccount = "Supprimer le compte";
@@ -8217,8 +8217,8 @@ $RemoveTerms = "Supprimer l'accord légal";
 $InformationRightToBeForgottenLinkX = "Vous trouverez plus d'information au sujet du droit à l'oubli des utilisateurs via le lien suivant: %s";
 $ExplanationDeleteLegal = "Merci de bien vouloir nous donner une brève explication sur la raison de votre demande de retrait des permissions que vous nous aviez précédemment donné afin que nous puissions procéder à la modification de la meilleure manière possible.";
 $ExplanationDeleteAccount = "Merci de bien vouloir nous donner une brève explication sur la raison de votre demande de suppression de votre compte, afin que nous puissions y procéder de la meilleure manière possible.";
-$WhyYouWantToDeleteYourLegalAgreement = "Ci-dessous, vous pouvez faire une demande de retrait de votre accord avec nos conditions d'utilisation ou de suppression de votre compte.<br />
-Dans le cas de l'accord avec nos conditions, une fois annulé, vous aurez à accepter à nouveau ces conditions lors de votre prochaine connexion afin d'accéder à notre plateforme, car il ne nous est pas possible de vous donner accès à une expérience personnalisée sans en même temps traiter vos données personnelles.<br />
+$WhyYouWantToDeleteYourLegalAgreement = "Ci-dessous, vous pouvez faire une demande de retrait de votre accord avec nos conditions d'utilisation ou de suppression de votre compte.<br />
+Dans le cas de l'accord avec nos conditions, une fois annulé, vous aurez à accepter à nouveau ces conditions lors de votre prochaine connexion afin d'accéder à notre plateforme, car il ne nous est pas possible de vous donner accès à une expérience personnalisée sans en même temps traiter vos données personnelles.<br />
 Dans le cas d'une demande de suppression de votre compte, celui-ci sera effectivement supprimé, incluant toutes vos souscriptions à des cours et toute l'information liée à votre compte. Veuillez sélectionner l'option correspondante avec soin. Dans les deux cas, l'un de nos administrateurs révisera votre demande avant de l'exécuter afin d'éviter quelque malentendu que ce soit et la perte définitive de vos données si ce n'était pas voulu.";
 $PersonalDataPrivacy = "Protection des données personnelles";
 $RequestForAccountDeletion = "Demande de suppression de compte";
@@ -8246,11 +8246,13 @@ $ProgressObtainedFromLPProgressAndTestsAverage = "Note: Le progrès ci-dessus es
 $CreateNewSurveyDoodle = "Créer une nouvelle enquête de type Doodle";
 $RemoveMultiplicateQuestions = "Supprimer les questions démultipliées";
 $MultiplicateQuestions = "Multiplier les questions";
-$QuestionTags = "Vous pouvez utiliser les balises {{class_name}} et {{student_full_name}} dans la question afin de multiplier les questions.
-Sur la page de liste d'enquêtes dans la colonne d'action, vous avez un bouton pour multiplier les questions qui recherchera la balise {{class_name}} et dupliquera la question pour toutes les classes inscrites au cours et la renommera avec le nom de la classe.
-Il ajoutera également un séparateur de page pour créer une nouvelle page pour chaque classe.
+$QuestionTags = "Vous pouvez utiliser les balises {{class_name}} et {{student_full_name}} dans la question afin de multiplier les questions.
+Sur la page de liste d'enquêtes dans la colonne d'action, vous avez un bouton pour multiplier les questions qui recherchera la balise {{class_name}} et dupliquera la question pour toutes les classes inscrites au cours et la renommera avec le nom de la classe.
+Il ajoutera également un séparateur de page pour créer une nouvelle page pour chaque classe.
 Ensuite, il recherchera la balise {{student_full_name}} et dupliquera la question pour tous les étudiants de la classe (pour chaque classe) et la renommera avec le nom complet de l'étudiant.";
 $CreateMeeting = "Créer un sondage de réunion";
 $QuestionForNextClass = "Questions pour la classe suivante";
 $NewExerciseAttemptDisabled = "Le portail ne permet pas le démarrage d'exercice pour le moment, revenez un peu plus tard pour essayer à nouveau";
+$PersonalDataOfficer = "Délégué à la protection des données personnelles";
+$MaxNumberSubscribedStudentsReached = "Le nombre maximum d'apprenants a déjà été atteint, il n'est pas possible d'en inscrire de nouveau";
 ?>

+ 163 - 155
main/lang/spanish/trad4all.inc.php

@@ -265,11 +265,11 @@ $AreYouSureDeleteTestResultBeforeDateD = "¿Está seguro que desea eliminar los
 $CleanStudentsResultsBeforeDate = "Eliminar todos los resultados antes de la fecha selecionada";
 $HGlossary = "Ayuda del glosario";
 $GlossaryContent = "Esta herramienta le permite crear términos de glosario para su curso, los cuales pueden luego ser usados en la herramienta de documentos";
-$ForumContent = "<p>El foro es una herramienta de conversación para el trabajo escrito asíncrono. A diferencia del e-mail, un foro es para conversaciones públicas, semi-públicas o de grupos.</p>
-<p>Para usar el foro de Chamilo, los alumnos del curso pueden simplemente usar su navegador - no requieren de ningun otro tipo de herramienta</p>
-<p>Para organizar los foros, dar click en la herramienta de foros. Las conversaciones se organizan según la estructura siguiente: Categoría > Foro > Tema de conversación > Respuesta. Para permitir a los alumnos participar en los foros de manera ordenada y efectiva, es esencial crear primero categorías y foros. Luego, pertenece a los participantes crear temas de conversación y enviar respuestas. Por defecto, si el curso se ha creado con contenido de ejemplo, el foro contiene una única categoría, un foro, un tema de foro y una respuesta. Puede añadir foros a la categoría, cambiar su titulo o crear otras categorías dentro de las cuales podría entonces crear nuevos foros (no confunda categorías y foros, y recuerde que una categoría que no contiene foros es inútil y no se mostrará a los alumnos).</p>
-<p>La descripción del foro puede incluir una lista de sus miembros, una definición de su propósito, una tarea, un objetivo, un tema, etc.</p>
-<p>Los foros de grupos no son creados por la herramienta de foros directamente, sino por la herramienta de grupos, donde puede determinar si los foros serán públicos o privados, permitiendo al mismo tiempo a los miembros sus grupos compartir documentos y otros recursos</p>
+$ForumContent = "<p>El foro es una herramienta de conversación para el trabajo escrito asíncrono. A diferencia del e-mail, un foro es para conversaciones públicas, semi-públicas o de grupos.</p>
+<p>Para usar el foro de Chamilo, los alumnos del curso pueden simplemente usar su navegador - no requieren de ningun otro tipo de herramienta</p>
+<p>Para organizar los foros, dar click en la herramienta de foros. Las conversaciones se organizan según la estructura siguiente: Categoría > Foro > Tema de conversación > Respuesta. Para permitir a los alumnos participar en los foros de manera ordenada y efectiva, es esencial crear primero categorías y foros. Luego, pertenece a los participantes crear temas de conversación y enviar respuestas. Por defecto, si el curso se ha creado con contenido de ejemplo, el foro contiene una única categoría, un foro, un tema de foro y una respuesta. Puede añadir foros a la categoría, cambiar su titulo o crear otras categorías dentro de las cuales podría entonces crear nuevos foros (no confunda categorías y foros, y recuerde que una categoría que no contiene foros es inútil y no se mostrará a los alumnos).</p>
+<p>La descripción del foro puede incluir una lista de sus miembros, una definición de su propósito, una tarea, un objetivo, un tema, etc.</p>
+<p>Los foros de grupos no son creados por la herramienta de foros directamente, sino por la herramienta de grupos, donde puede determinar si los foros serán públicos o privados, permitiendo al mismo tiempo a los miembros sus grupos compartir documentos y otros recursos</p>
 <p><b>Tips de enseñanza:</b> Un foro de aprendizaje no es lo mismo que un foro de los que ve en internet. De un lado, no es posible que los alumnos modifiquen sus respuestas una vez que un tema de conversación haya sido cerrado. Esto es con el objetivo de valorar su contribución en el foro. Luego, algunos usuarios privilegiados (profesor, tutor, asistente) pueden corregir directamente las respuestas dentro del foro.<br />Para hacerlo, pueden seguir el procedimiento siguiente:<br />dar click en el icono de edición (lapiz amarillo) y marcarlo usando una funcionalidad de edición (color, subrayado, etc). Finalmente, otros alumnos pueden beneficiar de esta corrección visualizando el foro nuevamente. La misa idea puede ser aplicada entre alumnos pero requiere usar la herramienta de citación para luego indicar los elementos incorrectos (ya que no pueden editar directamente la respuesta de otro alumno)</p>";
 $HForum = "Ayuda del foro";
 $LoginToGoToThisCourse = "Conectarse para entrar al curso";
@@ -330,18 +330,18 @@ $DeleteUsersNotInList = "Desinscribir los alumnos que no están en una sesión e
 $IfSessionExistsUpdate = "Si la sesión existe, actualizarla con los datos del archivo";
 $CreatedByXYOnZ = "Creado/a por <a href=\"%s\">%s</a> el %s";
 $LoginWithExternalAccount = "Ingresar con una cuenta externa";
-$ImportAikenQuizExplanationExample = "Este es el texto de la pregunta 1
-A. Respuesta 1
-B. Respuesta 2
-C. Respuesta 3
-ANSWER: B
-
-Este es el texto de la pregunta 2 (notese la línea blanca arriba)
-A. Respuesta 1
-B. Respuesta 2
-C. Respuesta 3
-D. Respuesta 4
-ANSWER: D
+$ImportAikenQuizExplanationExample = "Este es el texto de la pregunta 1
+A. Respuesta 1
+B. Respuesta 2
+C. Respuesta 3
+ANSWER: B
+
+Este es el texto de la pregunta 2 (notese la línea blanca arriba)
+A. Respuesta 1
+B. Respuesta 2
+C. Respuesta 3
+D. Respuesta 4
+ANSWER: D
 ANSWER_EXPLANATION: Este es un texto opcional de retroalimentación que aparecerá al costado de la respuesta correcta.";
 $ImportAikenQuizExplanation = "El formato Aiken es un simple formato texto (archivo .txt) con varios bloques de preguntas, cada bloque separado por una línea blanca. La primera línea es la pregunta. Las líneas de respuestas tienen un prefijo de letra y punto, y la respuesta correcta sigue, con el prefijo 'ANSWER:'. Ver ejemplo a continuación.";
 $ExerciseAikenErrorNoAnswerOptionGiven = "El archivo importado tiene por lo menos una pregunta sin respuesta (o las respuestas no incluyen la letra de prefijo requerida). Asegúrese de que cada pregunta tengo por lo mínimo una respuesta y que esté prefijada por una letra y un punto o una paréntesis, como sigue: A. Respuesta uno";
@@ -431,16 +431,16 @@ $LatestVersionIs = "La última versión es";
 $YourVersionNotUpToDate = "Su versión está actualizada";
 $Hotpotatoes = "Hotpotatoes";
 $ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected = "-1 = Todas las preguntas serán seleccionadas. 0 = Ninguna pregunta será seleccionada.";
-$EmailNotificationTemplateDescription = "Puede modificar el correo enviado a los usuarios al terminar el ejercicio. Puede usar los siguientes términos: 
-
-{{ student.username }}
-{{ student.firstname }} 
-{{ student.lastname }}
-{{ student.official_code }} 
-{{ exercise.title }} 
-{{ exercise.start_time }}
-{{ exercise.end_time }}
-{{ course.title }}
+$EmailNotificationTemplateDescription = "Puede modificar el correo enviado a los usuarios al terminar el ejercicio. Puede usar los siguientes términos: 
+
+{{ student.username }}
+{{ student.firstname }} 
+{{ student.lastname }}
+{{ student.official_code }} 
+{{ exercise.title }} 
+{{ exercise.start_time }}
+{{ exercise.end_time }}
+{{ course.title }}
 {{ course.code }}";
 $EmailNotificationTemplate = "Plantilla del correo electrónico enviado al usuario al terminar el ejercicio.";
 $ExerciseEndButtonDisconnect = "Desconexión de la plataforma";
@@ -2640,16 +2640,16 @@ $NoPosts = "Sin publicaciones";
 $WithoutAchievedSkills = "Sin competencias logradas";
 $TypeMessage = "Por favor, escriba su mensaje";
 $ConfirmReset = "¿Seguro que quiere borrar todos los mensajes?";
-$MailCronCourseExpirationReminderBody = "Estimado %s,
-
-Ha llegado a nuestra atención que no ha completado el curso %s aunque su fecha de vencimiento haya sido establecida al %s, quedando %s días para terminarlo.
-
-Le recordamos que solo tiene la posibilidad de seguir este curso una vez al año, razón por la cual le invitamos con insistencia a completar su curso en el plazo que queda.
-
-Puede regresar al curso conectándose a la plataforma en esta dirección: %s
-
-Saludos cordiales,
-
+$MailCronCourseExpirationReminderBody = "Estimado %s,
+
+Ha llegado a nuestra atención que no ha completado el curso %s aunque su fecha de vencimiento haya sido establecida al %s, quedando %s días para terminarlo.
+
+Le recordamos que solo tiene la posibilidad de seguir este curso una vez al año, razón por la cual le invitamos con insistencia a completar su curso en el plazo que queda.
+
+Puede regresar al curso conectándose a la plataforma en esta dirección: %s
+
+Saludos cordiales,
+
 El equipo de %s";
 $MailCronCourseExpirationReminderSubject = "Urgente: Recordatorio de vencimiento de curso %s";
 $ExerciseAndLearningPath = "Ejercicios y lecciones";
@@ -5829,7 +5829,7 @@ $Item = "Ítem";
 $ConfigureDashboardPlugin = "Configurar el plugin del Panel de control";
 $EditBlocks = "Editar bloques";
 $Never = "Nunca";
-$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Estimado usuario <br><br>
+$YourAccountIsActiveYouCanLoginAndCheckYourCourses = "Estimado usuario <br><br>
 Usted no esta activo en la plataforma, por favor inicie sesión nuevamente y revise sus cursos";
 $SessionFields = "Campos de sesión";
 $CopyLabelSuffix = "Copia";
@@ -5887,12 +5887,12 @@ $SearchProfileMatches = "Buscar perfiles que correspondan";
 $IsThisWhatYouWereLookingFor = "Corresponde a lo que busca?";
 $WhatSkillsAreYouLookingFor = "Que competencias busca?";
 $ProfileSearch = "Búsqueda de perfil";
-$CourseSettingsRegisterDirectLink = "Si su curso es público o abierto, puede usar el enlace directo abajo para invitar a nuevos usuarios, de tal manera que estén enviados directamente en este curso al finalizar el formulario de registro al portal. Si desea, puede añadir el parámetro e=1 a este enlace, remplazando \"1\" por el ID del ejercicio, para mandar los usuarios directamente a un ejercicio o examen. El ID del ejercicio se puede obtener en la URL del ejercicio cuando le de clic para entrar al mismo.<br />
+$CourseSettingsRegisterDirectLink = "Si su curso es público o abierto, puede usar el enlace directo abajo para invitar a nuevos usuarios, de tal manera que estén enviados directamente en este curso al finalizar el formulario de registro al portal. Si desea, puede añadir el parámetro e=1 a este enlace, remplazando \"1\" por el ID del ejercicio, para mandar los usuarios directamente a un ejercicio o examen. El ID del ejercicio se puede obtener en la URL del ejercicio cuando le de clic para entrar al mismo.<br />
 %s";
 $DirectLink = "Enlace directo";
 $here = "aqui";
 $GoAheadAndBrowseOurCourseCatalogXOnceRegisteredYouWillSeeTheCourseHereX = "Adelante, pulsa %s para acceder al catálogo de cursos e inscribirte en un curso que te interese. Una vez inscrito/a, el curso aparecerá en esta pantalla en lugar de este mensaje.";
-$HelloXAsYouCanSeeYourCourseListIsEmpty = "Hola %s, te damos la bienvenida,<br />
+$HelloXAsYouCanSeeYourCourseListIsEmpty = "Hola %s, te damos la bienvenida,<br />
 Como puedes ver, tu lista de cursos todavía está vacía. Esto es porque todavía no estás inscrito/a en ningún curso.";
 $UnsubscribeUsersAlreadyAddedInCourse = "Desinscribir todos los alumnos ya inscritos";
 $ImportUsers = "Importar usuarios";
@@ -6104,7 +6104,7 @@ $CourseRequestLegalNote = "La información de esta solicitud de formación es co
 $EnableCourseValidation = "Solicitud de cursos";
 $EnableCourseValidationComment = "Cuando la solicitud de cursos está activada, un profesor no podrá crear un curso por si mismo sino que tendrá que rellenar una solicitud. El administrador de la plataforma revisará la solicitud y la aprobará o rechazará. Esta funcionalidad se basa en mensajes de correo electrónico automáticos por lo que debe asegurarse de que su instalación de Chamilo usa un servidor de correo y una dirección de correo dedicada a ello.";
 $CourseRequestAskInfoEmailSubject = "%s Solicitud de informacion %s";
-$CourseRequestAskInfoEmailText = "Hemos recibido su solicitud para la creación de un curso con el código %s. Antes de considerar su aprobación necesitamos alguna información adicional.\n\n
+$CourseRequestAskInfoEmailText = "Hemos recibido su solicitud para la creación de un curso con el código %s. Antes de considerar su aprobación necesitamos alguna información adicional.\n\n
 Por favor, realice una breve descripción del contenido del curso, los objetivos y los estudiantes u otro tipo de usuarios que vayan a participar. Si es su caso, mencione el nombre de la institución u órgano en nombre de la cual Usted ha hecho la solicitud.";
 $CourseRequestAcceptedEmailSubject = "%s La petición del curso %s ha sido aprobada";
 $CourseRequestAcceptedEmailText = "Su solicitud del curso %s ha sido aprobada. Un nuevo curso %s ha sido creado y Usted ha quedado inscrito en él como profesor.\n\nPodrá acceder al curso creado desde: %s";
@@ -6155,9 +6155,9 @@ $AverageScore = "Puntuación media";
 $LastConnexionDate = "Fecha de la última conexión";
 $ToolVideoconference = "Videoconferencia";
 $BigBlueButtonEnableTitle = "Herramienta de videoconferencia BigBlueButton";
-$BigBlueButtonEnableComment = "Seleccione si desea habilitar la herramienta de videoconferencia BigBlueButton. Una vez activada, se mostrará como una herramienta en la página principal todos los curso. Los profesores podrán lanzar una videoconferencia en cualquier momento, pero los estudiantes sólo podrán unirse a una ya lanzada.
-Si no dispone de un servidor BigBlueButton, pruebe a 
-<a href=\"http://bigbluebutton.org/\" target=\"_blank\">configurar uno</a> o pida ayuda a los <a href=\"http://www.chamilo.org/en/providers\" target=\"_blank\">proveedores oficiales de Chamilo</a>.
+$BigBlueButtonEnableComment = "Seleccione si desea habilitar la herramienta de videoconferencia BigBlueButton. Una vez activada, se mostrará como una herramienta en la página principal todos los curso. Los profesores podrán lanzar una videoconferencia en cualquier momento, pero los estudiantes sólo podrán unirse a una ya lanzada.
+Si no dispone de un servidor BigBlueButton, pruebe a 
+<a href=\"http://bigbluebutton.org/\" target=\"_blank\">configurar uno</a> o pida ayuda a los <a href=\"http://www.chamilo.org/en/providers\" target=\"_blank\">proveedores oficiales de Chamilo</a>.
 BigBlueButton es libre, pero su instalación requiere ciertas habilidades técnicas que no todo el mundo posee. Puede instalarlo por su cuenta o buscar ayuda profesional con el consiguiente costo. En la lógica del software libre, nosotros le ofrecemos las herramientas para hacer más fácil su trabajo y le recomendamos profesionales (los proveedores oficiales de Chamilo) que serán capaces de ayudarle.";
 $BigBlueButtonHostTitle = "Servidor BigBlueButton";
 $BigBlueButtonHostComment = "Este es el nombre del servidor donde su servidor BigBlueButton está ejecutándose. Puede ser localhost, una dirección IP (ej., 192.168.14.54) o un nombre de dominio (por ej., my.video.com).";
@@ -6168,14 +6168,14 @@ $OnlyAccessFromYourGroup = "Sólo accesible desde su grupo";
 $CreateAssignmentPage = "Esto creará una página wiki especial en la que el profesor describe la tarea, la cual se enlazará automáticamente a las páginas wiki donde los estudiantes la realizarán. Tanto las página del docente como la de los estudiantes se crean automáticamente. En este tipo de tareas los estudiantes sólo podrán editar y ver sus páginas, aunque esto puede cambiarlo si lo desea.";
 $UserFolders = "Carpetas de los usuarios";
 $UserFolder = "Carpeta del usuario";
-$HelpUsersFolder = "INFORMACIÓN SOLO VISIBLE POR EL PROFESORADO:\nLa carpeta de los usuarios contiene una carpeta de cada usuario que haya accedido a ella a través de la herramienta documentos, o bien que haya enviado algún fichero al curso a través del editor, salvo desde la herramienta grupos. Si ninguna de las dos circuntancias se ha producido la carpeta del usuario no estará creada. En el caso de los grupos, los archivos que se envíen a través del editor se depositarán en la carpeta de cada grupo, la cual sólo será accesible por los alumnos desde la herramienta grupos.
-
-La carpeta de los usuarios y las carpetas que contiene de cada uno de ellos, se mantendrán por ocultas por defecto, si bien cada alumno podrá ver el contenido de la suya cuando acceda a inspeccionar los archivos del servidor a través del editor. No obstante, si un alumno conoce la dirección de un archivo de la carpeta de otro alumno podrá visualizarlo.
-
-Si se hace visible la carpeta de los usuarios y la carpeta de uno o más alumnos, el resto de los alumnos podrán ver todo su contenido. En este caso, el alumno propietario de la carpeta también podrá desde la herramienta documentos (sólo dentro de su carpeta): crear y editar documentos web, convertir un documento web en una plantilla para uso personal, crear y editar dibujos SVG y PNG, grabar archivos de audio en formato WAV, convertir texto en audio en formato MP3, realizar capturas a través de su webcam, enviar documentos, crear carpetas, mover carpetas y archivos, borrar carpetas y archivos, y descargar copias de seguridad de su carpeta.
-
-Por otra parte, la herramienta documentos se sincroniza con el gestor de archivos del editor web, así que los cambios en la gestión de los documentos realizados en una u otra afectarán a ambas. 
-
+$HelpUsersFolder = "INFORMACIÓN SOLO VISIBLE POR EL PROFESORADO:\nLa carpeta de los usuarios contiene una carpeta de cada usuario que haya accedido a ella a través de la herramienta documentos, o bien que haya enviado algún fichero al curso a través del editor, salvo desde la herramienta grupos. Si ninguna de las dos circuntancias se ha producido la carpeta del usuario no estará creada. En el caso de los grupos, los archivos que se envíen a través del editor se depositarán en la carpeta de cada grupo, la cual sólo será accesible por los alumnos desde la herramienta grupos.
+
+La carpeta de los usuarios y las carpetas que contiene de cada uno de ellos, se mantendrán por ocultas por defecto, si bien cada alumno podrá ver el contenido de la suya cuando acceda a inspeccionar los archivos del servidor a través del editor. No obstante, si un alumno conoce la dirección de un archivo de la carpeta de otro alumno podrá visualizarlo.
+
+Si se hace visible la carpeta de los usuarios y la carpeta de uno o más alumnos, el resto de los alumnos podrán ver todo su contenido. En este caso, el alumno propietario de la carpeta también podrá desde la herramienta documentos (sólo dentro de su carpeta): crear y editar documentos web, convertir un documento web en una plantilla para uso personal, crear y editar dibujos SVG y PNG, grabar archivos de audio en formato WAV, convertir texto en audio en formato MP3, realizar capturas a través de su webcam, enviar documentos, crear carpetas, mover carpetas y archivos, borrar carpetas y archivos, y descargar copias de seguridad de su carpeta.
+
+Por otra parte, la herramienta documentos se sincroniza con el gestor de archivos del editor web, así que los cambios en la gestión de los documentos realizados en una u otra afectarán a ambas. 
+
 Así pues, la carpeta de usuario no sólo es un lugar para depositar los archivos, sino que se convierte en un completo gestor de los documentos que los estudiantes utilizan durante el curso. Además, recuerde que cualquier usuario podrá copiar un archivo, que sea visible, de cualquier carpeta de la herramienta documentos (sea o no la suya) a su portafolios o área personal de documentos de la red social, donde estará disponible para que lo pueda usar en otros cursos.";
 $HelpFolderChat = "INFORMACIÓN SOLO VISIBLE POR EL PROFESORADO:\nEsta carpeta contiene todas las sesiones que se han realizado en el chat. Aunque muchas veces las sesiones en el chat pueden ser triviales, en otras pueden ser dignas de ser tratadas como un documento más de trabajo. Para ello, sin cambiar la visibilidad de esta carpeta, haga visible el archivo y enlácelo donde considere oportuno. No se recomienda hacer visible esta carpeta.";
 $HelpFolderCertificates = "INFORMACIÓN SOLO VISIBLE POR EL PROFESORADO:\nEsta carpeta contiene los distintos modelos de certificados que se han creado para la herramienta Evaluaciones. No se recomienda hacer visible esta carpeta.";
@@ -6189,7 +6189,7 @@ $UploadCorrections = "Subir correcciones";
 $Text2AudioTitle = "Activar servicios de conversión de texto en audio";
 $Text2AudioComment = "Herramienta on-line para convertir texto en voz. Utiliza tecnología y sistemas de síntesis del habla para ofrecer recursos de voz.";
 $ShowUsersFoldersTitle = "Mostrar las carpetas de los usuarios en la herramienta documentos";
-$ShowUsersFoldersComment = "
+$ShowUsersFoldersComment = "
 Esta opción le permitirá mostrar u ocultar a los profesores las carpetas que el sistema genera para cada usuario que visita la herramienta documentos o envía un archivo a través del editor web. Si muestra estas carpetas a los profesores, éstos podrán hacerlas visibles o no a los estudiantes y permitirán a cada estudiante tener un lugar específico en el curso donde, no sólo almacenar documentos, sino donde también podrán crear y modificar páginas web y poder exportarlas a pdf, realizar dibujos, realizar plantillas web personales, enviar archivos, así como crear, mover y eliminar subdirectorios y archivos, y sacar copias de seguridad de sus carpetas. Cada usuario del curso dispondrá de un completo gestor de documentos. Además, recuerde que cualquier usuario podrá copiar un archivo, que sea visible, de cualquier carpeta de la herramienta documentos (sea o no la suya) a su portafolios o área personal de documentos de la red social, donde estará disponible para que lo pueda usar en otros cursos.";
 $ShowDefaultFoldersTitle = "Mostrar en la herramienta documentos las carpetas que contienen los recursos multimedia suministrados por defecto.";
 $ShowDefaultFoldersComment = "Las carpetas de archivos multimedia suministradas por defecto contienen archivos de libre distribución organizados en las categorías de video, audio, imagen y animaciones flash que para utilizar en sus cursos. Aunque las oculte en la herramienta documentos, podrá seguir usándolas en el editor web de la plataforma.";
@@ -6225,8 +6225,8 @@ $Pediaphon = "Usar los servicios de audio de Pediaphon";
 $HelpPediaphon = "Admite textos con varios miles de caracteres, pudiéndose seleccionar varios tipos de voz masculinas y femeninas (según el idioma). Los archivos de audio se generarán y guardarán automáticamente en el directorio de Chamilo en el que Usted actualmente se encuentra.";
 $FirstSelectALanguage = "Primero seleccione un idioma";
 $MoveUserStats = "Mover los resultados de los usuarios desde/hacia una sesión de formación";
-$CompareUserResultsBetweenCoursesAndCoursesInASession = "Esta herramienta avanzada le permite mejorar manualmente el seguimiento de los resultados de los usuarios cuando cambia de un modelo de cursos a un modelo de sesiones de formación. En una mayoría de casos, no necesitará usarla.<br />
-En esta pantalla, puede comparar los resultados que los usuarios tienen en el contexto de un curso y en el contexto del mismo curso dentro de una sesión de formación.<br />
+$CompareUserResultsBetweenCoursesAndCoursesInASession = "Esta herramienta avanzada le permite mejorar manualmente el seguimiento de los resultados de los usuarios cuando cambia de un modelo de cursos a un modelo de sesiones de formación. En una mayoría de casos, no necesitará usarla.<br />
+En esta pantalla, puede comparar los resultados que los usuarios tienen en el contexto de un curso y en el contexto del mismo curso dentro de una sesión de formación.<br />
 Una vez que decidida cuál es el mejor contexto para el seguimiento (resultados de ejercicios y seguimiento de lecciones), podrá moverlo de un curso a una sesión.";
 $PDFExportWatermarkEnableTitle = "Marcas de agua en las exportaciones a PDF";
 $PDFExportWatermarkEnableComment = "Si activa esta opción podrá cargar una imagen o un texto que serán automáticamente añadidos como marca de agua en los documentos resultantes de todas las exportaciones a PDF que realice el sistema.";
@@ -6361,8 +6361,8 @@ $MailNotifyInvitation = "Notificar las invitaciones por correo electrónico";
 $MailNotifyMessage = "Notificar los mensajes por correo electrónico";
 $MailNotifyGroupMessage = "Notificar en los grupos los mensajes por correo electrónico";
 $SearchEnabledTitle = "Búsqueda a texto completo";
-$SearchEnabledComment = "Esta funcionalidad permite la indexación de la mayoría de los documentos subidos a su portal, con lo que permite la búsqueda para los usuarios.<br />
-Esta funcionalidad no indexa los documentos que ya fueron subidos, por lo que es importante (si se quiere) activarla al comienzo de su implementación.<br />
+$SearchEnabledComment = "Esta funcionalidad permite la indexación de la mayoría de los documentos subidos a su portal, con lo que permite la búsqueda para los usuarios.<br />
+Esta funcionalidad no indexa los documentos que ya fueron subidos, por lo que es importante (si se quiere) activarla al comienzo de su implementación.<br />
 Una vez activada, una caja de búsqueda aparecerá en la lista de cursos de cada usuario. Buscar un término específico suministra una lista de documentos, ejercicios o temas de foro correspondientes, filtrados dependiendo de su disponibilidad para el usuario.";
 $SpecificSearchFieldsAvailable = "Campos de búsqueda personalizados disponibles";
 $XapianModuleInstalled = "Módulo Xapian instalado";
@@ -6660,7 +6660,7 @@ $DisableEndDate = "Deshabilitar fecha final";
 $ForumCategories = "Categorías de foro";
 $Copy = "Copiar";
 $ArchiveDirCleanup = "Limpieza de caché y archivos temporales";
-$ArchiveDirCleanupDescr = "Chamilo guarda una copia de los archivos temporales que genera (para los backups, las exportaciones, las copias, etc) dentro del directorio app/cache/. Pasado un tiempo, todo esto puede llegar a ocupar bastante espacio en el disco duro. Si hace clic en el siguiente botón ejecutará una limpieza manual de este directorio. Esta operación debería ser realizada regularmente mediante la utilidad cron de Linux, pero si esto no es posible en su entorno puede utilizar esta página para eliminar todos los archivos temporales cada cierto tiempo.
+$ArchiveDirCleanupDescr = "Chamilo guarda una copia de los archivos temporales que genera (para los backups, las exportaciones, las copias, etc) dentro del directorio app/cache/. Pasado un tiempo, todo esto puede llegar a ocupar bastante espacio en el disco duro. Si hace clic en el siguiente botón ejecutará una limpieza manual de este directorio. Esta operación debería ser realizada regularmente mediante la utilidad cron de Linux, pero si esto no es posible en su entorno puede utilizar esta página para eliminar todos los archivos temporales cada cierto tiempo.
 Esta opción limpia el caché de temas también.";
 $ArchiveDirCleanupProceedButton = "Ejecutar la limpieza";
 $ArchiveDirCleanupSucceeded = "El contenido del directorio app/cache/ ha sido eliminado.";
@@ -7009,69 +7009,69 @@ $ResourceLockedByGradebook = "Esta opción no está disponible porque la activid
 $GradebookLockedAlert = "Esta evaluación ha sido bloqueada y no puede ser desbloqueada. Si necesita realmente desbloquearla, por favor contacte el administrador de la plataforma, explicando su razón (sino podría ser considerado como un intento de fraude).";
 $GradebookEnableLockingTitle = "Activar bloqueo de Evaluaciones por los profesores";
 $GradebookEnableLockingComment = "Una vez activada, esta opción permitirá a los profesores bloquear cualquier evaluación dentro de su curso. Esto prohibirá al profesor cualquier modificación posterior de los resultados de sus alumnos en los recursos usados para esta evaluación: exámenes, lecciones, tareas, etc. El único rol autorizado a desbloquear una evaluación es el administrador. El profesor estará informado de esta posibilidad al intentar desbloquear la evaluación. El bloqueo como el desbloqueo estarán guardados en el registro de actividades importantes del sistema.";
-$LdapDescriptionComment = "<div class=\"alert alert-info\">
-        <ul>
-            <li>Autentificación LDAP: <br>
-                Véase I. a continuación para configurar LDAP<br>
-                Véase II. a continuación para activar la autentificación LDAP
-            </li>
-            <li>Actualizar los atributos de usuario, con los datos de LDAP, después de la autentificación CAS (véase <a href=\"settings.php?category=CAS\">Configuración CAS</a>): <br>
-                Véase I. a continuación para configurar LDAP<br>
-                Para gestionar la autentificación de usuarios por CAS, no se requere la activación de LDAP.
-            </li>
-        </ul>
-    </div>
-    <h4>I. Configuración de LDAP</h4>
-    <h5>Editar el archivo app/config/auth.conf.php </h5>
-    <p>-&gt; Editar los valores del array <code>\$extldap_config</code></p>
-    <ul>
-        <li>base domain string (p. ej.: 'base_dn' =&gt; 'DC=cblue,DC=be')</li>
-        <li>admin distinguished name (p. ej.: 'admin_dn' =&gt;'CN=admin,dc=cblue,dc=be')</li>
-        <li>admin password (p. ej.: 'admin_password' =&gt; '123456') </li>
-        <li>ldap host (p. ej.: 'host' =&gt; array('1.2.3.4', '2.3.4.5', '3.4.5.6'))</li>
-        <li>filter (p. ej.: 'filter' =&gt; '') </li>
-        <li>port (p. ej.: 'port' =&gt; 389) </li>
-        <li>protocol version (2 or 3) (p. ej.: 'protocol_version' =&gt; 3)</li>
-        <li>user_search (p. ej.: 'user_search' =&gt; 'sAMAccountName=%username%') </li>
-        <li>encoding (p. ej.: 'encoding' =&gt; 'UTF-8')</li>
-        <li>update_userinfo (p. ej.: 'update_userinfo' =&gt; true) </li>
-    </ul>
-    <p>-&gt; Para actualizar las correspondencias entre los atributos de usuario y LDAP, editar el array <code>\$extldap_user_correspondance</code></p>
-    <p>Los valores del Array son &lt;chamilo_field&gt; =&gt; &gt;ldap_field&gt;</p><p>
-    </p>
-    <h4>II. Activar la atenticación LDAP</h4>
-    <h5>Editar el archivo app/config/configuration.php </h5>
-    <p>-&gt; Descomentar las líneas:</p>
-    <ul>
-        <li>
-    \$extAuthSource[\"extldap\"][\"login\"] = \$_configuration['root_sys'].\"main/auth/external_login/login.ldap.php\";</li>
-        <li>\$extAuthSource[\"extldap\"][\"newUser\"] = \$_configuration['root_sys'].\"main/auth/external_login/newUser.ldap.php\";</li>
-    </ul>
-    <p>N.B.: Los usuarios de LDAP usan los mismos campos que los usuarios de la plataforma para iniciar sesión.<br>
+$LdapDescriptionComment = "<div class=\"alert alert-info\">
+        <ul>
+            <li>Autentificación LDAP: <br>
+                Véase I. a continuación para configurar LDAP<br>
+                Véase II. a continuación para activar la autentificación LDAP
+            </li>
+            <li>Actualizar los atributos de usuario, con los datos de LDAP, después de la autentificación CAS (véase <a href=\"settings.php?category=CAS\">Configuración CAS</a>): <br>
+                Véase I. a continuación para configurar LDAP<br>
+                Para gestionar la autentificación de usuarios por CAS, no se requere la activación de LDAP.
+            </li>
+        </ul>
+    </div>
+    <h4>I. Configuración de LDAP</h4>
+    <h5>Editar el archivo app/config/auth.conf.php </h5>
+    <p>-&gt; Editar los valores del array <code>\$extldap_config</code></p>
+    <ul>
+        <li>base domain string (p. ej.: 'base_dn' =&gt; 'DC=cblue,DC=be')</li>
+        <li>admin distinguished name (p. ej.: 'admin_dn' =&gt;'CN=admin,dc=cblue,dc=be')</li>
+        <li>admin password (p. ej.: 'admin_password' =&gt; '123456') </li>
+        <li>ldap host (p. ej.: 'host' =&gt; array('1.2.3.4', '2.3.4.5', '3.4.5.6'))</li>
+        <li>filter (p. ej.: 'filter' =&gt; '') </li>
+        <li>port (p. ej.: 'port' =&gt; 389) </li>
+        <li>protocol version (2 or 3) (p. ej.: 'protocol_version' =&gt; 3)</li>
+        <li>user_search (p. ej.: 'user_search' =&gt; 'sAMAccountName=%username%') </li>
+        <li>encoding (p. ej.: 'encoding' =&gt; 'UTF-8')</li>
+        <li>update_userinfo (p. ej.: 'update_userinfo' =&gt; true) </li>
+    </ul>
+    <p>-&gt; Para actualizar las correspondencias entre los atributos de usuario y LDAP, editar el array <code>\$extldap_user_correspondance</code></p>
+    <p>Los valores del Array son &lt;chamilo_field&gt; =&gt; &gt;ldap_field&gt;</p><p>
+    </p>
+    <h4>II. Activar la atenticación LDAP</h4>
+    <h5>Editar el archivo app/config/configuration.php </h5>
+    <p>-&gt; Descomentar las líneas:</p>
+    <ul>
+        <li>
+    \$extAuthSource[\"extldap\"][\"login\"] = \$_configuration['root_sys'].\"main/auth/external_login/login.ldap.php\";</li>
+        <li>\$extAuthSource[\"extldap\"][\"newUser\"] = \$_configuration['root_sys'].\"main/auth/external_login/newUser.ldap.php\";</li>
+    </ul>
+    <p>N.B.: Los usuarios de LDAP usan los mismos campos que los usuarios de la plataforma para iniciar sesión.<br>
     N.B.: La activación LDAP agrega un menú \"Autentificación externa\" [LDAP] en las páginas de \"agregar o modificar\" usuarios.</p>";
 $ShibbolethMainActivateTitle = "<h3>Autenticación Shibboleth</h3>";
-$ShibbolethMainActivateComment = "En primer lugar, tiene que configurar Shibboleth para su servidor web.
-
-Para configurarlo en Chamilo:
-
-editar el archivo  <strong> main/auth/shibboleth/config/aai.class.php</strong>
-
-Modificar valores de \$result con el nombre de los atributos de Shibboleth
-
-    \$result->unique_id = 'mail';
-    \$result->firstname = 'cn';
-    \$result->lastname = 'uid';
-    \$result->email = 'mail';
-    \$result->language = '-';
-    \$result->gender = '-';
-    \$result->address = '-';
-    \$result->staff_category = '-';
-    \$result->home_organization_type = '-';
-    \$result->home_organization = '-';
-    \$result->affiliation = '-';
-    \$result->persistent_id = '-';
-    ...
-
+$ShibbolethMainActivateComment = "En primer lugar, tiene que configurar Shibboleth para su servidor web.
+
+Para configurarlo en Chamilo:
+
+editar el archivo  <strong> main/auth/shibboleth/config/aai.class.php</strong>
+
+Modificar valores de \$result con el nombre de los atributos de Shibboleth
+
+    \$result->unique_id = 'mail';
+    \$result->firstname = 'cn';
+    \$result->lastname = 'uid';
+    \$result->email = 'mail';
+    \$result->language = '-';
+    \$result->gender = '-';
+    \$result->address = '-';
+    \$result->staff_category = '-';
+    \$result->home_organization_type = '-';
+    \$result->home_organization = '-';
+    \$result->affiliation = '-';
+    \$result->persistent_id = '-';
+    ...
+
 Ir a Plug-in para añadir el botón 'Shibboleth Login' en su campus de Chamilo.";
 $LdapDescriptionTitle = "<h3>Autentificacion LDAP</h3>";
 $FacebookMainActivateTitle = "Autenticación con Facebook";
@@ -7500,8 +7500,8 @@ $AreYouSureToSubscribe = "¿Está seguro de suscribirse?";
 $CheckYourEmailAndFollowInstructions = "Revise su correo electrónico y siga las instrucciones.";
 $LinkExpired = "Enlace expirado, por favor vuelva a iniciar el proceso.";
 $ResetPasswordInstructions = "Instrucciones para el procedimiento de cambio de contraseña";
-$ResetPasswordCommentWithUrl = "Ha recibido este mensaje porque Usted (o alguien que intenta hacerse pasar por Ud) ha pedido que su contraseña sea generada nuevamente. Para configurar una nueva contraseña, necesita activarla. Para ello, por favor de clic en el siguiente enlace: %s.
-
+$ResetPasswordCommentWithUrl = "Ha recibido este mensaje porque Usted (o alguien que intenta hacerse pasar por Ud) ha pedido que su contraseña sea generada nuevamente. Para configurar una nueva contraseña, necesita activarla. Para ello, por favor de clic en el siguiente enlace: %s.
+
 Si no ha pedido un cambio de contraseña, puede ignorar este mensaje. No obstante, si vuelve a recibirlo repetidamente, por favor comuníquese con el administrador de su portal.";
 $CronRemindCourseExpirationActivateTitle = "Cron de Recordatorio de Expiración de Curso";
 $CronRemindCourseExpirationActivateComment = "Habilitar el cron de envío de recordatorio de expiración de cursos";
@@ -7510,14 +7510,14 @@ $CronRemindCourseExpirationFrequencyComment = "Número de días antes de la expi
 $CronCourseFinishedActivateText = "Cron de finalización de curso";
 $CronCourseFinishedActivateComment = "Activar el cron de finalización de curso";
 $MailCronCourseFinishedSubject = "Fin del curso %s";
-$MailCronCourseFinishedBody = "Estimado %s,
-
-Gracias por participar en el curso %s. Esperamos que hayas aprendido y disfrutado del curso.
-
-Puedes ver tu rendimiento a lo largo del curso en la sección Mi Avance.
-
-Saludos cordiales,
-
+$MailCronCourseFinishedBody = "Estimado %s,
+
+Gracias por participar en el curso %s. Esperamos que hayas aprendido y disfrutado del curso.
+
+Puedes ver tu rendimiento a lo largo del curso en la sección Mi Avance.
+
+Saludos cordiales,
+
 El equipo de %s";
 $GenerateDefaultContent = "Generar contenido por defecto";
 $ThanksForYourSubscription = "¡Gracias por su suscripción!";
@@ -7739,8 +7739,8 @@ $LegalAccepted = "Acuerdo legal aceptado";
 $LoadTermConditionsSectionTitle = "Cargar la sección de términos y condiciones";
 $LoadTermConditionsSectionDescription = "El acuerdo legal aparecerá durante el login o cuando entre a un curso.";
 $SendTermsSubject = "Su contrato de aprendizaje está listo para firmar.";
-$SendTermsDescriptionToUrlX = "Hola,
-
+$SendTermsDescriptionToUrlX = "Hola,
+
 Su tutor le ha enviado s contrato de aprendizaje. Puede ir a firmarlo siguiendo esta URL: %s";
 $UserXSignedTheAgreement = "El usuario %s ha firmado el acuerdo.";
 $UserXSignedTheAgreementTheY = "El usuario %s ha firmado el acuerdo el %s.";
@@ -7891,7 +7891,7 @@ $EditCourseCategoryToURL = "Editar categorías de cursos de una URL";
 $VisibleToSelf = "Visible para si mismo";
 $VisibleToOthers = "Visible por otros";
 $UpgradeVersion = "Actualizar la versión de Chamilo LMS";
-$CRSTablesIntro = "El script de instalación ha detectado tablas procedentes de versiones anteriores que podrían causar problemas durante el proceso de actualización.
+$CRSTablesIntro = "El script de instalación ha detectado tablas procedentes de versiones anteriores que podrían causar problemas durante el proceso de actualización.
 Por favor, haga clic en el botón de abajo para eliminarlas. Recomendamos seriamente hacer una copia de seguridad completa de estas antes de confirmar este último paso de instalación.";
 $Removing = "Removiendo";
 $CheckForCRSTables = "Comprobar si hay tablas de veriones anteriores";
@@ -8037,12 +8037,12 @@ $SpecificDate = "Envio en fecha concreta";
 $BaseDate = "Envío en base a fecha de inicio/fin de la sesión";
 $AfterOrBefore = "Antes o después";
 $Before = "Antes de";
-$ScheduleAnnouncementDescription = "Este formulario permite programar anuncios/avisos para que sean enviados de manera automática a los alumnos que están realizando un curso en una sesión.
-
-Existe dos tipos de anuncios que se pueden enviar:
-
-Envío en una fecha concreta: En este caso se selecciona un día concreto para hacer el envío.
-
+$ScheduleAnnouncementDescription = "Este formulario permite programar anuncios/avisos para que sean enviados de manera automática a los alumnos que están realizando un curso en una sesión.
+
+Existe dos tipos de anuncios que se pueden enviar:
+
+Envío en una fecha concreta: En este caso se selecciona un día concreto para hacer el envío.
+
 Envío en base a la fecha de inicio/finalización de la sesión: en este caso se ha de indicar el número de días que han de pasar antes de enviar el anuncio. Y esos días pueden estar asociados a antes o después de la fecha de inicio/finalización. Por ejemplo: 3 días después de fecha de inicio.";
 $MandatorySurveyNoAnswered = "Usted tiene pendiente una encuesta obligatoria. Para ingresar al curso, primero deberá completarla";
 $ShowPreviousButton = "Mostrar el botón 'anterior'";
@@ -8172,10 +8172,10 @@ $RedirectToTheDocumentList = "Redirigir a la lista de documentos";
 $TheExerciseAutoLaunchSettingIsONStudentsWillBeRedirectToTheExerciseList = "La funcionalidad de lanzamiento automático de ejercicios está activada. Los estudiantes serán automáticamente redirigidos a la lista de ejercicios.";
 $PostedExpirationDate = "Fecha límite publicada para enviar el trabajo (visible para el alumno)";
 $BossAlertMsgSentToUserXTitle = "Mensaje de seguimiento sobre alumno %s";
-$BossAlertUserXSentMessageToUserYWithLinkZ = "Hola,<br/><br/>
-
-El usuario %s ha enviado un mensaje de seguimiento sobre el alumno %s.<br/><br/>
-
+$BossAlertUserXSentMessageToUserYWithLinkZ = "Hola,<br/><br/>
+
+El usuario %s ha enviado un mensaje de seguimiento sobre el alumno %s.<br/><br/>
+
 El mensaje se puede ver en %s";
 $include_services = "Incluir los servicios";
 $culqi_enable = "Activar Culqi";
@@ -8217,11 +8217,11 @@ $ResultAccomplishedTest = "Resultados de la prueba dada";
 $YourResultsByDiscipline = "Sus notas, disciplina por disciplina";
 $ForComparisonYourLastResultToThisTest = "En comparación, su última nota para esta prueba";
 $YourOverallResultForTheTest = "Su nota para la prueba en general";
-$QuestionDegreeCertaintyHTMLMail = "Encontrará su nota a continuación para la prueba %s.<br />
-Para ver más detalles:
-<br /><br />
-1. Conectarse a la plataforma (usuario/contraseña usual): <a href='%s'>Hacia la plataforma</a>.
-<br /><br />
+$QuestionDegreeCertaintyHTMLMail = "Encontrará su nota a continuación para la prueba %s.<br />
+Para ver más detalles:
+<br /><br />
+1. Conectarse a la plataforma (usuario/contraseña usual): <a href='%s'>Hacia la plataforma</a>.
+<br /><br />
 2. Seguir este enlace: <a href='%s'>Ver resultados más detallados</a>.";
 $DegreeOfCertaintyVerySure = "Muy seguro";
 $DegreeOfCertaintyVerySureDescription = "Dió la respuesta correcta y estaba seguro de ella al 80% ¡Felicitaciones!";
@@ -8283,8 +8283,8 @@ $SubSkill = "Sub-competencia";
 $AddMultipleUsersToCalendar = "Agregar múltiples usuarios al calendario";
 $UpdateCalendar = "Actualizar calendario";
 $ControlPoint = "Punto de control";
-$MessageQuestionCertainty = "Siga las instrucciones a continuación para ver sus resultados de la prueba '%s' a detalle:<br /><br />
-1. Conéctese a la plataforma (con login/constraseña) en: %s<br />
+$MessageQuestionCertainty = "Siga las instrucciones a continuación para ver sus resultados de la prueba '%s' a detalle:<br /><br />
+1. Conéctese a la plataforma (con login/constraseña) en: %s<br />
 2. De click en el enlace siguiente: Ms<br />";
 $SessionMinDuration = "Duración mín de sesión";
 $CanNotTranslate = "No se pudo traducir";
@@ -8309,9 +8309,9 @@ $RemoveTerms = "Anular acuerdo de condiciones de uso";
 $InformationRightToBeForgottenLinkX = "Puede encontrar más información sobre el derecho al olvido en la página siguiente: %s";
 $ExplanationDeleteLegal = "Por favor indíquenos porqué desea eliminar su acuerda a nuestras condiciones de uso, para asegurar que lo podamos hacer de la mejor manera posible.";
 $ExplanationDeleteAccount = "Por favor indíquenos porqué desea que eliminemos su cuenta, para asegurar que lo podamos hacer de la mejor manera.";
-$WhyYouWantToDeleteYourLegalAgreement = "Puede solicitar a bajo la eliminación de su acuerdo a nuestras condiciones de uso o la eliminación de su cuenta.<br />
-En el caso de la eliminación de su acuerdo, tendrá que volver a aceptar nuestras condiciones en su próxima conexión, pues no nos es posible proveerle una experiencia personalizada sin al mismo tiempo gestionar algunos de sus datos personales.<br />
-En el caso de la eliminación completa de su cuenta, ésta será eliminada junta con todas sus suscripciones a cursos y toda la información relacionada con su cuenta.<br />
+$WhyYouWantToDeleteYourLegalAgreement = "Puede solicitar a bajo la eliminación de su acuerdo a nuestras condiciones de uso o la eliminación de su cuenta.<br />
+En el caso de la eliminación de su acuerdo, tendrá que volver a aceptar nuestras condiciones en su próxima conexión, pues no nos es posible proveerle una experiencia personalizada sin al mismo tiempo gestionar algunos de sus datos personales.<br />
+En el caso de la eliminación completa de su cuenta, ésta será eliminada junta con todas sus suscripciones a cursos y toda la información relacionada con su cuenta.<br />
 Por favor seleccione la opción correspondiente con mucho cuidado. En ambos casos, su solicitud será revisada por uno de nuestros administradores, con el fin de evitar cualquier malentendimiento y/o pérdida definitiva de sus datos.";
 $PersonalDataPrivacy = "Protección de datos personales";
 $RequestForAccountDeletion = "Pedido de eliminación de cuenta";
@@ -8339,11 +8339,19 @@ $ProgressObtainedFromLPProgressAndTestsAverage = "Nota: Este progreso es obtenid
 $CreateNewSurveyDoodle = "Crear una nueva encuesta de tipo Doodle";
 $RemoveMultiplicateQuestions = "Eliminar las preguntas demultiplicadas";
 $MultiplicateQuestions = "Demultiplicar las preguntas";
-$QuestionTags = "Puedes usar las etiquetas {{class_name}} y {{student_full_name}} en la pregunta para poder multiplicar las preguntas.
-En la página de la lista de encuestas en la columna de acción, hay un botón para multiplicar las preguntas que buscará la etiqueta {{class_name}} y duplicará la pregunta para todas las clases inscritas al curso y le cambiará el nombre con el nombre de la clase.
-También agregará un separador de página para hacer una nueva página para cada clase.
+$QuestionTags = "Puedes usar las etiquetas {{class_name}} y {{student_full_name}} en la pregunta para poder multiplicar las preguntas.
+En la página de la lista de encuestas en la columna de acción, hay un botón para multiplicar las preguntas que buscará la etiqueta {{class_name}} y duplicará la pregunta para todas las clases inscritas al curso y le cambiará el nombre con el nombre de la clase.
+También agregará un separador de página para hacer una nueva página para cada clase.
 Luego buscará la etiqueta {{student_full_name}} y duplicará la pregunta para todos los estudiantes de la clase (para cada clase) y le cambiará el nombre con el nombre completo del estudiante.";
 $CreateMeeting = "Crear encuesta de reunión";
 $QuestionForNextClass = "Preguntas para la siguiente clase";
 $NewExerciseAttemptDisabled = "El portal no permite de iniciar ejercicios por le momento, favor de regresar un poquito mas tarde.";
+$PersonalDataOfficer = "Responsable de la protección de datos";
+$MaxNumberSubscribedStudentsReached = "Se ha llegado al numero maximo de alumno inscrito en el curso, no se puede inscribir mas.";
+$UserXAddedToCourseX = "El usuario %s ha sido registrado al curso %s";
+$LpMinTime = "Tiempo mínimo (min)";
+$LpMinTimeDescription = "Tiempo mínimo (en minutos) que el estudiante debe permanecer en la lección para poder acceder a la siguiente.";
+$LpMinTimeWarning = "No ha alcanzado el tiempo mínimo en la lección.";
+$YouHaveSpentXTime = "Has invertido %s tiempo";
+$TimeSpentInLp = "Tiempo en la lección";
 ?>

+ 23 - 0
main/lp/embed.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use ChamiloSession as Session;
+
 require_once __DIR__.'/../inc/global.inc.php';
 
 $type = $_REQUEST['type'];
@@ -11,6 +13,27 @@ if (empty($type) || empty($src)) {
 
 $iframe = '';
 switch ($type) {
+    case 'download':
+        /** @var learnpath $learnPath */
+        $learnPath = Session::read('oLP');
+        $itemId = isset($_GET['lp_item_id']) ? $_GET['lp_item_id'] : '';
+        if (!$learnPath || empty($itemId)) {
+            api_not_allowed();
+        }
+
+        $file = learnpath::rl_get_resource_link_for_learnpath(
+            api_get_course_int_id(),
+            $learnPath->get_id(),
+            $itemId,
+            $learnPath->get_view_id()
+        );
+
+        $iframe = Display::return_message(
+            Display::url(get_lang('Download'), $file, ['class' => 'btn btn-primary']),
+            'info',
+            false
+        );
+        break;
     case 'youtube':
         $src = '//www.youtube.com/embed/'.$src;
         $iframe .= '<div id="content" style="width: 700px ;margin-left:auto; margin-right:auto;"><br />';

+ 90 - 88
main/lp/learnpath.class.php

@@ -3717,10 +3717,18 @@ class learnpath
 
                     switch ($lp_item_type) {
                         case 'document':
-                            if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
-                                if (Link::isPdfLink($file)) {
-                                    $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
-                                    $file = $pdfUrl;
+                            // Shows a button to download the file instead of just downloading the file directly.
+                            $documentPathInfo = pathinfo($file);
+                            if (isset($documentPathInfo['extension'])) {
+                                $parsed = parse_url($documentPathInfo['extension']);
+                                if (isset($parsed['path'])) {
+                                    $extension = $parsed['path'];
+                                    $extensionsToDownload = ['zip', 'ppt', 'pptx', 'ods', 'xlsx', 'xls', 'csv'];
+
+                                    if (in_array($extension, $extensionsToDownload)) {
+                                        $file = api_get_path(WEB_CODE_PATH).
+                                            'lp/embed.php?type=download&source=file&lp_item_id='.$item_id.'&'.api_get_cidreq();
+                                    }
                                 }
                             }
                             break;
@@ -3773,14 +3781,14 @@ class learnpath
 
                             $type_quiz = false;
                             foreach ($list as $toc) {
-                                if ($toc['id'] == $lp_item_id && ($toc['type'] == 'quiz')) {
+                                if ($toc['id'] == $lp_item_id && $toc['type'] == 'quiz') {
                                     $type_quiz = true;
                                 }
                             }
 
                             if ($type_quiz) {
-                                $lp_item_id = intval($lp_item_id);
-                                $lp_view_id = intval($lp_view_id);
+                                $lp_item_id = (int) $lp_item_id;
+                                $lp_view_id = (int) $lp_view_id;
                                 $sql = "SELECT count(*) FROM $lp_item_view_table
                                         WHERE
                                             c_id = $course_id AND
@@ -3800,7 +3808,6 @@ class learnpath
                     }
 
                     $tmp_array = explode('/', $file);
-
                     $document_name = $tmp_array[count($tmp_array) - 1];
                     if (strpos($document_name, '_DELETED_')) {
                         $file = 'blank.php?error=document_deleted';
@@ -7493,26 +7500,6 @@ class learnpath
                 }
                 break;
             case TOOL_DOCUMENT:
-                $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
-                $sql = "SELECT lp.*, doc.path as dir
-                        FROM $tbl_lp_item as lp
-                        LEFT JOIN $tbl_doc as doc
-                        ON (doc.iid = lp.path AND lp.c_id = doc.c_id)
-                        WHERE
-                            doc.c_id = $course_id AND
-                            lp.iid = ".$item_id;
-                $res_step = Database::query($sql);
-                $row_step = Database::fetch_array($res_step, 'ASSOC');
-                $return .= $this->display_manipulate(
-                    $item_id,
-                    $row['item_type']
-                );
-                $return .= $this->display_document_form(
-                    'edit',
-                    $item_id,
-                    $row_step
-                );
-                break;
             case TOOL_READOUT_TEXT:
                 $tbl_doc = Database::get_course_table(TABLE_DOCUMENT);
                 $sql = "SELECT lp.*, doc.path as dir
@@ -7524,32 +7511,37 @@ class learnpath
                             lp.iid = ".$item_id;
                 $res_step = Database::query($sql);
                 $row_step = Database::fetch_array($res_step, 'ASSOC');
-                $return .= $this->display_manipulate(
-                    $item_id,
-                    $row['item_type']
-                );
-                $return .= $this->displayFrmReadOutText(
-                    'edit',
-                    $item_id,
-                    $row_step
-                );
+                $return .= $this->display_manipulate($item_id, $row['item_type']);
+
+                if ($row['item_type'] === TOOL_DOCUMENT) {
+                    $return .= $this->display_document_form('edit', $item_id, $row_step);
+                }
+
+                if ($row['item_type'] === TOOL_READOUT_TEXT) {
+                    $return .= $this->displayFrmReadOutText('edit', $item_id, $row_step);
+                }
                 break;
             case TOOL_LINK:
-                $link_id = (string) $row['path'];
-                if (ctype_digit($link_id)) {
-                    $tbl_link = Database::get_course_table(TABLE_LINK);
-                    $sql_select = 'SELECT url FROM '.$tbl_link.'
-                                   WHERE c_id = '.$course_id.' AND iid = '.intval($link_id);
-                    $res_link = Database::query($sql_select);
+                $linkId = (int) $row['path'];
+                if (!empty($linkId)) {
+                    $table = Database::get_course_table(TABLE_LINK);
+                    $sql = 'SELECT url FROM '.$table.'
+                            WHERE c_id = '.$course_id.' AND iid = '.$linkId;
+                    $res_link = Database::query($sql);
                     $row_link = Database::fetch_array($res_link);
+                    if (empty($row_link)) {
+                        // Try with id
+                        $sql = 'SELECT url FROM '.$table.'
+                                WHERE c_id = '.$course_id.' AND id = '.$linkId;
+                        $res_link = Database::query($sql);
+                        $row_link = Database::fetch_array($res_link);
+                    }
+
                     if (is_array($row_link)) {
                         $row['url'] = $row_link['url'];
                     }
                 }
-                $return .= $this->display_manipulate(
-                    $item_id,
-                    $row['item_type']
-                );
+                $return .= $this->display_manipulate($item_id, $row['item_type']);
                 $return .= $this->display_link_form('edit', $item_id, $row);
                 break;
             case TOOL_LP_FINAL_ITEM:
@@ -7564,15 +7556,8 @@ class learnpath
                             lp.iid = ".$item_id;
                 $res_step = Database::query($sql);
                 $row_step = Database::fetch_array($res_step, 'ASSOC');
-                $return .= $this->display_manipulate(
-                    $item_id,
-                    $row['item_type']
-                );
-                $return .= $this->display_document_form(
-                    'edit',
-                    $item_id,
-                    $row_step
-                );
+                $return .= $this->display_manipulate($item_id, $row['item_type']);
+                $return .= $this->display_document_form('edit', $item_id, $row_step);
                 break;
             case TOOL_QUIZ:
                 $return .= $this->display_manipulate($item_id, $row['item_type']);
@@ -8833,10 +8818,10 @@ class learnpath
         $item_description = '';
         //If action==edit document
         //We don't display the document form if it's not an editable document (html or txt file)
-        if ($action == 'edit') {
+        if ($action === 'edit') {
             if (is_array($extra_info)) {
                 $path_parts = pathinfo($extra_info['dir']);
-                if ($path_parts['extension'] != "txt" && $path_parts['extension'] != "html") {
+                if ($path_parts['extension'] != 'txt' && $path_parts['extension'] != 'html') {
                     $no_display_edit_textarea = true;
                 }
             }
@@ -8845,7 +8830,7 @@ class learnpath
 
         // If action==add an existing document
         // We don't display the document form if it's not an editable document (html or txt file).
-        if ($action == 'add') {
+        if ($action === 'add') {
             if (is_numeric($extra_info)) {
                 $extra_info = (int) $extra_info;
                 $sql_doc = "SELECT path FROM $tbl_doc 
@@ -9684,23 +9669,24 @@ class learnpath
         $tbl_lp_item = Database::get_course_table(TABLE_LP_ITEM);
         $tbl_link = Database::get_course_table(TABLE_LINK);
 
+        $item_title = '';
+        $item_description = '';
+        $item_url = '';
+
         if ($id != 0 && is_array($extra_info)) {
             $item_title = stripslashes($extra_info['title']);
             $item_description = stripslashes($extra_info['description']);
             $item_url = stripslashes($extra_info['url']);
         } elseif (is_numeric($extra_info)) {
-            $extra_info = intval($extra_info);
-            $sql = "SELECT title, description, url FROM ".$tbl_link."
-                    WHERE c_id = ".$course_id." AND id = ".$extra_info;
+            $extra_info = (int) $extra_info;
+            $sql = "SELECT title, description, url 
+                    FROM $tbl_link
+                    WHERE c_id = $course_id AND iid = $extra_info";
             $result = Database::query($sql);
             $row = Database::fetch_array($result);
             $item_title = $row['title'];
             $item_description = $row['description'];
             $item_url = $row['url'];
-        } else {
-            $item_title = '';
-            $item_description = '';
-            $item_url = '';
         }
 
         $form = new FormValidator(
@@ -9709,10 +9695,9 @@ class learnpath
             $this->getCurrentBuildingModeURL()
         );
         $defaults = [];
+        $parent = 0;
         if ($id != 0 && is_array($extra_info)) {
             $parent = $extra_info['parent_item_id'];
-        } else {
-            $parent = 0;
         }
 
         $sql = "SELECT * FROM $tbl_lp_item
@@ -10160,14 +10145,25 @@ class learnpath
             $url.'&action=delete_item'
         );
 
-        if ($item_type == TOOL_HOTPOTATOES) {
-            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
-            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
-        }
-
-        if ($item_type == TOOL_DOCUMENT || $item_type == TOOL_LP_FINAL_ITEM) {
-            $document_data = DocumentManager::get_document_data_by_id($row['path'], $course_code);
-            $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
+        if (in_array($item_type, [TOOL_DOCUMENT, TOOL_LP_FINAL_ITEM, TOOL_HOTPOTATOES])) {
+            $documentData = DocumentManager::get_document_data_by_id($row['path'], $course_code);
+            if (empty($documentData)) {
+                // Try with iid
+                $table = Database::get_course_table(TABLE_DOCUMENT);
+                $sql = "SELECT path FROM $table
+                        WHERE 
+                              c_id = ".api_get_course_int_id()." AND 
+                              iid = ".$row['path']." AND 
+                              path NOT LIKE '%_DELETED_%'";
+                $result = Database::query($sql);
+                $documentData = Database::fetch_array($result);
+                if ($documentData) {
+                    $documentData['absolute_path_from_document'] = '/document'.$documentData['path'];
+                }
+            }
+            if (isset($documentData['absolute_path_from_document'])) {
+                $return .= get_lang('File').': '.$documentData['absolute_path_from_document'];
+            }
         }
 
         $return .= '</div>';
@@ -13466,18 +13462,15 @@ EOD;
                 return $main_dir_path.'forum/viewthread.php?post='.$id.'&thread='.$myrow['thread_id'].'&forum='
                     .$myrow['forum_id'].'&lp=true&'.$extraParams;
             case TOOL_READOUT_TEXT:
-                return api_get_path(WEB_CODE_PATH).'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'
-                    .$extraParams;
+                return api_get_path(WEB_CODE_PATH).
+                    'lp/readout_text.php?&id='.$id.'&lp_id='.$learningPathId.'&'.$extraParams;
             case TOOL_DOCUMENT:
-                $document = $em
-                    ->getRepository('ChamiloCourseBundle:CDocument')
-                    ->findOneBy(['cId' => $course_id, 'iid' => $id]);
+                $repo = $em->getRepository('ChamiloCourseBundle:CDocument');
+                $document = $repo->findOneBy(['cId' => $course_id, 'iid' => $id]);
 
                 if (empty($document)) {
                     // Try with normal id
-                    $document = $em
-                        ->getRepository('ChamiloCourseBundle:CDocument')
-                        ->findOneBy(['cId' => $course_id, 'id' => $id]);
+                    $document = $repo->findOneBy(['cId' => $course_id, 'id' => $id]);
 
                     if (empty($document)) {
                         return '';
@@ -13485,9 +13478,9 @@ EOD;
                 }
 
                 $documentPathInfo = pathinfo($document->getPath());
-                $jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
+                $jplayerSupportedFiles = ['mp4', 'ogv', 'flv', 'm4v'];
                 $extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
-                $showDirectUrl = !in_array($extension, $jplayer_supported_files);
+                $showDirectUrl = !in_array($extension, $jplayerSupportedFiles);
 
                 $openmethod = 2;
                 $officedoc = false;
@@ -13495,7 +13488,16 @@ EOD;
                 Session::write('officedoc', $officedoc);
 
                 if ($showDirectUrl) {
-                    return $main_course_path.'document'.$document->getPath().'?'.$extraParams;
+                    $file = $main_course_path.'document'.$document->getPath().'?'.$extraParams;
+                    if (api_get_configuration_value('allow_pdf_viewerjs_in_lp')) {
+                        if (Link::isPdfLink($file)) {
+                            $pdfUrl = api_get_path(WEB_LIBRARY_PATH).'javascript/ViewerJS/index.html#'.$file;
+
+                            return $pdfUrl;
+                        }
+                    }
+
+                    return $file;
                 }
 
                 return api_get_path(WEB_CODE_PATH).'document/showinframes.php?id='.$id.'&'.$extraParams;
@@ -13520,7 +13522,7 @@ EOD;
                 }
 
                 return $main_dir_path.'work/work.php?'.api_get_cidreq().'&id='.$rowItem->getPath().'&'.$extraParams;
-        } //end switch
+        }
 
         return $link;
     }

+ 1 - 2
main/lp/lp_add_audio.php

@@ -19,8 +19,7 @@ $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
 $type = isset($_GET['type']) ? $_GET['type'] : null;
 $action = isset($_GET['action']) ? $_GET['action'] : null;
 
-if ((!$is_allowed_to_edit) || $isStudentView) {
-    error_log('New LP - User not authorized in lp_add_audio.php');
+if (!$is_allowed_to_edit || $isStudentView) {
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
     exit;
 }

+ 1 - 2
main/lp/lp_admin_view.php

@@ -30,8 +30,7 @@ $learnpath_id = (int) $_REQUEST['lp_id'];
 $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
 $_course = api_get_course_info();
 
-if ((!$is_allowed_to_edit) || ($isStudentView)) {
-    error_log('New LP - User not authorized in lp_admin_view.php');
+if (!$is_allowed_to_edit || $isStudentView) {
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
     exit;
 }

+ 1 - 2
main/lp/lp_build.php

@@ -27,8 +27,7 @@ $learnpath_id = (int) $_REQUEST['lp_id'];
 $submit = $_POST['submit_button'];
 
 /* MAIN CODE */
-if ((!$is_allowed_to_edit) || ($isStudentView)) {
-    error_log('New LP - User not authorized in lp_build.php');
+if (!$is_allowed_to_edit || $isStudentView) {
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
     exit;
 }

+ 0 - 2
main/lp/lp_edit_item.php

@@ -38,7 +38,6 @@ $learnpath_id = (int) $_REQUEST['lp_id'];
 $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
 
 if (!$is_allowed_to_edit || $isStudentView) {
-    error_log('New LP - User not authorized in lp_edit_item.php');
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
     exit;
 }
@@ -150,7 +149,6 @@ if (!empty($path_file) && isset($path_parts['extension']) && $path_parts['extens
 } else {
     echo $learnPath->return_new_tree();
 }
-
 echo '</div>';
 echo '<div id="doc_form" class="col-md-8">';
 

+ 1 - 2
main/lp/lp_edit_item_prereq.php

@@ -25,8 +25,7 @@ $learnpath_id = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : null;
 $submit = isset($_POST['submit_button']) ? $_POST['submit_button'] : null;
 
 /* MAIN CODE */
-if ((!$is_allowed_to_edit) || $isStudentView) {
-    error_log('New LP - User not authorized in lp_edit_item_prereq.php');
+if (!$is_allowed_to_edit || $isStudentView) {
     header('location:lp_controller.php?action=view&lp_id='.$learnpath_id);
     exit;
 }

+ 2 - 5
main/lp/lp_view.php

@@ -244,7 +244,7 @@ if (!isset($src)) {
                 }
 
                 if (isset($file_info['extension']) &&
-                    api_strtolower(substr($file_info['extension'], 0, 3) == 'pdf')
+                    api_strtolower(substr($file_info['extension'], 0, 3)) == 'pdf'
                 ) {
                     $src = api_get_path(WEB_CODE_PATH).'lp/lp_view_item.php?lp_item_id='.$lp_item_id.'&'.api_get_cidreq();
                 }
@@ -460,10 +460,6 @@ if ($is_allowed_to_edit) {
         'url' => '#',
         'name' => get_lang('Preview'),
     ];
-}
-
-// Return to course home.
-if ($is_allowed_to_edit) {
     $buttonHomeUrl = 'lp_controller.php?'.api_get_cidreq(true, true, 'course').'&'.http_build_query([
         'isStudentView' => 'false',
         'action' => 'return_to_course_homepage',
@@ -535,6 +531,7 @@ $template->assign('glossary_tool_available_list', ['true', 'lp', 'exercise_and_l
 $gamificationMode = api_get_setting('gamification_mode');
 // ...AND this learning path is set in gamification mode, then change the display
 $gamificationMode = $gamificationMode && $lp->seriousgame_mode;
+
 $template->assign('gamification_mode', $gamificationMode);
 $template->assign('glossary_extra_tools', api_get_setting('show_glossary_in_extra_tools'));
 $template->assign('show_glossary_in_documents', api_get_setting('show_glossary_in_documents'));

+ 1 - 2
main/lp/lp_view_item.php

@@ -58,8 +58,7 @@ $is_allowed_to_edit = api_is_allowed_to_edit(null, true);
 $isStudentView = (empty($_REQUEST['isStudentView']) ? 0 : (int) $_REQUEST['isStudentView']);
 $learnpath_id = (int) $_REQUEST['lp_id'];
 
-if ((!$is_allowed_to_edit) || $isStudentView) {
-    error_log('New LP - User not authorized in lp_view_item.php');
+if (!$is_allowed_to_edit || $isStudentView) {
     header('Location: '.api_get_path(WEB_CODE_PATH).'lp/lp_controller.php?action=view&lp_id='.$learnpath_id.'&'.api_get_cidreq());
     exit;
 }

+ 0 - 4
main/lp/readout_text.php

@@ -30,7 +30,6 @@ $documentRepo = $em->getRepository('ChamiloCourseBundle:CDocument');
 // This page can only be shown from inside a learning path
 if (!$id && !$lpId) {
     api_not_allowed(true);
-    exit;
 }
 
 /** @var CDocument $document */
@@ -48,9 +47,6 @@ if (empty($document)) {
 }
 
 $documentPathInfo = pathinfo($document->getPath());
-$jplayer_supported_files = ['mp4', 'ogv', 'flv', 'm4v'];
-$extension = isset($documentPathInfo['extension']) ? $documentPathInfo['extension'] : '';
-
 $coursePath = api_get_path(SYS_COURSE_PATH).$courseInfo['directory'];
 $documentPath = '/document'.$document->getPath();
 $documentText = file_get_contents($coursePath.$documentPath);

+ 3 - 2
main/lp/scorm_api.php

@@ -1435,10 +1435,11 @@ function reinit_updatable_vars_list() {
  * @param	string		This parameter can be a string specifying the next
  *						item (like 'next', 'previous', 'first' or 'last') or the id to the next item
  */
-function switch_item(current_item, next_item) {
+function switch_item(current_item, next_item)
+{
     if (olms.lms_initialized == 0) {
         // Fix error when flash is not loaded and SCO is not started BT#14944
-        olms.G_LastError 		= G_NotInitialized;
+        olms.G_LastError = G_NotInitialized;
         olms.G_LastErrorMessage = G_NotInitializedMessage;
         logit_scorm('Error '+ G_NotInitialized + G_NotInitializedMessage, 0);
         window.location.reload(false);

+ 6 - 6
main/social/invitations.php

@@ -119,7 +119,7 @@ if ($numberLoop != 0) {
 
         $title = Security::remove_XSS($invitation['title'], STUDENT, true);
         $content = Security::remove_XSS($invitation['content'], STUDENT, true);
-        $date = api_convert_and_format_date($invitation['send_date'], DATE_TIME_FORMAT_LONG);
+        $date = Display::dateToStringAgoAndLongDate($invitation['send_date']);
         $invitationHtml .= '<div class="row">';
         $invitationHtml .= '<div class="col-md-2">';
         $invitationHtml .= '<a href="profile.php?u='.$sender_user_id.'">';
@@ -137,7 +137,7 @@ if ($numberLoop != 0) {
                 'is_my_friend' => 'friend',
             ]),
             'check',
-            'default',
+            'primary',
             ['id' => 'btn-accept-'.$sender_user_id]
         );
         $invitationHtml .= Display::toolbarButton(
@@ -147,7 +147,7 @@ if ($numberLoop != 0) {
                 'denied_friend_id' => $sender_user_id,
             ]),
             'times',
-            'default',
+            'danger',
             ['id' => 'btn-deny-'.$sender_user_id]
         );
         $invitationHtml .= '</div>';
@@ -157,7 +157,7 @@ if ($numberLoop != 0) {
                             '.$user_info['complete_name'].'</a>:
                             </h5>';
         $invitationHtml .= '<div class="content-invitation">'.$content.'</div>';
-        $invitationHtml .= '<div class="date-invitation">'.get_lang('DateSend').' : '.$date.'</div>';
+        $invitationHtml .= '<div class="date-invitation">'.get_lang('Sent').' : '.$date.'</div>';
 
         $invitationHtml .= '</div>';
         $invitationHtml .= '</div></div>';
@@ -173,7 +173,6 @@ if (count($list_get_invitation_sent) > 0) {
         $invitationSentHtml .= '<div id="id_'.$sender_user_id.'" class="well">';
         $title = Security::remove_XSS($invitation['title'], STUDENT, true);
         $content = Security::remove_XSS($invitation['content'], STUDENT, true);
-        $date = api_convert_and_format_date($invitation['send_date'], DATE_TIME_FORMAT_LONG);
 
         $invitationSentHtml .= '<div class="row">';
         $invitationSentHtml .= '<div class="col-md-3">';
@@ -184,7 +183,8 @@ if (count($list_get_invitation_sent) > 0) {
         $invitationSentHtml .= '<h4 class="title-profile">
             <a class="profile_link" href="profile.php?u='.$sender_user_id.'">'.$user_info['complete_name'].'</a></h4>';
         $invitationSentHtml .= '<div class="content-invitation">'.$title.' : '.$content.'</div>';
-        $invitationSentHtml .= '<div class="date-invitation">'.get_lang('DateSend').' : '.$date.'</div>';
+        $invitationSentHtml .= '<div class="date-invitation">'.
+            get_lang('Sent').' : '.Display::dateToStringAgoAndLongDate($invitation['send_date']).'</div>';
         $invitationSentHtml .= '</div>';
         $invitationSentHtml .= '</div></div>';
     }

+ 1 - 1
main/social/personal_data.php

@@ -442,12 +442,12 @@ if ($showWarningMessage) {
 SocialManager::setSocialUserBlock($tpl, api_get_user_id(), 'messages');
 if (api_get_setting('allow_social_tool') === 'true') {
     $tpl->assign('social_menu_block', $socialMenuBlock);
-    $tpl->assign('personal_data', $personalData);
 } else {
     $tpl->assign('social_menu_block', '');
     $tpl->assign('personal_data_block', $personalDataContent);
 }
 
+$tpl->assign('personal_data', $personalData);
 $tpl->assign('permission', $permissionBlock);
 $tpl->assign('term_link', $termLink);
 $socialLayout = $tpl->get_template('social/personal_data.tpl');

+ 1 - 1
main/template/default/auth/session_catalog.tpl

@@ -100,7 +100,7 @@
                                     	</a>
                                 	</h4>
                             	</div>
-                            	{% if show_tutor %}
+                            	{% if show_tutor and item.coach_id %}
                             	<div class="block-author">
                                 	<div class="author-card">
                                     	<a href="{{ item.coach_url }}" class="ajax" data-title="{{ item.coach_name }}">

+ 42 - 34
main/template/default/course_home/about.tpl

@@ -8,15 +8,14 @@
                     <li>{{ teacher.complete_name }} | </li>
                 {% endfor %}
             </ul>
-
         </div>
     </div>
 
     {% set course_video = '' %}
-    {% for extra_field in course.extra_fields %}
-    {% if extra_field.value.getField().getVariable() == 'video_url' %}
-    {% set course_video = extra_field.value.getValue() %}
-    {% endif %}
+        {% for extra_field in course.extra_fields %}
+        {% if extra_field.value.getField().getVariable() == 'video_url' %}
+            {% set course_video = extra_field.value.getValue() %}
+        {% endif %}
     {% endfor %}
 
     <div class="panel panel-default">
@@ -34,7 +33,6 @@
                         <img src="{{ course.image }}" class="img-responsive" />
                     </div>
                     {% endif %}
-
                     <div class="share-social-media">
                         <ul class="sharing-buttons">
                             <li>
@@ -69,7 +67,6 @@
             </div>
             {% if course.tags %}
                 <div class="panel-tags">
-
                     <ul class="list-inline course-tags">
                         <li>{{ 'Tags'|get_lang }} :</li>
                         {% for tag in course.tags %}
@@ -105,24 +102,32 @@
                     </div>
                 </div>
             </div>
-
             <div class="col-sm-4">
                 {% if allow_subscribe == true %}
                 <div class="panel panel-default">
                     <div class="panel-body">
                         {% if is_premium == false %}
-                            <h5>{{ 'CourseSubscription'|get_lang }}</h5>
                             <div class="session-subscribe">
                                 {% if _u.logged == 0 %}
                                     {% if 'allow_registration'|api_get_setting != 'false' %}
-                                        <a href="{{ _p.web_main ~ 'auth/inscription.php' ~ redirect_to_session }}" class="btn btn-success btn-block btn-lg">
-                                            <i class="fa fa-pencil" aria-hidden="true"></i> {{ 'SignUp'|get_lang }}
+                                        <a
+                                            href="{{ _p.web_main ~ 'auth/inscription.php' ~ redirect_to_session }}"
+                                            class="btn btn-success btn-block btn-lg">
+                                            <i class="fa fa-pencil" aria-hidden="true"></i>
+                                            {{ 'SignUp'|get_lang }}
                                         </a>
                                     {% endif %}
                                 {% elseif course.subscription %}
-                                    <a href="{{ _p.web }}courses/{{ course.code }}/index.php?id_session=0" class="btn btn-lg btn-success btn-block">{{ 'CourseHomepage'|get_lang }}</a>
+                                    <a href="{{ _p.web }}courses/{{ course.code }}/index.php?id_session=0"
+                                       class="btn btn-lg btn-success btn-block">
+                                        {{ 'CourseHomepage'|get_lang }}
+                                    </a>
                                 {% else %}
-                                    <a href="{{ _p.web }}courses/{{ course.code }}/index.php?action=subscribe&sec_token={{ token }}" class="btn btn-lg btn-success btn-block">{{ 'Subscribe'|get_lang }}</a>
+                                    <a
+                                        href="{{ _p.web }}courses/{{ course.code }}/index.php?action=subscribe&sec_token={{ token }}"
+                                       class="btn btn-lg btn-success btn-block">
+                                        {{ 'Subscribe'|get_lang }}
+                                    </a>
                                 {% endif %}
                             </div>
                         {% else %}
@@ -134,43 +139,46 @@
                                     {{ is_premium.iso_code }} {{ is_premium.price }}
                                 </div>
                                 <div class="buy-box">
-                                    <a href="{{ _p.web }}plugin/buycourses/src/process.php?i={{ is_premium.product_id }}&t={{ is_premium.product_type }}" class="btn btn-lg btn-primary btn-block">{{ 'BuyNow'|get_lang }}</a>
+                                    <a
+                                        href="{{ _p.web }}plugin/buycourses/src/process.php?i={{ is_premium.product_id }}&t={{ is_premium.product_type }}"
+                                        class="btn btn-lg btn-primary btn-block">{{ 'BuyNow'|get_lang }}</a>
                                 </div>
                             </div>
                         {% endif %}
                     </div>
                 </div>
                 {% endif %}
-                <div class="panel panel-default">
-                    <div class="panel-body">
-                        <div class="panel-teachers">
-                            <h3 class="sub-title">{{ "Coaches"|get_lang }}</h3>
-                        </div>
-                        {%  for teacher in course.teachers %}
-                        <div class="coach-information">
-                            <div class="coach-header">
-                                <div class="coach-avatar">
-                                    <img class="img-circle img-responsive" src="{{ teacher.image }}" alt="{{ teacher.complete_name }}">
+                {% if course.teachers %}
+                    <div class="panel panel-default">
+                        <div class="panel-body">
+                            <div class="panel-teachers">
+                                <h3 class="sub-title">{{ "Coaches"|get_lang }}</h3>
+                            </div>
+                            {%  for teacher in course.teachers %}
+                            <div class="coach-information">
+                                <div class="coach-header">
+                                    <div class="coach-avatar">
+                                        <img class="img-circle img-responsive" src="{{ teacher.image }}" alt="{{ teacher.complete_name }}">
+                                    </div>
+                                    <div class="coach-title">
+                                        <h4>{{ teacher.complete_name }}</h4>
+                                        <p> {{ teacher.diploma }}</p>
+                                    </div>
                                 </div>
-                                <div class="coach-title">
-                                    <h4>{{ teacher.complete_name }}</h4>
-                                    <p> {{ teacher.diploma }}</p>
+                                <div class="open-area  {{ course.teachers | length >= 2 ? 'open-more' : ' ' }}">
+                                    {{ teacher.openarea }}
                                 </div>
                             </div>
-                            <div class="open-area  {{ course.teachers | length >= 2 ? 'open-more' : ' ' }}">
-                                {{ teacher.openarea }}
-                            </div>
+                            {% endfor %}
                         </div>
-                        {% endfor %}
-
                     </div>
-                </div>
+                {% endif %}
             </div>
         </div>
     </section>
 </div>
 
-<script type="text/javascript">
+<script>
     $(document).ready(function() {
         $('.course-information').readmore({
             speed: 100,

+ 66 - 61
main/template/default/learnpath/view.tpl

@@ -20,7 +20,6 @@
                             </div>
                         {% endif %}
                     </div>
-
                     {% if show_audio_player %}
                         <div id="lp_media_file" class="audio-scorm">
                             {{ media_player }}
@@ -135,7 +134,7 @@
                                 </div>
                             </div>
                         </div>
-                       <!--- end gamification -->
+                        <!--- end gamification -->
                     {% else %}
                        {{ lp_progress }}
                     {% endif %}
@@ -143,11 +142,11 @@
                     {{ teacher_toc_buttons }}
                 </div>
             </div>
-        {# TOC layout #}
-        <div id="toc_id" class="scorm-body" name="toc_name">
+            {# TOC layout #}
+            <div id="toc_id" class="scorm-body" name="toc_name">
                 {% include 'learnpath/scorm_list.tpl'|get_template %}
             </div>
-        {# end TOC layout #}
+            {# end TOC layout #}
         </div>
     </div>
     {# end left zone #}
@@ -166,13 +165,17 @@
         </a>
         {% endif %}
 
-        <a id="home-course" title = "{{ 'Home'|get_lang }}" href="{{ button_home_url }}" class="icon-toolbar" target="_self" onclick="javascript: window.parent.API.save_asset();">
+        <a id="home-course"
+            title = "{{ 'Home'|get_lang }}"
+            href="{{ button_home_url }}"
+            class="icon-toolbar" target="_self"
+           onclick="javascript: window.parent.API.save_asset();">
             <em class="fa fa-home"></em> <span class="hidden-xs hidden-sm"></span>
         </a>
         {{ navigation_bar }}
     </div>
 
-    {# right zone #}
+    {# Right zone #}
     <div id="learning_path_right_zone" class="{{ show_left_column == 1 ? 'content-scorm' : 'no-right-col' }}">
         <div class="lp-view-zone-container">
             <div class="lp-view-tabs">
@@ -196,9 +199,25 @@
                     <div role="tabpanel" class="tab-pane active" id="lp-view-content">
                         <div id="wrapper-iframe">
                         {% if lp_mode == 'fullscreen' %}
-                            <iframe id="content_id_blank" name="content_name_blank" src="blank.php" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
+                            <iframe
+                                id="content_id_blank"
+                                name="content_name_blank"
+                                src="blank.php"
+                                style="width:100%; height:100%"
+                                border="0"
+                                frameborder="0"
+                                allowfullscreen="true"
+                                webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
                         {% else %}
-                            <iframe id="content_id" name="content_name" src="{{ iframe_src }}" style="width:100%; height:100%" border="0" frameborder="0" allowfullscreen="true" webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
+                            <iframe
+                                id="content_id"
+                                name="content_name"
+                                src="{{ iframe_src }}"
+                                style="width:100%; height:100%"
+                                border="0"
+                                frameborder="0"
+                                allowfullscreen="true"
+                                webkitallowfullscreen="true" mozallowfullscreen="true"></iframe>
                         {% endif %}
                         </div>
                     </div>
@@ -215,7 +234,6 @@
     var LPViewUtils = {
         setHeightLPToc: function () {
             var scormInfoHeight = $('#scorm-info').outerHeight(true);
-
             $('#learning_path_toc').css({
                 top: scormInfoHeight
             });
@@ -226,65 +244,56 @@
         if (/iPhone|iPod|iPad/.test(navigator.userAgent)) {
             // Fix an issue where you cannot scroll below first screen in
             // learning paths on Apple devices
-            document.getElementById('wrapper-iframe')
-                .setAttribute(
-                    'style',
-                    'width:100%; overflow:auto; position:auto; -webkit-overflow-scrolling:touch !important;'
-                );
+            document.getElementById('wrapper-iframe').setAttribute(
+                'style',
+                'width:100%; overflow:auto; position:auto; -webkit-overflow-scrolling:touch !important;'
+            );
             // Fix another issue whereby buttons do not react to click below
             // second screen in learning paths on Apple devices
-            document.getElementById('content_id')
-                .setAttribute(
-                    'style',
-                    'overflow: auto;'
-                );
-
+            document.getElementById('content_id').setAttribute('style', 'overflow: auto;');
         }
 
         {% if lp_mode == 'embedframe' %}
             //$('#learning_path_main').addClass('lp-view-collapsed');
             $('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
-            e.preventDefault();
-
-            $('#learning_path_main').toggleClass('lp-view-collapsed');
-            $('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
-            $('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
-            var className = $('#lp-view-expand-toggle span.fa').attr('class');
-            if (className == 'fa fa-expand') {
-                $(this).attr('title', '{{ "Expand" | get_lang }}');
-            } else {
-                $(this).attr('title', '{{ "Collapse" | get_lang }}');
-            }
-
-            if($('#navTabsbar').is(':hidden')){
-                $('#navTabsbar').show();
-            } else {
-                $('#navTabsbar').hide();
-            }
+                e.preventDefault();
+                $('#learning_path_main').toggleClass('lp-view-collapsed');
+                $('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
+                $('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
+                var className = $('#lp-view-expand-toggle span.fa').attr('class');
+                if (className == 'fa fa-expand') {
+                    $(this).attr('title', '{{ "Expand" | get_lang }}');
+                } else {
+                    $(this).attr('title', '{{ "Collapse" | get_lang }}');
+                }
 
-        });
+                if($('#navTabsbar').is(':hidden')) {
+                    $('#navTabsbar').show();
+                } else {
+                    $('#navTabsbar').hide();
+                }
+            });
         {% else %}
-        $('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
-            e.preventDefault();
-
-            $('#learning_path_main').toggleClass('lp-view-collapsed');
+            $('#lp-view-expand-button, #lp-view-expand-toggle').on('click', function (e) {
+                e.preventDefault();
 
-            $('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
-            $('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
+                $('#learning_path_main').toggleClass('lp-view-collapsed');
+                $('#lp-view-expand-toggle span.fa').toggleClass('fa-expand');
+                $('#lp-view-expand-toggle span.fa').toggleClass('fa-compress');
 
-            var className = $('#lp-view-expand-toggle span.fa').attr('class');
-            if (className == 'fa fa-expand') {
-                $(this).attr('title', '{{ "Expand" | get_lang }}');
-            } else {
-                $(this).attr('title', '{{ "Collapse" | get_lang }}');
-            }
+                var className = $('#lp-view-expand-toggle span.fa').attr('class');
+                if (className == 'fa fa-expand') {
+                    $(this).attr('title', '{{ "Expand" | get_lang }}');
+                } else {
+                    $(this).attr('title', '{{ "Collapse" | get_lang }}');
+                }
 
-            if($('#navTabsbar').is(':hidden')){
-                $('#navTabsbar').show();
-            } else {
-                $('#navTabsbar').hide();
-            }
-        });
+                if($('#navTabsbar').is(':hidden')){
+                    $('#navTabsbar').show();
+                } else {
+                    $('#navTabsbar').hide();
+                }
+            });
         {% endif %}
 
         $('.lp-view-tabs').on('click', '.disabled', function (e) {
@@ -293,9 +302,7 @@
 
         $('a#ui-option').on('click', function (e) {
             e.preventDefault();
-
             var icon = $(this).children('.fa');
-
             if (icon.is('.fa-chevron-up')) {
                 icon.removeClass('fa-chevron-up').addClass('fa-chevron-down');
 
@@ -317,7 +324,6 @@
 
         $('#learning_path_right_zone #lp-view-content iframe').on('load', function () {
             $('.lp-view-tabs a[href="#lp-view-content"]').tab('show');
-
             $('.lp-view-tabs').animate({opacity: 1}, 500);
         });
 
@@ -381,7 +387,6 @@
         {% 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');
                 }

+ 1 - 1
main/template/default/session/about.tpl

@@ -14,7 +14,7 @@
                 <div class="row">
                     <div class="col-md-8">
                         <h2 class="session-title">{{ session.name }}</h2>
-                        {% if show_tutor %}
+                        {% if show_tutor and session.generalCoach %}
                             <div class="session-tutor">
                                 <em class="fa fa-user"></em> {{ 'SessionGeneralCoach'|get_lang }}:
                                 <em>{{ session.generalCoach.getCompleteName() }}</em>

+ 3 - 0
main/template/default/social/personal_data.tpl

@@ -38,6 +38,7 @@
         </div>
         {% endif %}
         {% if personal_data.officer_name %}
+            {% set officer_data %}
             <div class="panel personal-data-responsible">
                 <div class="panel-title">{{ 'PersonalDataOfficerName' | get_lang }}</div>
                 <div class="personal-data-responsible-description">
@@ -48,6 +49,8 @@
                     {{ personal_data.officer_role }}
                 </div>
             </div>
+            {% endset %}
+            {{ display.panel('PersonalDataOfficer' | get_lang , officer_data ) }}
         {% endif %}
 
         {% if term_link %}

+ 22 - 22
main/template/default/tracking/tracking_course_log.tpl

@@ -1,22 +1,20 @@
 <!-- tracking course log -->
-<script type="text/javascript">
-    window.onload = function() {
+<script>
+window.onload = function() {
     var scoreStudent = document.getElementById("chart-score").getContext('2d');
     var lastAccess = document.getElementById("chart-access").getContext('2d');
-
     var jsonfile = {{ json_time_student }};
     var labels = [];
     var times = [];
 
-       Object.keys(jsonfile).forEach(function(key) {
-           //Names
-           labels.push(jsonfile[key].fullname);
-           //Time plataform total
-           times.push(jsonfile[key].total_time);
-       });
-
+   Object.keys(jsonfile).forEach(function(key) {
+       //Names
+       labels.push(jsonfile[key].fullname);
+       //Time plataform total
+       times.push(jsonfile[key].total_time);
+   });
 
-    var myChartAccess = new Chart(lastAccess,{
+    var myChartAccess = new Chart(lastAccess, {
         type: 'line',
         data: {
             labels: labels,
@@ -124,7 +122,6 @@
         <div class="col-lg-3 col-sm-3">
             <div class="panel panel-default tracking tracking-lessons">
                 <div class="panel-body">
-
                     <span class="tracking-icon">
                         <i class="fa fa-book" aria-hidden="true"></i>
                     </span>
@@ -134,14 +131,12 @@
                             {{ students_completed_lp }}/{{ number_students }}
                         </div>
                     </div>
-
                 </div>
             </div>
         </div>
         <div class="col-lg-3 col-sm-3">
             <div class="panel panel-default tracking tracking-exercise">
                 <div class="panel-body">
-
                     <span class="tracking-icon">
                         <i class="fa fa-heartbeat" aria-hidden="true"></i>
                     </span>
@@ -151,14 +146,12 @@
                             {{ students_test_score }}%
                         </div>
                     </div>
-
                 </div>
             </div>
         </div>
         <div class="col-lg-3 col-sm-3">
             <div class="panel panel-default tracking tracking-certificate">
                 <div class="panel-body">
-
                     <span class="tracking-icon">
                         <i class="fa fa-id-card-o" aria-hidden="true"></i>
                     </span>
@@ -168,7 +161,6 @@
                             {{ certificate_count }}/{{ number_students }}
                         </div>
                     </div>
-
                 </div>
             </div>
         </div>
@@ -189,20 +181,27 @@
                 <div class="panel-body">
                     <h4 class="tracking-box-title">{{ 'OutstandingStudents'|get_lang }}</h4>
                     <ul class="list-top">
-
                         {% for student in top_students %}
                             {% set counter = counter + 1 %}
                             {% if counter <= 3 %}
                             <li>
                                 <div class="avatar">
                                     <span class="round">
-                                        <img title="{{ student.fullname }}" alt="{{ student.fullname }}" src="{{ student.avatar }}" width="40px">
+                                        <img
+                                                title="{{ student.fullname }}"
+                                                alt="{{ student.fullname }}"
+                                                src="{{ student.avatar }}"
+                                                width="40px">
                                     </span>
                                 </div>
                                 <div class="info">
                                     <h3 class="name">{{ student.fullname }}</h3>
                                     <div class="progress">
-                                        <div class="progress-bar progress-bar-success progress-bar-striped" role="progressbar" aria-valuenow="{{ student.score }}" aria-valuemin="0" aria-valuemax="100" style="width: {{ student.score }}%;">
+                                        <div
+                                            class="progress-bar progress-bar-success progress-bar-striped"
+                                            role="progressbar"
+                                            aria-valuenow="{{ student.score }}" aria-valuemin="0"
+                                            aria-valuemax="100" style="width: {{ student.score }}%;">
                                             {{ student.score }}%
                                         </div>
                                     </div>
@@ -210,9 +209,10 @@
                             </li>
                             {% endif %}
                         {% endfor %}
-
                     </ul>
-                   <span class="tracking-box-legend">{{ 'ProgressObtainedFromLPProgressAndTestsAverage'|get_lang }}</span>
+                   <span class="tracking-box-legend">
+                       {{ 'ProgressObtainedFromLPProgressAndTestsAverage'|get_lang }}
+                   </span>
                 </div>
             </div>
         </div>

+ 67 - 67
main/template/default/user_portal/classic_courses_with_category.tpl

@@ -1,17 +1,16 @@
+{% import 'macro/macro.tpl'|get_template as display %}
+
 {% if not categories is empty %}
     <div class="classic-courses">
         {% for category in categories %}
             {% if category.courses %}
-                <div class="panel panel-default">
-                <div id="category-{{ category.id_category }}" class="panel-heading">
-                    {{ category.title_category }}
-                </div>
-                <div class="panel-body">
-                    {% for item in category.courses %}
-                        <div class="row">
-                            <div class="col-md-2">
-                                {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
-                                    <span class="thumbnail">
+                {% set course_content %}
+
+                {% for item in category.courses %}
+                    <div class="row">
+                        <div class="col-md-2">
+                            {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                                <span class="thumbnail">
                                         {% if item.thumbnails != '' %}
                                             <img src="{{ item.thumbnails }}" title="{{ item.title }}"
                                                  alt="{{ item.title }}"/>
@@ -19,83 +18,84 @@
                                             {{ 'blackboard.png' | img(48, item.title ) }}
                                         {% endif %}
                                     </span>
-                                {% else %}
-                                    <a href="{{ item.link }}" class="thumbnail">
-                                        {% if item.thumbnails != '' %}
-                                            <img src="{{ item.thumbnails }}" title="{{ item.title }}"
-                                                 alt="{{ item.title }}"/>
-                                        {% else %}
-                                            {{ 'blackboard.png' | img(48, item.title ) }}
-                                        {% endif %}
-                                    </a>
-                                {% endif %}
-                            </div>
-                            <div class="col-md-10">
-                                {% if item.edit_actions != '' %}
-                                    <div class="pull-right">
-                                        {% if item.document == '' %}
+                            {% else %}
+                                <a href="{{ item.link }}" class="thumbnail">
+                                    {% if item.thumbnails != '' %}
+                                        <img src="{{ item.thumbnails }}" title="{{ item.title }}"
+                                             alt="{{ item.title }}"/>
+                                    {% else %}
+                                        {{ 'blackboard.png' | img(48, item.title ) }}
+                                    {% endif %}
+                                </a>
+                            {% endif %}
+                        </div>
+                        <div class="col-md-10">
+                            {% if item.edit_actions != '' %}
+                                <div class="pull-right">
+                                    {% if item.document == '' %}
+                                        <a class="btn btn-default btn-sm" href="{{ item.edit_actions }}">
+                                            <i class="fa fa-pencil" aria-hidden="true"></i>
+                                        </a>
+                                    {% else %}
+                                        <div class="btn-group" role="group">
                                             <a class="btn btn-default btn-sm" href="{{ item.edit_actions }}">
                                                 <i class="fa fa-pencil" aria-hidden="true"></i>
                                             </a>
-                                        {% else %}
-                                            <div class="btn-group" role="group">
-                                                <a class="btn btn-default btn-sm" href="{{ item.edit_actions }}">
-                                                    <i class="fa fa-pencil" aria-hidden="true"></i>
-                                                </a>
-                                                {{ item.document }}
-                                            </div>
-                                        {% endif %}
-                                    </div>
-                                {% endif %}
-                                <h4 class="course-items-title">
-                                    {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                                            {{ item.document }}
+                                        </div>
+                                    {% endif %}
+                                </div>
+                            {% endif %}
+                            <h4 class="course-items-title">
+                                {% if item.visibility == constant('COURSE_VISIBILITY_CLOSED') and not item.current_user_is_teacher %}
+                                    {{ item.title }} {{ item.code_course }}
+                                {% else %}
+                                    <a href="{{ item.link }}">
                                         {{ item.title }} {{ item.code_course }}
-                                    {% else %}
-                                        <a href="{{ item.link }}">
-                                            {{ item.title }} {{ item.code_course }}
-                                        </a>
-                                        {{ item.notifications }}
+                                    </a>
+                                    {{ item.notifications }}
+                                {% endif %}
+                            </h4>
+                            <div class="course-items-session">
+                                <div class="list-teachers">
+                                    {% if item.teachers|length > 0 %}
+                                        <img src="{{ 'teacher.png'|icon(16) }}" width="16" height="16">&nbsp;
+                                        {% for teacher in item.teachers %}
+                                            {% set counter = counter + 1 %}
+                                            {% if counter > 1 %} | {% endif %}
+                                            <a href="{{ teacher.url }}" class="ajax"
+                                               data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
+                                                {{ teacher.firstname }} {{ teacher.lastname }}
+                                            </a>
+                                        {% endfor %}
                                     {% endif %}
-                                </h4>
-                                <div class="course-items-session">
-                                    <div class="list-teachers">
-                                        {% if item.teachers|length > 0 %}
-                                            <img src="{{ 'teacher.png'|icon(16) }}" width="16" height="16">&nbsp;
-                                            {% for teacher in item.teachers %}
-                                                {% set counter = counter + 1 %}
-                                                {% if counter > 1 %} | {% endif %}
-                                                <a href="{{ teacher.url }}" class="ajax"
-                                                   data-title="{{ teacher.firstname }} {{ teacher.lastname }}">
-                                                    {{ teacher.firstname }} {{ teacher.lastname }}
-                                                </a>
-                                            {% endfor %}
-                                        {% endif %}
-                                    </div>
-                                    {% if item.student_info %}
-                                        {% if item.student_info.progress is not null or item.student_info.score is not null or item.student_info.certificate is not null %}
+                                </div>
+                                {% if item.student_info %}
+                                    {% if item.student_info.progress is not null or item.student_info.score is not null or item.student_info.certificate is not null %}
                                         <div class="course-student-info">
                                             <div class="student-info">
                                                 {% if (item.student_info.progress is not null) %}
-                                                {{ "StudentCourseProgressX" | get_lang | format(item.student_info.progress) }}
+                                                    {{ "StudentCourseProgressX" | get_lang | format(item.student_info.progress) }}
                                                 {% endif %}
 
                                                 {% if (item.student_info.score is not null) %}
-                                                {{ "StudentCourseScoreX" | get_lang | format(item.student_info.score) }}
+                                                    {{ "StudentCourseScoreX" | get_lang | format(item.student_info.score) }}
                                                 {% endif %}
 
                                                 {% if (item.student_info.certificate is not null) %}
-                                                {{ "StudentCourseCertificateX" | get_lang | format(item.student_info.certificate) }}
+                                                    {{ "StudentCourseCertificateX" | get_lang | format(item.student_info.certificate) }}
                                                 {% endif %}
                                             </div>
                                         </div>
-                                        {% endif %}
                                     {% endif %}
-                                </div>
+                                {% endif %}
                             </div>
                         </div>
-                    {% endfor %}
-                </div>
-            </div>
+                    </div>
+                {% endfor %}
+                {% endset %}
+
+                {{ display.collapse('course_category_' ~ category.id_category, category.title_category, course_content, false, category.collapsed == 0) }}
             {% endif %}
         {% endfor %}
     </div>

+ 13 - 1
main/template/default/user_portal/classic_session.tpl

@@ -2,8 +2,8 @@
 
 {% for row in session %}
     <div class="panel panel-default">
+        {% set collapsable = '' %}
         {% if not row.show_simple_session_info %}
-            {% set collapsable = '' %}
             {% if row.course_list_session_style %} {# If not style then no show header #}
                 <div class="panel-heading">
                     {% if row.course_list_session_style == 1 or row.course_list_session_style == 2 %} {# Session link #}
@@ -38,8 +38,20 @@
                             </a>
                         </div>
                     {% endif %}
+                    {% if row.collapsable_link %}
+                        <div class="pull-right">
+                            {{ row.collapsable_link }}
+                        </div>
+                    {% endif %}
                 </div>
             {% endif %}
+
+            {% if row.collapsable_link %}
+                {% if row.collapsed == 1 %}
+                    {% set collapsable = 'collapse' %}
+                {% endif %}
+            {% endif %}
+
             <div class="session panel-body {{ collapsable }}" id="collapse_{{ row.id }}">
                 <div class="row">
                     <div class="col-md-12">

+ 0 - 1
main/template/default/user_portal/grid_courses_with_category.tpl

@@ -88,7 +88,6 @@
                                             </div>
                                         {% endif %}
                                     {% endif %}
-
                                 </div>
                             </div>
                         </div>

+ 13 - 0
main/template/default/user_portal/grid_session.tpl

@@ -120,6 +120,7 @@
 {% set session_image = 'window_list.png'|img(32, row.title) %}
 
 {% for row in session %}
+    {% set collapsable = '' %}
     <div id="session-{{ item.id }}" class="session panel panel-default">
         {% if row.course_list_session_style %} {# If not style then no show header #}
             <div class="panel-heading">
@@ -155,8 +156,20 @@
                         </a>
                     </div>
                 {% endif %}
+                {% if row.collapsable_link %}
+                    <div class="pull-right">
+                       {{ row.collapsable_link }}
+                    </div>
+                {% endif %}
             </div>
         {% endif %}
+
+        {% if row.collapsable_link %}
+            {% if row.collapsed == 1 %}
+                {% set collapsable = 'collapse' %}
+            {% endif %}
+        {% endif %}
+
         <div class="session panel-body {{ collapsable }}" id="collapse_{{ row.id }}">
             {% if row.show_description %}
                 {{ row.description }}

+ 6 - 9
main/tracking/courseLog.php

@@ -166,16 +166,10 @@ $tpl = new Template($nameTools);
 // getting all the students of the course
 if (empty($session_id)) {
     // Registered students in a course outside session.
-    $a_students = CourseManager::get_student_list_from_course_code(
-        $courseId
-    );
+    $a_students = CourseManager::get_student_list_from_course_code($courseId);
 } else {
     // Registered students in session.
-    $a_students = CourseManager::get_student_list_from_course_code(
-        $courseId,
-        true,
-        $sessionId
-    );
+    $a_students = CourseManager::get_student_list_from_course_code($courseId, true, $sessionId);
 }
 
 $nbStudents = count($a_students);
@@ -337,10 +331,13 @@ if ($showReporting) {
         $html .= '</ul>';
     }
 }
+//Load Orden
+$trackingColumn = isset($_GET['users_tracking_column']) ? $_GET['users_tracking_column'] : null;
+$trackingDirection = isset($_GET['users_tracking_direction']) ? $_GET['users_tracking_direction'] : null;
 
 // Show the charts part only if there are students subscribed to this course/session
 if ($nbStudents > 0) {
-    $usersTracking = TrackingCourseLog::get_user_data(null, $nbStudents, null, 'DESC', false);
+    $usersTracking = TrackingCourseLog::get_user_data(null, $nbStudents, $trackingColumn, $trackingDirection, false);
     $numberStudentsCompletedLP = 0;
     $averageStudentsTestScore = 0;
     $scoresDistribution = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0];

+ 5 - 5
main/tracking/course_log_tools.php

@@ -126,7 +126,7 @@ if ($lpReporting) {
             Display::return_icon(
                 'scorms.gif',
                 get_lang('AverageProgressInLearnpath')
-            ).get_lang('AverageProgressInLearnpath')
+            ).' '.get_lang('AverageProgressInLearnpath')
         );
         echo '<table class="data_table">';
         if ($export_csv) {
@@ -180,7 +180,7 @@ if ($exerciseReporting) {
         Display::return_icon(
             'quiz.png',
             get_lang('AverageResultsToTheExercices')
-        ).get_lang('AverageResultsToTheExercices')
+        ).' '.get_lang('AverageResultsToTheExercices')
     );
     echo '<table class="data_table">';
     $course_id = api_get_course_int_id();
@@ -269,7 +269,7 @@ if ($export_csv) {
 // Forums tracking.
 echo '<div class="report_section">';
 echo Display::page_subheader(
-    Display::return_icon('forum.gif', get_lang('Forum')).
+    Display::return_icon('forum.gif', get_lang('Forum')).' '.
     get_lang('Forum').'&nbsp;-&nbsp;<a href="../forum/index.php?'.api_get_cidreq().'">'.
     get_lang('SeeDetail').'</a>'
 );
@@ -284,7 +284,7 @@ echo '<div class="clear"></div>';
 if ($showChatReporting) {
     echo '<div class="report_section">';
     echo Display::page_subheader(
-        Display::return_icon('chat.gif', get_lang('Chat')).get_lang('Chat')
+        Display::return_icon('chat.gif', get_lang('Chat')).' '.get_lang('Chat')
     );
 
     echo '<table class="data_table">';
@@ -321,7 +321,7 @@ if ($showTrackingReporting) {
         Display::return_icon(
             'acces_tool.gif',
             get_lang('ToolsMostUsed')
-        ).get_lang('ToolsMostUsed')
+        ).' '.get_lang('ToolsMostUsed')
     );
     echo '<table class="data_table">';
 

+ 1 - 1
main/tracking/exams.php

@@ -118,7 +118,7 @@ if (!$exportToXLS) {
     } else {
         $actionsLeft = TrackingCourseLog::actionsLeft('exams', api_get_session_id());
 
-        $actionsLeft .= Display::url(
+        $actionsRight .= Display::url(
             Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), [], 32),
             api_get_self().'?'.api_get_cidreq().'&export=1&score='.$filter_score.'&exercise_id='.$exerciseId
         );

+ 64 - 46
main/user/subscribe_user.php

@@ -30,25 +30,25 @@ if (!api_is_allowed_to_edit()) {
     api_not_allowed(true);
 }
 
-$tool_name = get_lang("SubscribeUserToCourse");
+$tool_name = get_lang('SubscribeUserToCourse');
 $type = isset($_REQUEST['type']) ? intval($_REQUEST['type']) : STUDENT;
 $keyword = isset($_REQUEST['keyword']) ? Security::remove_XSS($_REQUEST['keyword']) : null;
 
 $courseInfo = api_get_course_info();
 
 if ($type == COURSEMANAGER) {
-    $tool_name = get_lang("SubscribeUserToCourseAsTeacher");
+    $tool_name = get_lang('SubscribeUserToCourseAsTeacher');
 }
 
 //extra entries in breadcrumb
 $interbreadcrumb[] = [
-    "url" => "user.php?".api_get_cidreq(),
-    "name" => get_lang("ToolUser"),
+    'url' => 'user.php?'.api_get_cidreq(),
+    'name' => get_lang('ToolUser'),
 ];
 if ($keyword) {
     $interbreadcrumb[] = [
-        "url" => "subscribe_user.php?type=".$type.'&'.api_get_cidreq(),
-        "name" => $tool_name,
+        'url' => 'subscribe_user.php?type='.$type.'&'.api_get_cidreq(),
+        'name' => $tool_name,
     ];
     $tool_name = get_lang('SearchResults');
 }
@@ -59,30 +59,29 @@ $list_not_register_user = '';
 
 if (isset($_REQUEST['register'])) {
     $userInfo = api_get_user_info($_REQUEST['user_id']);
-    $message = $userInfo['complete_name_with_username'].' '.get_lang('AddedToCourse');
-
-    if ($type === COURSEMANAGER) {
-        if (!empty($sessionId)) {
-            SessionManager::set_coach_to_course_session(
-                $_REQUEST['user_id'],
-                $sessionId,
-                $courseInfo['real_id']
-            );
-            Display::addFlash(Display::return_message($message));
+    if ($userInfo) {
+        if ($type === COURSEMANAGER) {
+            if (!empty($sessionId)) {
+                $message = $userInfo['complete_name_with_username'].' '.get_lang('AddedToCourse');
+                SessionManager::set_coach_to_course_session(
+                    $_REQUEST['user_id'],
+                    $sessionId,
+                    $courseInfo['real_id']
+                );
+                Display::addFlash(Display::return_message($message));
+            } else {
+                CourseManager::subscribeUser(
+                    $_REQUEST['user_id'],
+                    $courseInfo['code'],
+                    COURSEMANAGER
+                );
+            }
         } else {
-            CourseManager::subscribe_user(
+            CourseManager::subscribeUser(
                 $_REQUEST['user_id'],
-                $courseInfo['code'],
-                COURSEMANAGER
+                $courseInfo['code']
             );
-            Display::addFlash(Display::return_message($message));
         }
-    } else {
-        CourseManager::subscribe_user(
-            $_REQUEST['user_id'],
-            $courseInfo['code']
-        );
-        Display::addFlash(Display::return_message($message));
     }
     header('Location:'.api_get_path(WEB_CODE_PATH).'user/user.php?'.api_get_cidreq().'&type='.$type);
     exit;
@@ -92,30 +91,50 @@ if (isset($_POST['action'])) {
     switch ($_POST['action']) {
         case 'subscribe':
             if (is_array($_POST['user'])) {
+                $isSuscribe = [];
                 foreach ($_POST['user'] as $index => $user_id) {
                     $userInfo = api_get_user_info($user_id);
-                    if ($type === COURSEMANAGER) {
-                        if (!empty($sessionId)) {
-                            $is_suscribe[] = SessionManager::set_coach_to_course_session(
-                                $user_id,
-                                $sessionId,
-                                $courseInfo['real_id']
-                            );
+                    if ($userInfo) {
+                        $message = $userInfo['complete_name_with_username'].' '.get_lang('AddedToCourse');
+                        if ($type === COURSEMANAGER) {
+                            if (!empty($sessionId)) {
+                                $result = SessionManager::set_coach_to_course_session(
+                                    $user_id,
+                                    $sessionId,
+                                    $courseInfo['real_id']
+                                );
+                                if ($result) {
+                                    $isSuscribe[] = $message;
+                                }
+                            } else {
+                                $result = CourseManager::subscribeUser(
+                                    $user_id,
+                                    $courseInfo['code'],
+                                    COURSEMANAGER
+                                );
+
+                                if ($result) {
+                                    $isSuscribe[] = $message;
+                                }
+                            }
                         } else {
-                            $is_suscribe[] = CourseManager::subscribe_user(
+                            $result = CourseManager::subscribeUser(
                                 $user_id,
-                                $courseInfo['code'],
-                                COURSEMANAGER
+                                $courseInfo['code']
                             );
+                            if ($result) {
+                                $isSuscribe[] = $message;
+                            } else {
+                                $isSuscribe[] = get_lang('MaxNumberSubscribedStudentsReached').' '.$userInfo['complete_name_with_username'];
+                            }
                         }
-                    } else {
-                        $is_suscribe[] = CourseManager::subscribe_user(
-                            $user_id,
-                            $courseInfo['code']
-                        );
                     }
-                    $message = $userInfo['complete_name_with_username'].' '.get_lang('AddedToCourse');
-                    Display::addFlash(Display::return_message($message));
+                }
+
+                if (!empty($isSuscribe)) {
+                    foreach ($isSuscribe as $info) {
+                        Display::addFlash(Display::return_message($info));
+                    }
                 }
             }
 
@@ -164,10 +183,9 @@ if (!empty($_POST['keyword'])) {
     echo '<br/>'.get_lang('SearchResultsFor').' <span style="font-style: italic ;"> '.$keyword_name.' </span><br>';
 }
 
-Display :: display_header($tool_name, "User");
+Display :: display_header($tool_name, 'User');
 
 // Build search-form
-
 switch ($type) {
     case STUDENT:
         $url = api_get_path(WEB_CODE_PATH).'user/user.php?'.api_get_cidreq().'';
@@ -176,7 +194,7 @@ switch ($type) {
         $url = api_get_path(WEB_CODE_PATH).'user/user.php?'.api_get_cidreq().'&type='.COURSEMANAGER;
         break;
 }
-$actionsLeft = '';
+
 $actionsLeft = Display::url(
     Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM),
     $url

+ 2 - 2
main/user/user_import.php

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

+ 1 - 1
main/webservices/cm_webservice_course.php

@@ -696,7 +696,7 @@ class WSCMCourse extends WSCM
                     return true;
                 } else {
                     // Subscribe user
-                    if (CourseManager::subscribe_user($user_id, $course_code, $status)) {
+                    if (CourseManager::subscribeUser($user_id, $course_code, $status)) {
                         return true;
                     } else {
                         return new WSError(203, 'An error occured subscribing to this course');

+ 2 - 2
main/webservices/registration.soap.php

@@ -4653,7 +4653,7 @@ function WSSubscribeUserToCourse($params)
                 if ($debug) {
                     error_log('WSSubscribeUserToCourse courseCode: '.$courseCode);
                 }
-                $result = CourseManager::add_user_to_course($user_id, $courseCode, $status, false, false);
+                $result = CourseManager::subscribeUser($user_id, $courseCode, $status, 0, 0, false);
                 if ($result) {
                     $resultValue = 1;
                     if ($debug) {
@@ -4767,7 +4767,7 @@ function WSSubscribeUserToCourseSimple($params)
             if ($debug) {
                 error_log('Try to register: user_id= '.$user_id.' to course: '.$course_data['code']);
             }
-            if (!CourseManager::add_user_to_course($user_id, $course_data['code'], $status, false, false)) {
+            if (!CourseManager::subscribeUser($user_id, $course_data['code'], $status, 0, false, false)) {
                 $result = 'User was not registered possible reasons: User already registered to the course, 
                            Course visibility doesnt allow user subscriptions ';
                 if ($debug) {

+ 1 - 1
main/webservices/webservice_course.php

@@ -713,7 +713,7 @@ class WSCourse extends WS
                     return true;
                 } else {
                     // Subscribe user
-                    if (CourseManager::subscribe_user(
+                    if (CourseManager::subscribeUser(
                         $user_id,
                         $course_code,
                         $status

+ 3 - 0
plugin/buycourses/admin.php

@@ -0,0 +1,3 @@
+<?php
+// Redirect to buycourses/index.php
+header('location: index.php');

+ 3 - 1
plugin/buycourses/src/buy_course_plugin.class.php

@@ -52,6 +52,8 @@ class BuyCoursesPlugin extends Plugin
     const CULQI_INTEGRATION_TYPE = 'INTEG';
     const CULQI_PRODUCTION_TYPE = 'PRODUC';
 
+    public $isAdminPlugin = true;
+
     /**
      * BuyCoursesPlugin constructor.
      */
@@ -879,7 +881,7 @@ class BuyCoursesPlugin extends Plugin
         switch ($sale['product_type']) {
             case self::PRODUCT_TYPE_COURSE:
                 $course = api_get_course_info_by_id($sale['product_id']);
-                $saleIsCompleted = CourseManager::subscribe_user($sale['user_id'], $course['code']);
+                $saleIsCompleted = CourseManager::subscribeUser($sale['user_id'], $course['code']);
                 break;
             case self::PRODUCT_TYPE_SESSION:
                 SessionManager::subscribeUsersToSession(

+ 0 - 19
plugin/buycourses/view/configuration.tpl

@@ -101,25 +101,6 @@
                     {% for item in sessions %}
                         <tr data-item="{{ item.session_id }}" data-type="session">
                             <td>
-                                {% if item.session_visibility == 0 %}
-                                    <img src="{{ 'bullet_red.png'|icon() }}"
-                                         alt="{{ 'CourseVisibilityClosed'|get_lang }}"
-                                         title="{{ 'CourseVisibilityClosed'|get_lang }}">
-                                {% elseif item.session_visibility == 1 %}
-                                    <img src="{{ 'bullet_orange.png'|icon() }}" alt="{{ 'Private'|get_lang }}"
-                                         title="{{ 'Private'|get_lang }}">
-                                {% elseif item.session_visibility == 2 %}
-                                    <img src="{{ 'bullet_green.png'|icon() }}" alt="{{ 'OpenToThePlatform'|get_lang }}"
-                                         title="{{ 'OpenToThePlatform'|get_lang }}">
-                                {% elseif item.session_visibility == 3 %}
-                                    <img src="{{ 'bullet_blue.png'|icon() }}" alt="{{ 'OpenToTheWorld'|get_lang }}"
-                                         title="{{ 'OpenToTheWorld'|get_lang }}">
-                                {% elseif item.session_visibility == 4 %}
-                                    <img src="{{ 'bullet_gray.png'|icon() }}"
-                                         alt="{{ 'CourseVisibilityHidden'|get_lang }}"
-                                         title="{{ 'CourseVisibilityHidden'|get_lang }}">
-                                {% endif %}
-
                                 <a href="{{ _p.web_main ~ 'session/index.php?' ~ {'session_id': item.session_id}|url_encode() }}">{{ item.session_name }}</a>
                             </td>
                             <td class="text-center">

+ 13 - 0
plugin/ims_lti/ImsLtiPlugin.php

@@ -543,4 +543,17 @@ class ImsLtiPlugin extends Plugin
             }
         }
     }
+
+    /**
+     * Avoid conflict with foreign key when deleting a course
+     *
+     * @param int $courseId
+     */
+    public function doWhenDeletingCourse($courseId)
+    {
+        $em = Database::getManager();
+
+        $em->createQuery('DELETE FROM ChamiloPluginBundle:ImsLti\ImsLtiTool tool WHERE tool.course = :c_id')
+            ->execute(['c_id' => (int) $courseId]);
+    }
 }

+ 1 - 1
plugin/search_course/lib/register_course_widget.class.php

@@ -109,7 +109,7 @@ class RegisterCourseWidget
             $user_id = $_user['user_id'];
         }
 
-        return (bool) CourseManager::add_user_to_course($user_id, $course_code);
+        return (bool) CourseManager::subscribeUser($user_id, $course_code);
     }
 
     /**

+ 11 - 0
plugin/studentfollowup/StudentFollowUpPlugin.php

@@ -240,4 +240,15 @@ class StudentFollowUpPlugin extends Plugin
     {
         return 20;
     }
+
+    /**
+     * @param int $userId
+     */
+    public function doWhenDeletingUser($userId)
+    {
+        $userId = (int) $userId;
+
+        Database::query("DELETE FROM sfu_post WHERE user_id = $userId");
+        Database::query("DELETE FROM sfu_post WHERE insert_user_id = $userId");
+    }
 }

+ 4 - 0
plugin/whispeakauth/WhispeakAuthPlugin.php

@@ -63,6 +63,10 @@ class WhispeakAuthPlugin extends Plugin
         }
 
         $em = Database::getManager();
+
+        $em->createQuery('DELETE FROM ChamiloCoreBundle:ExtraFieldValues efv WHERE efv.field = :field')
+            ->execute(['field' => $extraField]);
+
         $em->remove($extraField);
         $em->flush();
     }

+ 2 - 2
tests/behat/features/course_user_registration.feature

@@ -7,7 +7,7 @@ Feature: Subscribe users to the course
     Given I am on "/main/user/subscribe_user.php?keyword=amann&type=5&cidReq=TEMP"
     Then I should see "Aimee"
     Then I follow "Register"
-    Then I should see "Aimee Mann (amann) has been registered to your course"
+    Then I should see "User Aimee Mann (amann) has been registered to course TEMP"
 
   Scenario: Unsubscribe user "amann" the course "TEMP"
     Given I am on "/main/user/user.php?cidReq=TEMP"
@@ -20,4 +20,4 @@ Feature: Subscribe users to the course
     Given I am on "/main/user/subscribe_user.php?keyword=acostea&type=5&cidReq=TEMP"
     Then I should see "Andrea"
     Then I follow "Register"
-    Then I should see "Andrea Costea (acostea) has been registered to your course"
+    Then I should see "User Andrea Costea (acostea) has been registered to course TEMP"

+ 1 - 1
tests/scripts/users_no_course.php

@@ -27,7 +27,7 @@ $students = Database::store_result($result);
 if (!empty($students)) {
     foreach ($students as $student) {
         var_dump($student['username'].'- '.$student['user_id']);
-        $result = CourseManager::add_user_to_course($student['user_id'], $courseCode);
+        $result = CourseManager::subscribeUser($student['user_id'], $courseCode);
         var_dump($result);
         echo '<br />';
     }

+ 22 - 0
user_portal.php

@@ -46,6 +46,28 @@ Event::registerLog($logInfo);
 
 $userId = api_get_user_id();
 
+$collapsable = api_get_configuration_value('allow_user_session_collapsable');
+if ($collapsable) {
+    $action = isset($_REQUEST['action']) ? $_REQUEST['action'] : '';
+    $sessionId = isset($_REQUEST['session_id']) ? $_REQUEST['session_id'] : '';
+    $value = isset($_REQUEST['value']) ? (int) $_REQUEST['value'] : '';
+    switch ($action) {
+        case 'collapse_session':
+            if (!empty($sessionId)) {
+                $userRelSession = SessionManager::getUserSession($userId, $sessionId);
+                if ($userRelSession) {
+                    $table = Database::get_main_table(TABLE_MAIN_SESSION_USER);
+                    $sql = "UPDATE $table SET collapsed = $value WHERE id = ".$userRelSession['id'];
+                    Database::query($sql);
+                    Display::addFlash(Display::return_message(get_lang('Updated')));
+                }
+                header('Location: user_portal.php');
+                exit;
+            }
+            break;
+    }
+}
+
 /* Constants and CONFIGURATION parameters */
 $load_dirs = api_get_setting('show_documents_preview');
 $displayMyCourseViewBySessionLink = api_get_setting('my_courses_view_by_session') === 'true';