Jelajahi Sumber

Merge 1.11.x

Julio 9 tahun lalu
induk
melakukan
f98e142425
93 mengubah file dengan 2095 tambahan dan 1133 penghapusan
  1. 0 1
      .gitignore
  2. 1 1
      README.md
  3. 1 0
      app/AppKernel.php
  4. 9 0
      app/Resources/public/css/base.css
  5. 2 2
      app/config/config.yml
  6. 2 1
      app/config/parameters.yml.dist
  7. 4 0
      app/config/routing.yml
  8. 1 0
      app/config/sonata/sonata_admin.yml
  9. 1 0
      app/config/sonata/sonata_page.yml
  10. 2 1
      app/config/sonata/sonata_translation.yml
  11. 6 5
      composer.json
  12. 84 0
      documentation/changelog.html
  13. 11 0
      main/badge/issued.php
  14. 17 4
      main/course_info/infocours.php
  15. 1 1
      main/document/document.php
  16. 2 2
      main/document/edit_document.php
  17. 3 2
      main/dropbox/index.php
  18. 2 1
      main/exercice/exercise.class.php
  19. 1 1
      main/gradebook/lib/be/category.class.php
  20. 1 3
      main/inc/ajax/course_home.ajax.php
  21. 18 0
      main/inc/ajax/user_manager.ajax.php
  22. 2 1
      main/inc/lib/add_course.lib.inc.php
  23. 1 1
      main/inc/lib/api.lib.php
  24. 20 4
      main/inc/lib/course.lib.php
  25. 6 6
      main/inc/lib/database.lib.php
  26. 0 181
      main/inc/lib/formvalidator/Element/DatePicker.php
  27. 3 117
      main/inc/lib/internationalization.lib.php
  28. 2 1
      main/inc/lib/message.lib.php
  29. 2 2
      main/inc/lib/notebook.lib.php
  30. 2 0
      main/inc/lib/plugin.class.php
  31. 0 327
      main/inc/lib/ppt2png/DocumentConverter.java.orig
  32. 4 2
      main/inc/lib/social.lib.php
  33. 10 3
      main/inc/lib/sortable_table.class.php
  34. 7 1
      main/inc/lib/usermanager.lib.php
  35. 29 17
      main/inc/lib/userportal.lib.php
  36. 2 1
      main/lang/brazilian/trad4all.inc.php
  37. 4 0
      main/lang/english/trad4all.inc.php
  38. 6 0
      main/lang/french/trad4all.inc.php
  39. 4 0
      main/lang/spanish/trad4all.inc.php
  40. 1 1
      main/mySpace/company_reports.php
  41. 1 1
      main/mySpace/company_reports_resumed.php
  42. 2 2
      main/mySpace/myStudents.php
  43. 1 1
      main/mySpace/student.php
  44. 5 0
      main/newscorm/final_item_template/template.html
  45. 210 50
      main/newscorm/learnpath.class.php
  46. 5 5
      main/newscorm/lp_add_item.php
  47. 47 4
      main/newscorm/lp_controller.php
  48. 128 0
      main/newscorm/lp_final_item.php
  49. 3 0
      main/template/default/user_portal/index.tpl
  50. 5 0
      main/template/default/user_portal/index_grid.tpl
  51. 65 65
      main/template/default/user_portal/session.tpl
  52. 1 0
      main/user/user.php
  53. 1 1
      main/work/work.lib.php
  54. 0 2
      plugin/ticket/config.install.php
  55. 2 0
      plugin/ticket/config.php
  56. 14 4
      plugin/ticket/database.php
  57. 1 1
      plugin/ticket/install.php
  58. 13 0
      plugin/ticket/lang/english.php
  59. 1 1
      plugin/ticket/plugin.php
  60. 3 1
      plugin/ticket/src/assign_tickets.php
  61. 152 0
      plugin/ticket/src/categories.php
  62. 52 0
      plugin/ticket/src/categories_add_user.php
  63. 1 3
      plugin/ticket/src/course_user_list.php
  64. 17 8
      plugin/ticket/src/myticket.php
  65. 72 72
      plugin/ticket/src/new_ticket.php
  66. 6 2
      plugin/ticket/src/report.php
  67. 389 60
      plugin/ticket/src/ticket.class.php
  68. 11 3
      plugin/ticket/src/ticket_assign_log.php
  69. 79 71
      plugin/ticket/src/ticket_details.php
  70. 18 6
      plugin/ticket/src/ticket_plugin.class.php
  71. 4 3
      plugin/ticket/src/tutor_report.lib.php
  72. 1 1
      plugin/ticket/start.php
  73. 1 1
      plugin/ticket/uninstall.php
  74. 51 0
      src/Chamilo/ContactBundle/Admin/CategoryAdmin.php
  75. 15 0
      src/Chamilo/ContactBundle/ChamiloContactBundle.php
  76. 93 0
      src/Chamilo/ContactBundle/Controller/ContactController.php
  77. 26 0
      src/Chamilo/ContactBundle/DependencyInjection/ChamiloContactExtension.php
  78. 81 0
      src/Chamilo/ContactBundle/Entity/Category.php
  79. 13 0
      src/Chamilo/ContactBundle/Entity/Contact.php
  80. 78 0
      src/Chamilo/ContactBundle/Form/Type/ContactType.php
  81. 9 0
      src/Chamilo/ContactBundle/Resources/config/admin.yml
  82. 3 0
      src/Chamilo/ContactBundle/Resources/config/routing.yml
  83. 11 0
      src/Chamilo/ContactBundle/Resources/views/contact.html.twig
  84. 12 0
      src/Chamilo/ContactBundle/Resources/views/index.html.twig
  85. 44 2
      src/Chamilo/CoreBundle/EventListener/LegacyLoginListener.php
  86. 1 1
      src/Chamilo/CoreBundle/Resources/config/routing.yml
  87. 1 1
      src/Chamilo/CoreBundle/Resources/views/layout_one_col.html.twig
  88. 42 32
      src/Chamilo/FaqBundle/Resources/views/Faq/index.html.twig
  89. 10 10
      src/Chamilo/ThemeBundle/Resources/views/Layout/footer.js.twig
  90. 9 9
      src/Chamilo/ThemeBundle/Resources/views/Layout/header.js.twig
  91. 1 1
      tests/scripts/fix_migrations_1.9.x_1.10.0.php
  92. 10 3
      user_portal.php
  93. 0 16
      web/.htaccess

+ 0 - 1
.gitignore

@@ -16,7 +16,6 @@
 app/home/*
 
 app/logs/*
-app/Resources/translations/*
 app/Resources/views/*
 
 # Upload content

+ 1 - 1
README.md

@@ -75,7 +75,7 @@ generate the missing files.
 
 On a Debian-based system, launch:
 ```
-sudo chown -R www-data:www-data app main/default_course_document/images main/lang  web
+sudo chown -R www-data:www-data app main/default_course_document/images main/lang web
 ```
 
 ### Start the installer

+ 1 - 0
app/AppKernel.php

@@ -111,6 +111,7 @@ class AppKernel extends Kernel
             new Chamilo\NotificationBundle\ChamiloNotificationBundle(),
             new Chamilo\AdminBundle\ChamiloAdminBundle(),
             new Chamilo\TimelineBundle\ChamiloTimelineBundle(),
+            new \Chamilo\ContactBundle\ChamiloContactBundle(),
 
             // Based in Sonata
             new Chamilo\ClassificationBundle\ChamiloClassificationBundle(),

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

@@ -5873,6 +5873,15 @@ a.sessionView {
     color: black !important;
 }
 
+.skill-badge-img{
+    width: 150px;
+    overflow: hidden;
+}
+
+.text-light{
+    color: #00CCFF;
+}
+
 
 #dropzone {
     position: relative;

+ 2 - 2
app/config/config.yml

@@ -12,7 +12,7 @@ imports:
     - { resource: mopa/mopa_bootstrap.yml }
 
     # FriendOfSymfony Bundles
-    #- { resource: fos/fos_comment.yml }r
+    #- { resource: fos/fos_comment.yml }
 #    - { resource: fos/fos_rest.yml }
     - { resource: fos/fos_user.yml }
     #- { resource: fos/fos_elastica.yml }
@@ -82,7 +82,7 @@ twig:
 
     form:
         resources:
-            - 'bootstrap_3_layout.html.twig'
+            - 'bootstrap_3_horizontal_layout.html.twig'
             - 'SonataFormatterBundle:Form:formatter.html.twig'
             - 'SonataMediaBundle:Form:media_widgets.html.twig'
             - 'SonataCoreBundle:Form:datepicker.html.twig'

+ 2 - 1
app/config/parameters.yml.dist

@@ -2,6 +2,7 @@
 # Set parameters here that may be different on each deployment target of the app, e.g. development, staging, production.
 # http://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
 parameters:
+    database_driver: pdo_mysql
     database_host: 127.0.0.1
     database_port: ~
     database_name: chamilomaster
@@ -39,7 +40,7 @@ parameters:
     # sonata_media.cdn.host: /chamilo_master/web/uploads/media
 
     sonata_page.varnish.command: 'if [ ! -r "/etc/varnish/secret" ]; then echo "VALID ERROR :/"; else varnishadm -S /etc/varnish/secret -T 127.0.0.1:6082 {{ COMMAND }} "{{ EXPRESSION }}"; fi;'
-    locales: [en, fr, es]
+    locales: [en, fr, es, de]
 
     # Aspell Settings
     # aspell_bin: '/usr/bin/hunspell'

+ 4 - 0
app/config/routing.yml

@@ -42,6 +42,10 @@ chamilo_course:
     resource: "@ChamiloCourseBundle/Resources/config/routing.yml"
     prefix:   /
 
+chamilo_contact:
+    resource: "@ChamiloContactBundle/Resources/config/routing.yml"
+    prefix:   /contact
+
 _courses:
     resource: routing_courses.yml
 

+ 1 - 0
app/config/sonata/sonata_admin.yml

@@ -74,6 +74,7 @@ sonata_admin:
                 items:
                     - sonata.page.admin.page
                     - sonata.page.admin.site
+                    - sonata.admin.contact_category
 
             sonata.admin.group.faq:
                 label: FAQ

+ 1 - 0
app/config/sonata/sonata_page.yml

@@ -44,6 +44,7 @@ sonata_page:
         - ^/faq
         - ^/courses/(.*)
         - ^/front/editor
+        - ^/contact(.*) # sonata admin
 
     ignore_route_patterns:
         - (.*)administration(.*)     # ignore admin route, ie route containing 'admin'  # sonata admin

+ 2 - 1
app/config/sonata/sonata_translation.yml

@@ -1,8 +1,9 @@
 lunetics_locale:
     guessing_order:
+        - session
         - query
         - router
-        - session
+
         #- browser
 
     allowed_locales: '%locales%'

+ 6 - 5
composer.json

@@ -114,22 +114,22 @@
         "sonata-project/seo-bundle": "~2.0",
         "sonata-project/doctrine-extensions": "~1@dev",
         "sonata-project/intl-bundle": "~2.2@dev",
-        "sonata-project/admin-bundle": "~2.4@dev",
-        "sonata-project/doctrine-orm-admin-bundle": "~2.4@dev",
+        "sonata-project/admin-bundle": "dev-master as 2.4",
+        "sonata-project/doctrine-orm-admin-bundle": "dev-master as 2.4",
         "sonata-project/notification-bundle": "~2.2@dev",
         "sonata-project/block-bundle": "~2.3@dev",
         "sonata-project/media-bundle": "~2.4@dev",
         "sonata-project/user-bundle": "~2.2@dev",
         "sonata-project/cache-bundle": "~2.1@dev",
         "sonata-project/cache": "~1.0@dev",
-        "sonata-project/page-bundle": "~2.4@dev",
+        "sonata-project/page-bundle": "dev-master",
         "sonata-project/core-bundle": "2.3.x-dev",
         "sonata-project/formatter-bundle": "~2.4@dev",
         "sonata-project/news-bundle": "~2.3@dev",
         "sonata-project/datagrid-bundle": "~2.2@dev",
         "sonata-project/exporter": "~1.3@dev",
         "sonata-project/timeline-bundle": "~2.3@dev",
-        "sonata-project/classification-bundle": "~2.2@dev",
+        "sonata-project/classification-bundle": "~2.3@dev",
         "sonata-project/comment-bundle": "~2.2@dev",
         "sonata-project/sonata-composer": "dev-master",
 
@@ -171,7 +171,8 @@
         "gregwar/captcha-bundle": "^2.0",
         "ornicar/gravatar-bundle": "^1.1",
         "a2lix/translation-form-bundle": "~2.0",
-        "knplabs/doctrine-behaviors": "~1.1"
+        "knplabs/doctrine-behaviors": "~1.1",
+	"jimmiw/php-time-ago": "^0.4.11"
     },
     "require-dev": {
         "behat/behat": "@stable",

+ 84 - 0
documentation/changelog.html

@@ -63,6 +63,90 @@
   </ul>
 <p>&nbsp;</p>
 
+   <a name="1.10.6"></a>
+
+    <h1>Chamilo 1.10.6 - ???, ?? of April 2016</h1>
+    <h3>Release notes - summary</h3>
+    <p>Chamilo 1.10.6 is a minor, bugfix release of the 1.10.x branch, with a few new features and bugfixes on top of 1.10.4.</p>
+    <h3>Release name</h3>
+    <p><a href="http://www.openstreetmap.org/node/1947201#map=14/51.3786/-2.3580">NNNh</a> .....</p>
+    <h3>Security fixes</h3>
+    <ul>
+        <li>None that we know of.</li>
+    </ul>
+    <h3>Possibly breaking changes</h3>
+    <ul>
+        <li>None that we know of.</li>
+    </ul>
+    <h3>Notable new Features</h3>
+    <h4>For end-users, teachers and Chamilo admins</h4>
+    <ul>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/10729dea98234918de3e7e8c8718503ba5e2efbd">10729dea</a> - <a href="https://task.beeznest.com/issues/10952">BT#10952</a>) Add multiple attachment in agenda</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/940fbdd51c97e6feecebbd156130ba8818d98ad8">940fbdd5</a> - <a href="https://task.beeznest.com/issues/10052">BT#10052</a>) Adding setting "exercise_enable_category_order"</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/6b51fae1a37d3c7b3f7c656e3631cb2eb50d028e">6b51fae1</a> - <a href="https://task.beeznest.com/issues/10052">BT#10052</a>) Add function needed for BT#10052</li>
+    </ul>
+    <h4>For developers and sysadmins</h4>
+    <ul>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/7b82a178a2059ca4784d66419ad8887d0745a5a0">7b82a178</a> - <a href="https://task.beeznest.com/issues/11005">BT#11005</a>) Save track_e_course_access before removing the session information</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/12c7e7eccbf933ccf29306dcb720972792a9f8f2">12c7e7ec</a> - <a href="https://support.chamilo.org/issues/8175">#8175</a>) Use custom CKEditor</li>
+    </ul>
+
+    <h3>Improvements (minor features) and debug</h3>
+    <ul>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/37d5af337e3ea895f6b5b7dec55e9dc4dbecfe2e">37d5af33</a>) Fix forms when adding/editing lp items</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/36635d142199d3c8e6a5adbfd1b531dbbe85d318">36635d14</a>) Fix URL for create forum in lp</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/1f0c917fc949132afbc50f070dd2bb3ef25f18a6">1f0c917f</a>) Fix report titles.</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/90da1acf4666c7ec7e295d1d739cc0eb207cb68b">90da1acf</a>) Use api_get_course_info</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/ece4c9d8f227f8fcf39bc4a2c995af8eb35c9d3b">ece4c9d8</a>) If in a session show all LPs not only LP added in session.</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/5bcac3efc308d4c2dc4a6708f59b16bbc02c4206">5bcac3ef</a>) Fix constructor</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/33dafb19fa049fa9b70263d4daf9fc0b8ab6292a">33dafb19</a> - <a href="https://task.beeznest.com/issues/11062">BT#11062</a>) Change scrollbar to scrollbar-light</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/405b2c7bdf7918ef5a7c12f56a7b305eb74f3dd1">405b2c7b</a>) Add SMTP debug information</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/10f30d19c69e5880cdca9d7adc19c1ccadb766ec">10f30d19</a>) Fix link</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/5e5859107b0d6d247626bce7f6d1e308dc009bfc">5e585910</a>) Fix social group doesnt show in group list when adding an image (1.10.x)</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/f34bae386d81c613c11411c921d81e7d9656cfd0">f34bae38</a> - <a href="https://support.chamilo.org/issues/8188">#8188</a>) Fix menu tabs (broken in 1.10.4)</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/88b3acf7002421f9e7c60b1619d507bfd15c9d9d">88b3acf7</a> - <a href="https://support.chamilo.org/issues/8185">#8185</a>) Update bower assets</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/cede17daf3925bfe56b108476dfb33140023d164">cede17da</a>) Use 1 setting instead of 2</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/7f42c3251a15e3b91bc114fdd3db5ec8cb6dc04f">7f42c325</a>) Use __construct</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/997288805472841bbfc3bf3caedf996f1db0acba">99728880</a> - <a href="https://support.chamilo.org/issues/8157">#8157</a>) Replace mysql with pdo_mysql extension</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/c002d1e8f752b14e111e43ee65926eba37e558f7">c002d1e8</a>) Fix fatal error.</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/64f6ccf359425a726f4be99df86c371ad59e9c16">64f6ccf3</a> - <a href="https://task.beeznest.com/issues/11021">BT#11021</a>) Remove button for download video file in lp</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/380db06ab13e4524247999e92cb4065aeaf12ef1">380db06a</a> - <a href="https://task.beeznest.com/issues/11021">BT#11021</a>) Fix template for jplayer viewer</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/ecf16fb37b0e14bd54ec624bf6055925f8d26366">ecf16fb3</a> - <a href="https://task.beeznest.com/issues/11021">BT#11021</a>) Show lp video items in iframe</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/615a1224e97b16a510a65b1f9fe389f74aec011c">615a1224</a> - <a href="https://task.beeznest.com/issues/10936">BT#10936</a>) Set user id from function</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/a231628dc85d72a6728c6b2a3360d8c94011817e">a231628d</a> - <a href="https://task.beeznest.com/issues/10936">BT#10936</a>) Fix fatal error</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/db12470781fa479ed7c25d0a61af62eda07cb2af">db124707</a> - <a href="https://task.beeznest.com/issues/10936">BT#10936</a>) Fix fatal error</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/8f864966c9bc725047b09270e21db891399fb1ab">8f864966</a> - <a href="https://support.chamilo.org/issues/8173">#8173</a>) Fix Renaming a document in upload document's form</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/8f652341004346d57558183aa9f9134a97a58339">8f652341</a>) Fix select bootstrap overflow visible</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/cec0161eff56dd0a81c778bfe24a2e7d618f1691">cec0161e</a> - <a href="https://support.chamilo.org/issues/8166">#8166</a>) Fix create course when doesn't exists an admin with id 1</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/55c17b801c573e1f65fd036efaf1cd114040d3db">55c17b80</a> - <a href="https://task.beeznest.com/issues/10997">BT#10997</a>) Fix Migrations</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/a1f5d466b7baec90de31b31a40a09f606e728404">a1f5d466</a> - <a href="https://support.chamilo.org/issues/8024">#8024</a>) Add clock-exercise.png</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/d46b5c15141020c681d26fb46bd9242a6e5e12a1">d46b5c15</a> - <a href="https://support.chamilo.org/issues/8024">#8024</a>) Fixed css clock CT#8024</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/4292a69177fb993561ad07aadce77fedfe0b4244">4292a691</a> - <a href="https://support.chamilo.org/issues/8154">#8154</a>) Fix attached files in mail</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/6c56132f69b3124ed36d6a0f577b95bed6888580">6c56132f</a> - <a href="https://task.beeznest.com/issues/10997">BT#10997</a>) Remove condition hiding the user ID when teacher cannot subscribe students to course, because it causes the user_id data to be nullified, which prevents links to work properly</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/bdb5a042a50d40c40b8775b241deb5b97b998d57">bdb5a042</a> - <a href="https://task.beeznest.com/issues/10979">BT#10979</a>) Fix and remove E_NOTICE when search_enabled is true</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/798b6f52089b52092cd597b76eef0ec9e3a41c16">798b6f52</a> - <a href="https://support.chamilo.org/issues/8135">#8135</a>) Fix previous/next buttons in calendar</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/28117fcfc2e04dcaa2528e589a45fcd72787db1f">28117fcf</a>) Fix issue with foreign keys when removing URLs and avoid a notice</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/767d99cf540650ac8b43690986690f6c0964bb15">767d99cf</a> - <a href="https://task.beeznest.com/issues/10927">BT#10927</a>) Add from and too</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/44706ef925530c642933112adc084cb313e05148">44706ef9</a> - <a href="https://support.chamilo.org/issues/8140">#8140</a>) Remove user from all portals</li>
+        <li>(<a href="https://github.com/chamilo/chamilo-lms/commit/9f4f4a53ded2d67fd9416a8d10137277bcd5fefa">9f4f4a53</a> - <a href="https://task.beeznest.com/issues/10979">BT#10979</a>) Fix E_NOTICEs when search_enabled is true</li>
+    </ul>
+    <h3>Stylesheets and theming</h3>
+    <ul>
+        <li>None that we know of.</li>
+    </ul>
+    <h3>Web services</h3>
+    <ul>
+        <li>None that we know of.</li>
+    </ul>
+    <h3>Removals</h3>
+    <ul>
+        <li>None that we know of.</li>
+    </ul>
+    <h3>Known issues</h3>
+    <ul>
+        <li>IMS/QTI import/export has issues</li>
+        <li>In Internet Explorer 9, learning paths might not show other pages than the first to be opened. We lacked computers with IE9 at the time of release of 1.10.4. Guaranteeing IE9 support will likely require additional services. See <a href="https://support.chamilo.org/issues/8075">the corresponding bug report</a></li>
+    </ul>
 
     <a name="1.10.4"></a>
 

+ 11 - 0
main/badge/issued.php

@@ -61,6 +61,17 @@ $skillInfo = [
     'courses' => []
 ];
 
+
+
+// Open Graph Markup
+$htmlHeadXtra[] = "
+    <meta property='og:type' content='article' />
+    <meta property='og:title' content='".sprintf(get_lang('IHaveObtainedSkillXOnY'), $skillInfo['name'], api_get_setting('siteName'))."' />
+    <meta property='og:url' content='".api_get_path(WEB_PATH)."badge/".$skillId."/user/".$userId."' />
+    <meta property='og:description' content='".$skillInfo['description']."' />
+    <meta property='og:image' content='".$skillInfo['badge_image']."' />
+";
+
 $badgeAssertions = [];
 
 foreach ($userSkills as $userSkill) {

+ 17 - 4
main/course_info/infocours.php

@@ -177,6 +177,13 @@ $form->addElement(
 );
 $form->addElement('select_language', 'course_language', array(get_lang('Ln'), get_lang('TipLang')));
 
+$group = array(
+    $form->createElement('radio', 'show_course_in_user_language', null, get_lang('Yes'), 1),
+    $form->createElement('radio', 'show_course_in_user_language', null, get_lang('No'), 2),
+);
+
+$form->addGroup($group, '', array(get_lang("ShowCourseInUserLanguage")), '');
+
 $form->addText('department_name', get_lang('Department'), false);
 $form->applyFilter('department_name', 'html_filter');
 $form->applyFilter('department_name', 'trim');
@@ -203,8 +210,12 @@ $form->addHtml('
 ');
 $form->addHidden('cropResult', '');
 $allowed_picture_types = array ('jpg', 'jpeg', 'png', 'gif');
-$form->addRule('picture', get_lang('OnlyImagesAllowed').' ('.implode(',', $allowed_picture_types).')', 'filetype', $allowed_picture_types);
-//$form->addElement('html', '<div class="form-group "><div class="col-md-2"></div> <div class="col-md-8 help-image">'.get_lang('UniqueAnswerImagePreferredSize200x150').'</div></div>');
+$form->addRule(
+    'picture',
+    get_lang('OnlyImagesAllowed').' ('.implode(',', $allowed_picture_types).')',
+    'filetype',
+    $allowed_picture_types
+);
 $form->addElement('checkbox', 'delete_picture', null, get_lang('DeletePicture'));
 
 if (api_get_setting('document.pdf_export_watermark_by_course') == 'true') {
@@ -450,12 +461,14 @@ $form->addButtonSave(get_lang('SaveSettings'), 'submit_save');
 $form->addElement('html', '</div></div>');
 
 // Document settings
-$form->addElement('html', '<div><h3>'.Display::return_icon('folder.png', Security::remove_XSS(get_lang('Documents')),'',ICON_SIZE_SMALL).' '.Security::remove_XSS(get_lang('Documents')).'</h3><div>');
+$form->addElement(
+    'html',
+    '<div><h3>'.Display::return_icon('folder.png', Security::remove_XSS(get_lang('Documents')),'',ICON_SIZE_SMALL).' '.Security::remove_XSS(get_lang('Documents')).'</h3><div>'
+);
 
 $group = array(
     $form->createElement('radio', 'show_system_folders', null, get_lang('Yes'), 1),
     $form->createElement('radio', 'show_system_folders', null, get_lang('No'), 2),
-
 );
 $form->addGroup($group, '', array(get_lang("ShowSystemFolders")), '');
 $form->addButtonSave(get_lang('SaveSettings'), 'submit_save');

+ 1 - 1
main/document/document.php

@@ -1794,7 +1794,7 @@ if (isset($documentAndFolders) && is_array($documentAndFolders)) {
 
             // Last edit date
             $last_edit_date = api_get_local_time($document_data['lastedit_date']);
-            $display_date = date_to_str_ago($last_edit_date).
+            $display_date = date_to_str_ago($document_data['lastedit_date']).
                 ' <div class="muted"><small>'.$last_edit_date."</small></div>";
 
             $row[] = $invisibility_span_open.$display_date.$invisibility_span_close;

+ 2 - 2
main/document/edit_document.php

@@ -469,8 +469,8 @@ if ($owner_id == api_get_user_id() ||
 
 	if (!$group_document && !DocumentManager::is_my_shared_folder(api_get_user_id(), $currentDirPath, $sessionId)) {
 		// Updated on field
-		$last_edit_date = api_get_local_time($last_edit_date);
-        $display_date = date_to_str_ago($last_edit_date).' <span class="dropbox_date">'.api_format_date($last_edit_date).'</span>';
+        $display_date = date_to_str_ago($last_edit_date).
+			' <span class="dropbox_date">'.api_format_date(api_get_local_time($last_edit_date)).'</span>';
 		$form->addElement('static', null, get_lang('UpdatedOn'), $display_date);
 	}
 

+ 3 - 2
main/dropbox/index.php

@@ -383,7 +383,7 @@ if ($action != 'add') {
 				$dropbox_file_data[] = $dropbox_file->author;
 
 				$last_upload_date = api_get_local_time($dropbox_file->last_upload_date);
-				$dropbox_file_data[] = date_to_str_ago($last_upload_date).'<br /><span class="dropbox_date">'.
+				$dropbox_file_data[] = date_to_str_ago($dropbox_file->last_upload_date).'<br /><span class="dropbox_date">'.
                     api_format_date($last_upload_date).'</span>';
 
 				$action_icons = check_number_feedback($dropbox_file->id, $number_feedback).' '.get_lang('Feedback').'
@@ -555,7 +555,8 @@ if ($action != 'add') {
 				$receivers_celldata = trim(trim($receivers_celldata), ','); // Removing the trailing comma.
 				$dropbox_file_data[] = $receivers_celldata;
 				$last_upload_date = api_get_local_time($dropbox_file->last_upload_date);
-				$dropbox_file_data[] = date_to_str_ago($last_upload_date).'<br /><span class="dropbox_date">'.api_format_date($last_upload_date).'</span>';
+				$dropbox_file_data[] = date_to_str_ago($dropbox_file->last_upload_date).'<br /><span class="dropbox_date">'.
+					api_format_date($last_upload_date).'</span>';
 
 				//$dropbox_file_data[] = $dropbox_file->author;
 				$receivers_celldata = '';

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

@@ -103,6 +103,7 @@ class Exercise
 
         $id  = intval($id);
         if (empty($this->course_id)) {
+
             return false;
         }
         $sql = "SELECT * FROM $TBL_EXERCISES WHERE c_id = ".$this->course_id." AND id = ".$id;
@@ -5799,7 +5800,7 @@ class Exercise
      */
     private function setMediaList($questionList)
     {
-        $mediaList= array();
+        $mediaList = array();
         if (!empty($questionList)) {
             foreach ($questionList as $questionId) {
                 $objQuestionTmp = Question::read($questionId, $this->course_id);

+ 1 - 1
main/gradebook/lib/be/category.class.php

@@ -2054,7 +2054,7 @@ class Category implements GradebookItem
         $userHasSkills = false;
 
         if ($skillToolEnabled) {
-            if (!$category->getGenerateCertificates()) {
+            if ($category->getGenerateCertificates()) {
                 $skill = new Skill();
                 $skill->add_skill_to_user(
                     $user_id,

+ 1 - 3
main/inc/ajax/course_home.ajax.php

@@ -51,9 +51,7 @@ switch ($action) {
             if ($_GET["id"] == strval(intval($_GET["id"]))) {
                 $sql = "UPDATE $tool_table SET
                         visibility = $requested_visible
-                        WHERE c_id = $course_id AND iid ='".intval(
-                        $_GET['id']
-                    )."'";
+                        WHERE c_id = $course_id AND iid ='".intval($_GET['id'])."'";
                 Database::query($sql);
             }
             $response_data = array(

+ 18 - 0
main/inc/ajax/user_manager.ajax.php

@@ -7,6 +7,24 @@
 $action = $_GET['a'];
 
 switch ($action) {
+    case 'get_user_like':
+
+        $query = $_REQUEST['q'];
+        $conditions = [
+            'username' => $query,
+            'firstname' => $query,
+            'lastname' => $query,
+        ];
+        $users = UserManager::get_user_list_like($conditions, [], false, 'OR');
+        $result = [];
+        if (!empty($users)) {
+            foreach ($users as $user) {
+                $result[] = ['id' => $user['id'], 'text' => $user['complete_name'].' ('.$user['username'].')'];
+            }
+            $result['items'] = $result;
+        }
+        echo json_encode($result);
+        break;
     case 'get_user_popup':
         $user_info = api_get_user_info($_REQUEST['user_id']);
         $ajax_url = api_get_path(WEB_AJAX_PATH).'message.ajax.php';

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

@@ -533,7 +533,8 @@ class AddCourse
                 'default' => api_get_setting('course.allow_public_certificates') === 'true' ? 1 : '',
                 'category' =>'certificates'
             ],
-            'documents_default_visibility' => ['default' =>'visible', 'category' =>'document']
+            'documents_default_visibility' => ['default' =>'visible', 'category' =>'document'],
+            'show_course_in_user_language' => ['default' => 2],
         ];
 
         /*$counter = 1;

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

@@ -101,6 +101,7 @@ define('SURVEY_VISIBLE_PUBLIC', 2);
 // CONSTANTS defining all tools, using the english version
 /* When you add a new tool you must add it into function api_get_tools_lists() too */
 define('TOOL_DOCUMENT', 'document');
+define('TOOL_LP_FINAL_ITEM', 'final_item');
 define('TOOL_THUMBNAIL', 'thumbnail');
 define('TOOL_HOTPOTATOES', 'hotpotatoes');
 define('TOOL_CALENDAR_EVENT', 'calendar_event');
@@ -277,7 +278,6 @@ define('REPEATED_SLASHES_PURIFIER', '/\/{2,}/');                    // $path = p
 define('VALID_WEB_PATH', '/https?:\/\/[^\/]*(\/.*)?/i');            // $is_valid_path = preg_match(VALID_WEB_PATH, $path);
 define('VALID_WEB_SERVER_BASE', '/https?:\/\/[^\/]*/i');            // $new_path = preg_replace(VALID_WEB_SERVER_BASE, $new_base, $path);
 
-// Constants for api_get_path() and api_get_path_type(), etc. - registered path types.
 // Constants for api_get_path() and api_get_path_type(), etc. - registered path types.
 // basic (leaf elements)
 define('REL_CODE_PATH', 'REL_CODE_PATH');

+ 20 - 4
main/inc/lib/course.lib.php

@@ -3667,6 +3667,7 @@ class CourseManager
         $result = Database::query($sql);
         $html = null;
         $courseCount = 0;
+        $items = [];
         while ($row = Database::fetch_array($result)) {
             // We simply display the title of the category.
             $params = array(
@@ -3683,10 +3684,14 @@ class CourseManager
                 $load_dirs
             );
 
-            $html .= self::course_item_parent(
+            $item = self::course_item_parent(
                 self::course_item_html($params, true),
                 $courseInCategory['html']
             );
+
+            $html .= $item;
+
+            $items[] = $item;
             $courseCount += $courseInCategory['course_count'];
         }
 
@@ -3694,10 +3699,16 @@ class CourseManager
         $courseInCategory = self::displayCoursesInCategory(0, $load_dirs);
 
         $html .= $courseInCategory['html'];
+
+        if (!empty($courseInCategory['items'])) {
+            $items = array_merge($items, $courseInCategory['items']);
+        }
+
         $courseCount += $courseInCategory['course_count'];
 
         return [
             'html' => $html,
+            'items' => $items,
             'course_count' => $courseCount
         ];
     }
@@ -3756,6 +3767,7 @@ class CourseManager
 
         $result = Database::query($sql);
         $html = '';
+        $items = [];
 
         $course_list = array();
         $showCustomIcon = api_get_setting('course.course_images_in_courses_list');
@@ -3880,11 +3892,14 @@ class CourseManager
             if (empty($user_category_id)) {
                 $isSubContent = false;
             }
-            $html .= self::course_item_html($params, $isSubContent);
+            $item = self::course_item_html($params, $isSubContent);
+            $html .= $item;
+            $items[] = $item;
         }
 
         return [
             'html' => $html,
+            'items' => $items,
             'course_count' => $courseCount
         ];
     }
@@ -5172,7 +5187,8 @@ class CourseManager
             'pdf_export_watermark_text',
             'show_system_folders',
             'exercise_invisible_in_session',
-            'enable_forum_auto_launch'
+            'enable_forum_auto_launch',
+            'show_course_in_user_language'
         );
 
         $allowLPReturnLink = api_get_setting('course.allow_lp_return_link');
@@ -5582,7 +5598,7 @@ class CourseManager
             $result[$content['value']] = $content['content'];
         }
 
-        $form->addElement('advmultiselect', 'users', get_lang('Users'), $result);
+        return $form->addElement('advmultiselect', 'users', get_lang('Users'), $result);
     }
 
     /**

+ 6 - 6
main/inc/lib/database.lib.php

@@ -647,15 +647,15 @@ class Database
         $path = !empty($path) ? $path : api_get_path(SYS_PATH);
 
         $paths = array(
-            $path.'src/Chamilo/ClassificationBundle/Entity',
-            $path.'src/Chamilo/MediaBundle/Entity',
-            $path.'src/Chamilo/PageBundle/Entity',
+            //$path.'src/Chamilo/ClassificationBundle/Entity',
+            //$path.'src/Chamilo/MediaBundle/Entity',
+            //$path.'src/Chamilo/PageBundle/Entity',
             $path.'src/Chamilo/CoreBundle/Entity',
             $path.'src/Chamilo/UserBundle/Entity',
             $path.'src/Chamilo/CourseBundle/Entity',
-            $path.'vendor/sonata-project/user-bundle/Entity',
-            $path.'vendor/sonata-project/user-bundle/Model',
-            $path.'vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Entity',
+            //$path.'vendor/sonata-project/user-bundle/Entity',
+            //$path.'vendor/sonata-project/user-bundle/Model',
+            //$path.'vendor/friendsofsymfony/user-bundle/FOS/UserBundle/Entity',
         );
 
         $proxyDir = $path.'app/cache/';

+ 0 - 181
main/inc/lib/formvalidator/Element/DatePicker.php

@@ -1,181 +0,0 @@
-<?php
-/* For licensing terms, see /license.txt */
-
-/**
- * Form element to select a date.
- *
- * Class DatePicker
- */
-class DatePicker extends HTML_QuickForm_text
-{
-    /**
-     * @param string $elementName
-     * @param string $elementLabel
-     * @param array  $attributes
-     */
-    public function __construct($elementName = null, $elementLabel = null, $attributes = null)
-    {
-        if (!isset($attributes['id'])) {
-            $attributes['id'] = $elementName;
-        }
-        $attributes['class'] = 'form-control';
-
-        parent::__construct($elementName, $elementLabel, $attributes);
-        $this->_appendName = true;
-        $this->_type = 'date_picker';
-    }
-
-    /**
-    * HTML code to display this datepicker
-     *
-     * @return string
-    */
-    public function toHtml()
-    {
-        if ($this->_flagFrozen) {
-            return $this->getFrozenHtml();
-        }
-
-        $id = $this->getAttribute('id');
-        $value = $this->getValue();
-        $label = $this->getLabel();
-
-        if (!empty($value)) {
-            $value = api_format_date($value, DATE_TIME_FORMAT_LONG_24H);
-        }
-
-        return $this->getElementJS() . '
-                    <input ' . $this->_getAttrString($this->_attributes) . '>
-        ';
-    }
-
-    /**
-     * @param string $value
-     */
-    public function setValue($value)
-    {
-        $value = substr($value, 0, 16);
-        $this->updateAttributes(
-            array(
-                'value' => $value
-            )
-        );
-    }
-
-    /**
-     * Get the necessary javascript for this datepicker
-     * @return string
-     */
-    private function getElementJS()
-    {
-        $js = null;
-        $id = $this->getAttribute('id');
-
-        $js .= "<script>
-            $(function() {
-                /*$('#$id').hide().datepicker({
-                    defaultDate: '" . $this->getValue() . "',
-                    dateFormat: 'yy-mm-dd',
-                    altField: '#{$id}_alt',
-                    altFormat: \"" . get_lang('DateFormatLongNoDayJS') . "\",
-                    showOn: 'both',
-                    buttonImage: '" . Display::return_icon('attendance.png', null, [], ICON_SIZE_TINY, true, true) . "',
-                    buttonImageOnly: true,
-                    buttonText: '" . get_lang('SelectDate') . "',
-                    changeMonth: true,
-                    changeYear: true,
-                    yearRange: 'c-60y:c+5y'
-                });*/
-
-                 $('#$id').datetimepicker({
-                    defaultDate: '".$this->getValue()."',
-                    format: 'YYYY-MM-DD'
-                });
-
-            });
-        </script>";
-
-        return $js;
-    }
-
-    /**
-     * @param string $layout
-     *
-     * @return string
-     */
-    public function getTemplate($layout)
-    {
-        $size = $this->getColumnsSize();
-        $id = $this->getAttribute('id');
-        $value = $this->getValue();
-
-        if (empty($size)) {
-            $sizeTemp = $this->getInputSize();
-            if (empty($size)) {
-                $sizeTemp = 8;
-            }
-            $size = array(2, $sizeTemp, 2);
-        } else {
-            if (is_array($size)) {
-                if (count($size) != 3) {
-                    $sizeTemp = $this->getInputSize();
-                    if (empty($size)) {
-                        $sizeTemp = 8;
-                    }
-                    $size = array(2, $sizeTemp, 2);
-                }
-                // else just keep the $size array as received
-            } else {
-                $size = array(2, intval($size), 2);
-            }
-        }
-
-        if (!empty($value)) {
-            $value = api_format_date($value, DATE_FORMAT_LONG_NO_DAY);
-        }
-
-        switch ($layout) {
-            case FormValidator::LAYOUT_INLINE:
-                return '
-                <div class="form-group {error_class}">
-                    <label {label-for} >
-                        <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
-                        {label}
-                    </label>
-
-                    {element}
-                </div>';
-                break;
-            case FormValidator::LAYOUT_HORIZONTAL:
-                return '
-                <div class="form-group {error_class}">
-                    <label {label-for} class="col-sm-'.$size[0].' control-label" >
-                        <!-- BEGIN required --><span class="form_required">*</span><!-- END required -->
-                        {label}
-                    </label>
-                    <div class="col-sm-'.$size[1].'">
-                        {icon}
-
-                        {element}
-
-                        <!-- BEGIN label_2 -->
-                            <p class="help-block">{label_2}</p>
-                        <!-- END label_2 -->
-
-                        <!-- BEGIN error -->
-                            <span class="help-inline">{error}</span>
-                        <!-- END error -->
-                    </div>
-                    <div class="col-sm-'.$size[2].'">
-                        <!-- BEGIN label_3 -->
-                            {label_3}
-                        <!-- END label_3 -->
-                    </div>
-                </div>';
-                break;
-            case FormValidator::LAYOUT_BOX_NO_LABEL:
-                return '{element}';
-                break;
-        }
-    }
-}

+ 3 - 117
main/inc/lib/internationalization.lib.php

@@ -501,125 +501,11 @@ function api_format_date($time, $format = null, $language = null)
  * @author Julio Montoya
  */
 
-function date_to_str_ago($date)
+function date_to_str_ago($date, $timeZone = 'UTC')
 {
-    static $initialized = false;
-    static $today, $yesterday;
-    static $min_decade, $min_year, $min_month, $min_week, $min_day, $min_hour, $min_minute;
-    static $min_decades, $min_years, $min_months, $min_weeks, $min_days, $min_hours, $min_minutes;
-    static $sec_time_time, $sec_time_sing, $sec_time_plu;
+    $timeAgo = new TimeAgo($timeZone, api_get_language_isocode());
 
-    $system_timezone = date_default_timezone_get();
-    date_default_timezone_set(_api_get_timezone());
-
-    if (!$initialized) {
-        $today = get_lang('Today');
-        $yesterday = get_lang('Yesterday');
-
-        $min_decade = get_lang('MinDecade');
-        $min_year = get_lang('MinYear');
-        $min_month = get_lang('MinMonth');
-        $min_week = get_lang('MinWeek');
-        $min_day = get_lang('MinDay');
-        $min_hour = get_lang('MinHour');
-        $min_minute = get_lang('MinMinute');
-
-        $min_decades = get_lang('MinDecades');
-        $min_years = get_lang('MinYears');
-        $min_months = get_lang('MinMonths');
-        $min_weeks = get_lang('MinWeeks');
-        $min_days = get_lang('MinDays');
-        $min_hours = get_lang('MinHours');
-        $min_minutes = get_lang('MinMinutes');
-
-        // original 1
-        //$sec_time=array('century'=>3.1556926*pow(10,9),'decade'=>315569260,'year'=>31556926,'month'=>2629743.83,'week'=>604800,'day'=>86400,'hour'=>3600,'minute'=>60,'second'=>1);
-        //$sec_time=array(get_lang('MinDecade')=>315569260,get_lang('MinYear')=>31556926,get_lang('MinMonth')=>2629743.83,get_lang('MinWeek')=>604800,get_lang('MinDay')=>86400,get_lang('MinHour')=>3600,get_lang('MinMinute')=>60);
-        $sec_time_time = array(315569260, 31556926, 2629743.83, 604800, 86400, 3600, 60);
-        $sec_time_sing = array($min_decade, $min_year, $min_month, $min_week, $min_day, $min_hour, $min_minute);
-        $sec_time_plu = array($min_decades, $min_years, $min_months, $min_weeks, $min_days, $min_hours, $min_minutes);
-        $initialized = true;
-    }
-
-    $dst_date = is_string($date) ? strtotime($date) : $date;
-    // For avoiding calling date() several times
-    $date_array = date('s/i/G/j/n/Y', $dst_date);
-    $date_split = explode('/', $date_array);
-
-    $dst_s = $date_split[0];
-    $dst_m = $date_split[1];
-    $dst_h = $date_split[2];
-    $dst_day = $date_split[3];
-    $dst_mth = $date_split[4];
-    $dst_yr = $date_split[5];
-
-    $dst_date = mktime($dst_h, $dst_m, $dst_s, $dst_mth, $dst_day, $dst_yr);
-    $time = $offset = time() - $dst_date; // Seconds between current days and today.
-
-    // Here start the functions sec_to_str()
-    $act_day = date('d');
-    $act_mth = date('n');
-    $act_yr = date('Y');
-
-    if ($dst_day == $act_day && $dst_mth == $act_mth && $dst_yr == $act_yr) {
-        return $today;
-    }
-
-    if ($dst_day == $act_day - 1 && $dst_mth == $act_mth && $dst_yr == $act_yr) {
-        return $yesterday;
-    }
-
-    $str_result = array();
-    $time_result = array();
-    $key_result = array();
-
-    $str = '';
-    $i = 0;
-    for ($i = 0; $i < count($sec_time_time); $i++) {
-        $seconds = $sec_time_time[$i];
-        if ($seconds > $time) {
-            continue;
-        }
-        $current_value = intval($time/$seconds);
-
-        if ($current_value != 1) {
-            $date_str = $sec_time_plu[$i];
-        } else {
-            $date_str = $sec_time_sing[$i];
-
-        }
-        $key_result[] = $sec_time_sing[$i];
-
-        $str_result[] = $current_value.' '.$date_str;
-        $time_result[] = $current_value;
-        $str .= $current_value.$date_str;
-        $time %= $seconds;
-    }
-
-    if (!empty($key_result)) {
-        if ($key_result[0] == $min_day && $key_result[1]== $min_minute) {
-            $key_result[1] = ' 0 '.$min_hours;
-            $str_result[0] = $time_result[0].' '.$key_result[0];
-            $str_result[1] = $key_result[1];
-        }
-
-        if ($key_result[0] == $min_year && ($key_result[1] == $min_day || $key_result[1] == $min_week)) {
-            $key_result[1] = ' 0 '.$min_months;
-            $str_result[0] = $time_result[0].' '.$key_result[0];
-            $str_result[1] = $key_result[1];
-        }
-    }
-
-    if (!empty($str_result)) {
-        if (!empty($str_result[1])) {
-            $str = $str_result[0].', '.$str_result[1];
-        } else {
-            $str = $str_result[0];
-        }
-    }
-
-    date_default_timezone_set($system_timezone);
-    return $str;
+    return $timeAgo->inWords($date);
 }
 
 /**

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

@@ -1218,7 +1218,8 @@ class MessageManager
                     if (!empty($topic['update_date']) &&
                         $topic['update_date'] != '0000-00-00 00:00:00'
                     ) {
-                        $date .= '<div class="message-group-date" > <i>'.get_lang('LastUpdate').' '.date_to_str_ago($topic['update_date']).'</i></div>';
+                        $date .= '<div class="message-group-date" >
+                            <i>'.get_lang('LastUpdate').' '.date_to_str_ago($topic['update_date']).'</i></div>';
                     }
                 } else {
                     $date .= '<div class="message-group-date"> <i>'.get_lang('Created').' '.date_to_str_ago($topic['send_date']).'</i></div>';

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

@@ -274,7 +274,7 @@ class NotebookManager
 
             $updateValue = '';
             if ($row['update_date'] <> $row['creation_date']) {
-                $updateValue = ', ' . get_lang('UpdateDate') . ': ' . date_to_str_ago($update_date) . '&nbsp;&nbsp;<span class="dropbox_date">' . $update_date . '</span>';
+                $updateValue = ', ' . get_lang('UpdateDate') . ': ' . date_to_str_ago($row['update_date']) . '&nbsp;&nbsp;<span class="dropbox_date">' . $update_date . '</span>';
             }
 
             $actions = '<a href="' . api_get_self() . '?action=editnote&notebook_id=' . $row['notebook_id'] . '">' .
@@ -285,7 +285,7 @@ class NotebookManager
             echo Display::panel(
                 $row['description'],
                 $row['title'] . $session_img.' <div class="pull-right">'.$actions.'</div>',
-                get_lang('CreationDate') . ': ' . date_to_str_ago($creation_date) . '&nbsp;&nbsp;<span class="dropbox_date">' . $creation_date . $updateValue."</span>"
+                get_lang('CreationDate') . ': ' . date_to_str_ago($row['creation_date']) . '&nbsp;&nbsp;<span class="dropbox_date">' . $creation_date . $updateValue."</span>"
             );
         }
     }

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

@@ -325,6 +325,7 @@ class Plugin
 
             //1. Loading english if exists
             $english_path = $root.$plugin_name."/lang/english.php";
+
             if (is_readable($english_path)) {
                 $strings = array();
                 include $english_path;
@@ -332,6 +333,7 @@ class Plugin
             }
 
             $path = $root.$plugin_name."/lang/$language_interface.php";
+
             //2. Loading the system language
             if (is_readable($path)) {
                 include $path;

+ 0 - 327
main/inc/lib/ppt2png/DocumentConverter.java.orig

@@ -1,327 +0,0 @@
-import java.awt.Event;
-
-//import sun.text.Normalizer;
-
-import com.enterprisedt.net.ftp.FTPClient;
-import com.enterprisedt.net.ftp.FTPConnectMode;
-import com.enterprisedt.net.ftp.FTPTransferType;
-import com.sun.star.beans.PropertyValue;
-import com.sun.star.beans.XPropertySet;
-import com.sun.star.bridge.XBridge;
-import com.sun.star.bridge.XBridgeFactory;
-import com.sun.star.connection.NoConnectException;
-import com.sun.star.connection.XConnection;
-import com.sun.star.connection.XConnector;
-import com.sun.star.container.XNamed;
-import com.sun.star.document.XExporter;
-import com.sun.star.document.XFilter;
-import com.sun.star.drawing.XDrawPage;
-import com.sun.star.drawing.XDrawPages;
-import com.sun.star.drawing.XDrawPagesSupplier;
-import com.sun.star.frame.XComponentLoader;
-import com.sun.star.lang.XComponent;
-import com.sun.star.lang.XMultiComponentFactory;
-import com.sun.star.uno.UnoRuntime;
-import com.sun.star.uno.XComponentContext;
-
-/**
- * The class <CODE>DocumentConverter</CODE> allows you to convert all
- * documents in a given directory and in its subdirectories to a given type. A
- * converted document will be created in the same directory as the origin
- * document.
- * 
- */
-public class DocumentConverter {
-	/**
-	 * Containing the loaded documents
-	 */
-	static XComponentLoader xcomponentloader = null;
- 
-	/**
-	 * Connecting to the office with the component UnoUrlResolver and calling
-	 * the static method traverse
-	 * 
-	 * @param args
-	 *            The array of the type String contains the directory, in which
-	 *            all files should be converted, the favoured converting type
-	 *            and the wanted extension
-	 */
-	public static void main(String args[]) {
-		
-		String cnx, ftpuser, host, port, url, ftpPasswd, destinationFolder, remoteFolderFullPath, remoteFolder;
-		int width, height;
-		
-		try {			
-			host = args[0];
-			port = args[1];
-			url = args[2];
-			destinationFolder = args[3];
-			width = Integer.parseInt(args[4]);
-			height = Integer.parseInt(args[5]);
-			if(args.length == 8){
-				ftpuser = args[6];
-				ftpPasswd = args[7];
-			}
-			else{
-				ftpuser = "";
-				ftpPasswd = "";
-			}
-			
-			
-			if(host.equals("localhost")){
-				String prefix = "file://";
-				if(url.charAt(0)!='/')
-					prefix += '/';
-				url = prefix+url;
-				remoteFolder = destinationFolder; 
-				remoteFolderFullPath = prefix;
-			}
-			else {
-				remoteFolderFullPath = "file:///home/"+ftpuser+"/";					
-				remoteFolder = url.replace('/','_');
-				remoteFolder = removeAccents(remoteFolder);
-			}			
-			
-			cnx = "socket,host="+host+",port="+port;
-		
-			XComponentContext xComponentContext = com.sun.star.comp.helper.Bootstrap
-					.createInitialComponentContext(null);
-			
-			
-			XComponentContext xRemoteContext = xComponentContext;
-
-			Object x = xRemoteContext
-					.getServiceManager()
-					.createInstanceWithContext(
-							"com.sun.star.connection.Connector", xRemoteContext);
-
-			XConnector xConnector = (XConnector) UnoRuntime.queryInterface(
-					XConnector.class, x);
-			
-			XConnection connection = xConnector.connect(cnx);
-
-			//if (connection == null)
-				//System.out.println("Connection is null");
-			x = xRemoteContext.getServiceManager().createInstanceWithContext(
-					"com.sun.star.bridge.BridgeFactory", xRemoteContext);
-
-			
-			XBridgeFactory xBridgeFactory = (XBridgeFactory) UnoRuntime
-					.queryInterface(XBridgeFactory.class, x);
-			
-			// this is the bridge that you will dispose
-			XBridge bridge = xBridgeFactory.createBridge("", "urp", connection,null);
-			
-			/*XComponent xComponent = (XComponent) UnoRuntime.queryInterface(
-					XComponent.class, bridge);*/
-			// get the remote instance
-			x = bridge.getInstance("StarOffice.ServiceManager");
-			// Query the initial object for its main factory interface
-			XMultiComponentFactory xMultiComponentFactory = (XMultiComponentFactory) UnoRuntime
-					.queryInterface(XMultiComponentFactory.class, x);
-			XPropertySet xProperySet = (XPropertySet) UnoRuntime
-					.queryInterface(XPropertySet.class, xMultiComponentFactory);
-			
-			// Get the default context from the office server.
-			Object oDefaultContext = xProperySet
-					.getPropertyValue("DefaultContext");
-
-			// Query for the interface XComponentContext.
-			xComponentContext = (XComponentContext) UnoRuntime.queryInterface(
-					XComponentContext.class, oDefaultContext);
-			
-			
-			while (xcomponentloader == null) {
-				try {	
-					
-					xcomponentloader = (XComponentLoader) UnoRuntime
-							.queryInterface(
-									XComponentLoader.class,
-									xMultiComponentFactory
-											.createInstanceWithContext(
-													"com.sun.star.frame.Desktop",
-													xComponentContext));
-					
-					//System.out.println("Loading document "+url);
-									
-					FTPClient ftp = new FTPClient();
-					if(!host.equals("localhost")){
-						//ftp connexion						
-						ftp.setRemoteHost(host);
-						ftp.connect();
-						ftp.login(ftpuser, ftpPasswd);
-						ftp.setConnectMode(FTPConnectMode.PASV);
-						ftp.setType(FTPTransferType.BINARY);
-			            try{
-			            	ftp.mkdir(remoteFolder);
-			            }catch(Exception e){}
-			            ftp.chdir(remoteFolder);
-			            ftp.put(url,"presentation.ppt");			            
-			            url = remoteFolderFullPath+"/"+remoteFolder+"/presentation.ppt";
-			            
-			            
-					}
-					
-					PropertyValue[] loadProps = new PropertyValue[2];
-					loadProps[0] = new PropertyValue();
-				    loadProps[0].Name = "Hidden";
-				    loadProps[0].Value = new Boolean(true);
-				    
-					// open the document
-					XComponent component = xcomponentloader
-							.loadComponentFromURL(url,
-									"_blank", 0, loadProps);
-					
-		          
-					//System.out.println("Document Opened");
-					
-					// filter
-					loadProps = new PropertyValue[4];
-					
-					// type of image
-					loadProps[0] = new PropertyValue();
-					loadProps[0].Name = "MediaType";
-					loadProps[0].Value = "image/png";
-					
-					// Height and width
-					PropertyValue[] filterDatas = new PropertyValue[4];
-					for(int i = 0; i<4 ; i++){
-						filterDatas[i] = new PropertyValue();
-					}
-					
-					filterDatas[0].Name = "PixelWidth";
-					filterDatas[0].Value = new Integer(width);
-					filterDatas[1].Name = "PixelHeight";
-					filterDatas[1].Value = new Integer(height);
-					filterDatas[2].Name = "LogicalWidth";
-					filterDatas[2].Value = new Integer(2000);
-					filterDatas[3].Name = "LogicalHeight";
-					filterDatas[3].Value = new Integer(2000);
-					
-					
-					XDrawPagesSupplier pagesSupplier = (XDrawPagesSupplier) UnoRuntime
-							.queryInterface(XDrawPagesSupplier.class, component);
-					//System.out.println(pagesSupplier.toString());				
-					XDrawPages pages = pagesSupplier.getDrawPages();
-					int nbPages = pages.getCount();
-		            
-					
-					for (int i = 0; i < nbPages; i++) {
-											
-						XDrawPage page = (XDrawPage) UnoRuntime.queryInterface(
-								com.sun.star.drawing.XDrawPage.class, pages
-										.getByIndex(i));
-						
-						XNamed xPageName = (XNamed)UnoRuntime.queryInterface(XNamed.class,page);
-						
-						xPageName.setName("slide"+(i+1));
-						//if(!xPageName.getName().equals("slide"+(i+1)) && !xPageName.getName().equals("page"+(i+1)))
-							//xPageName.setName((i+1)+"-"+xPageName.getName());
-						Object GraphicExportFilter = xMultiComponentFactory
-								.createInstanceWithContext(
-										"com.sun.star.drawing.GraphicExportFilter",
-										xComponentContext);
-						XExporter xExporter = (XExporter) UnoRuntime
-								.queryInterface(XExporter.class,
-										GraphicExportFilter);
-
-						XComponent xComp = (XComponent) UnoRuntime
-								.queryInterface(XComponent.class, page);
-
-						xExporter.setSourceDocument(xComp);
-						loadProps[1] = new PropertyValue();
-						loadProps[1].Name = "URL";
-						loadProps[1].Value = remoteFolderFullPath+remoteFolder+"/"+xPageName.getName()+".png";
-						loadProps[2] = new PropertyValue();
-						loadProps[2].Name = "FilterData";
-						loadProps[2].Value = filterDatas;
-						loadProps[3] = new PropertyValue(); 
-						loadProps[3].Name = "Quality"; 
-						loadProps[3].Value = new Integer(100);
-						
-						XFilter xFilter = (XFilter) UnoRuntime.queryInterface(XFilter.class, GraphicExportFilter);
-
-						xFilter.filter(loadProps);
-						System.out.println(xPageName.getName()+".png");
-
-						//System.out.println("Page saved to url "+loadProps[1].Value);
-						
-					}
-					
-					if(!host.equals("localhost")){
-						String[] files = ftp.dir();
-			            for (int i = 0; i < files.length; i++){
-			            	//System.out.println("Transfer of "+files[i]+ "to "+destinationFolder+"/"+files[i]);
-			            	if(!files[i].equals("presentation.ppt"))
-			            		ftp.get(destinationFolder+"/"+files[i],files[i]);
-			            	ftp.delete(files[i]);
-			            }
-						ftp.chdir("..");
-						ftp.rmdir(remoteFolder);
-						ftp.quit();
-					}
-					
-					//System.out.println("Closing Document");
-					component.dispose();
-					//System.out.println("Document close");
-
-					System.exit(0);
-				} 
-				catch (NoConnectException e) {
-					System.out.println(e.toString());
-					e.printStackTrace();
-					System.exit(255);
-				} 
-				catch (Exception e) {
-					System.out.println(e.toString());
-					e.printStackTrace();
-					System.exit(255);
-				}
-
-			}
-		} 
-		catch (Exception e) {
-			System.out.println(e.toString());
-			e.printStackTrace();
-			System.exit(255);
-		}
-
-	}
-	
-	public static String removeAccents(String text) {
-	    
-		/*
-		String newText =  Normalizer.decompose(text, false, 0)
-	                     .replaceAll("\\p{InCombiningDiacriticalMarks}+", "");*/
-	    /*
-	    newText = newText.replace('\u00B4','_');
-	    newText = newText.replace('\u02CA','_');
-	    newText = newText.replace('\u02B9','_');
-	    newText = newText.replace('\u02BC','_');	    
-	    newText = newText.replace('\u02B9','_');
-	    newText = newText.replace('\u03D8','_');
-	    newText = newText.replace('\u0374','_');
-	    newText = newText.replace('\u0384','_');
-	    newText = newText.replace('\u055A','_');
-	    */
-		/*
-	    newText = newText.replace('\u2019','_');
-	    newText = newText.replace('\u00B4','_');
-	    newText = newText.replace('\u055A','_');
-	    newText = newText.replace('?','_');
-	    newText = newText.replace('\'','_');
-	    newText = newText.replace(' ','_');
-	    return newText;*/
-		return java.text.Normalizer.normalize(text, java.text.Normalizer.Form.NFD).replaceAll("[\u0300-\u036F]", "");
-
-	}
-	
-	public boolean handleEvent(Event evt) {
-        // Traitement de l'evenement de fin de programme
-         if ( evt.id == evt.WINDOW_DESTROY ) {
-              System.exit(0) ;
-              return true ;
-         }
-         return false ;
-  }
-}

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

@@ -984,7 +984,8 @@ class SocialManager extends UserManager
         }
 
         if ($show_delete_account_button) {
-            $html .= '<div class="sidebar-nav"><ul><li>';
+            $html .= '<div class="panel panel-default"><div class="panel-body">';
+            $html .= '<ul class="nav nav-pills nav-stacked"><li>';
             $url = api_get_path(WEB_CODE_PATH).'auth/unsubscribe_account.php';
             $html .= Display::url(
                 Display::return_icon(
@@ -995,7 +996,8 @@ class SocialManager extends UserManager
                 ).get_lang('Unsubscribe'),
                 $url
             );
-            $html .= '</li></ul></div>';
+            $html .= '</li></ul>';
+            $html .= '</div></div>';
         }
         $html .= '';
 

+ 10 - 3
main/inc/lib/sortable_table.class.php

@@ -255,7 +255,6 @@ class SortableTable extends HTML_Table
         $empty_table = false;
 
         $content = $this->get_table_html();
-
         if ($this->get_total_number_of_items() == 0) {
             $cols = $this->getColCount();
             $this->setCellAttributes(1, 0, 'style="font-style: italic;text-align:center;" colspan='.$cols);
@@ -964,10 +963,18 @@ class SortableTable extends HTML_Table
      */
     public function get_table_data($from = null, $per_page = null, $column = null, $direction = null, $sort = null)
     {
+        $data = [];
         if (!is_null($this->get_data_function)) {
-            return call_user_func($this->get_data_function, $from, $this->per_page, $this->column, $this->direction);
+            $data = call_user_func(
+                $this->get_data_function,
+                $from,
+                $this->per_page,
+                $this->column,
+                $this->direction
+            );
         }
-        return array();
+
+        return $data;
     }
 
 

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

@@ -1271,7 +1271,7 @@ class UserManager
 
     /**
      * Get a list of users of which the given conditions match with an = 'cond'
-     * @param array $conditions a list of condition (exemple : status=>STUDENT)
+     * @param array $conditions a list of condition (example : status=>STUDENT)
      * @param array $order_by a list of fields on which sort
      * @return array An array with all users of the platform.
      * @todo optional course code parameter, optional sorting parameters...
@@ -1301,6 +1301,7 @@ class UserManager
         }
         $sql_result = Database::query($sql_query);
         while ($result = Database::fetch_array($sql_result)) {
+            $result['complete_name'] = api_get_person_name($result['firstname'], $result['lastname']);
             $return_array[] = $result;
         }
         return $return_array;
@@ -1340,6 +1341,11 @@ class UserManager
         }
         $sql_result = Database::query($sql_query);
         while ($result = Database::fetch_array($sql_result)) {
+
+            $result['complete_name'] = api_get_person_name(
+                $result['firstname'],
+                $result['lastname']               
+            );
             $return_array[] = $result;
         }
         return $return_array;

+ 29 - 17
main/inc/lib/userportal.lib.php

@@ -1095,6 +1095,7 @@ class IndexManager
         $sessionCount = 0;
         $courseCount = 0;
 
+        $items = [];
         // If we're not in the history view...
         if (!isset($_GET['history'])) {
             // Display special courses.
@@ -1109,6 +1110,7 @@ class IndexManager
                 $this->load_directories_preview
             );
             $courses_html .= $courses['html'];
+            $items = $courses['items'];
             $courseCount = $specialCourses['course_count'] + $courses['course_count'];
         }
 
@@ -1256,10 +1258,12 @@ class IndexManager
                             $this->tpl->assign('session', $params);
                             $this->tpl->assign('gamification_mode', $gamificationModeIsActive);
 
-                            $sessions_with_no_category .= $this->tpl->fetch(
+                            $item = $this->tpl->fetch(
                                 $this->tpl->get_template('/user_portal/session.tpl')
                             );
 
+                            $sessions_with_no_category .= $item;
+                            $items[] = $item;
                             $sessionCount++;
                         }
                     }
@@ -1344,9 +1348,7 @@ class IndexManager
                                 $sessionParams['id'] = $session_id;
                                 $sessionParams['show_link_to_session'] = !api_is_drh() && $sessionTitleLink;
                                 $sessionParams['title'] = $session_box['title'];
-                                $sessionParams['subtitle'] = (!empty($session_box['coach'])
-                                    ? $session_box['coach'] . ' | '
-                                    : '') . $session_box['dates'];
+                                $sessionParams['subtitle'] = (!empty($session_box['coach']) ? $session_box['coach'] . ' | ': '') . $session_box['dates'];
                                 $sessionParams['show_actions'] = api_is_platform_admin();
                                 $sessionParams['courses'] = $html_courses_session;
                                 $sessionParams['show_simple_session_info'] = false;
@@ -1356,10 +1358,14 @@ class IndexManager
                                 }
 
                                 $this->tpl->assign('session', $sessionParams);
-                                $html_sessions .= $this->tpl->fetch(
+                                $item = $this->tpl->fetch(
                                     $this->tpl->get_template('user_portal/session.tpl')
                                 );
 
+                                $html_sessions .= $item;
+
+                                $items[] = $item;
+
                                 $sessionCount++;
                             }
                         }
@@ -1405,16 +1411,19 @@ class IndexManager
                         }
 
                         $this->tpl->assign('session_category', $categoryParams);
-                        $sessions_with_category .= $this->tpl->fetch(
-                            "{$this->tpl->templateFolder}/user_portal/session_category.tpl"
-                        );
+                        $item = $this->tpl->fetch("{$this->tpl->templateFolder}/user_portal/session_category.tpl");
+                        $sessions_with_category .= $item;
+                        $items[] = $item;
                     }
                 }
             }
         }
 
+        $items = array_reverse($items);
+        
         return [
             'html' => $sessions_with_category.$sessions_with_no_category.$courses_html.$special_courses,
+            'items' => $items,
             'session_count' => $sessionCount,
             'course_count' => $courseCount
         ];
@@ -1466,7 +1475,7 @@ class IndexManager
         if ($load_history) {
             $html .= Display::page_subheader(get_lang('HistoryTrainingSession'));
             if (empty($session_categories)) {
-                $html .=  get_lang('YouDoNotHaveAnySessionInItsHistory');
+                $html .= get_lang('YouDoNotHaveAnySessionInItsHistory');
             }
         }
 
@@ -1518,7 +1527,7 @@ class IndexManager
         $listUserCategories[0] = '';
 
         $html = '<div class="session-view-block">';
-
+        $items = [];
         foreach ($listUserCategories as $userCategoryId => $userCatTitle) {
             // add user category
             $userCategoryHtml = '';
@@ -1574,7 +1583,7 @@ class IndexManager
                     }
                     $htmlSessionCategory .= '</div>'; // end session cat block
                     $htmlCategory .=  $htmlSessionCategory .'</div>' ;
-                    $htmlCategory .= '';   // end course block
+                    $items[] = $htmlSessionCategory;
                 }
                 $userCategoryHtml .= $htmlCategory;
             }
@@ -1583,32 +1592,35 @@ class IndexManager
             // if course not already added
             $htmlCategory = '';
             foreach ($listCoursesInfo as $i => $listCourse) {
+                $item = '';
                 if ($listCourse['userCatId'] == $userCategoryId && !isset($listCoursesAlreadyDisplayed[$listCourse['id']])) {
                     if ($userCategoryId != 0) {
-                        $htmlCategory .= '<div class="session-view-row" >';
+                        $item .= '<div class="session-view-row" >';
                     } else {
-                        $htmlCategory .= '<div class="session-view-well well">';
+                        $item .= '<div class="session-view-well well">';
                     }
-                    $htmlCategory .= self::getHtmlForCourse(
+                    $item .= self::getHtmlForCourse(
                         $listCourse['course'],
                         $userCategoryId,
                         0,
                         $loadDirs
                     );
-                    $htmlCategory .= '</div>';
+                    $item .= '</div>';
+                    $htmlCategory .= $item;
+                    $items[] = $item;
                 }
             }
-            $htmlCategory .= '';
             $userCategoryHtml .= $htmlCategory;   // end user cat block
             if ($userCategoryId != 0) {
                 $userCategoryHtml .= '</div>';
             }
-            $html .= $userCategoryHtml;   //
+            $html .= $userCategoryHtml;
         }
         $html .= '</div>';
 
         return [
             'html' => $html.$specialCourses,
+            'items' => $items,
             'session_count' => $sessionCount,
             'course_count' => $courseCount
         ];

+ 2 - 1
main/lang/brazilian/trad4all.inc.php

@@ -7418,7 +7418,7 @@ $FieldTypeLettersSpaces = "letras e espaços de texto";
 $CronRemindCourseFinishedActivateTitle = "Enviar curso terminou notificação";
 $FieldTypeAlphanumericSpaces = "caracteres alfanuméricos texto e espaços";
 $CronRemindCourseFinishedActivateComment = "Se enviar um e-mail para os alunos quando seu curso (sessão) é acabados. Isto requer tarefas cron para ser configurado (ver/cron/diretório principal).";
-$ThanksForRegisteringToSite = "Obrigado por registrar em %s.";
+$ThanksForRegisteringToSite = "Obrigado por registrar-se em %s.";
 $AllowCoachFeedbackExercisesTitle = "Permitir que os treinadores para comentar na avaliação de exercícios";
 $AllowCoachFeedbackExercisesComment = "Permitir que os treinadores para editar o feedback durante a revisão de exercícios";
 $PreventMultipleSimultaneousLoginTitle = "Impedir o login simultânea";
@@ -7558,4 +7558,5 @@ $NewLogoUpdated = "Novo logotipo carregado";
 $CurrentLogo = "Logo atual";
 $UpdateLogo = "Logotipo atualização";
 $FollowedStudentBosses = "chefes de estudantes seguiram";
+$AccessData = "Dados de acesso";
 ?>

+ 4 - 0
main/lang/english/trad4all.inc.php

@@ -7640,4 +7640,8 @@ $AdditionallyYouHaveObtainedTheFollowingSkills = "Additionally, you have achieve
 $IHaveObtainedSkillXOnY = "I have achieved skill %s on %s";
 $AnotherAttempt = "Another attempt";
 $RemainingXAttempts = "Remaining %d attempts";
+$Map = "Map";
+$MyLocation = "My location";
+$ShowCourseInUserLanguage = "Show course in user's language";
+$AccessData = "Access data";
 ?>

+ 6 - 0
main/lang/french/trad4all.inc.php

@@ -7625,4 +7625,10 @@ $EnableForumAutoLaunch = "Activar l'auto-démarrage des forums";
 $NowDownloadYourCertificateClickHere = "Télécharge ton certificat en cliquant ici";
 $AdditionallyYouHaveObtainedTheFollowingSkills = "En plus, tu as acquis les compétences suivantes";
 $IHaveObtainedSkillXOnY = "J'ai acquis la compétence %s sur %s";
+$AnotherAttempt = "Nouvelle tentative";
+$RemainingXAttempts = "%s tentatives restantes";
+$Map = "Carte";
+$MyLocation = "Ma position";
+$ShowCourseInUserLanguage = "Afficher les cours dans la langue de l'utilisateur";
+$AccessData = "Données d'accès";
 ?>

+ 4 - 0
main/lang/spanish/trad4all.inc.php

@@ -7665,4 +7665,8 @@ $AdditionallyYouHaveObtainedTheFollowingSkills = "Adicionalmente, has obtenido l
 $IHaveObtainedSkillXOnY = "He logrado la competencia %s en %s";
 $AnotherAttempt = "Realizar otro intento";
 $RemainingXAttempts = "%s intentos restantes";
+$Map = "Mapa";
+$MyLocation = "Mi ubicación";
+$ShowCourseInUserLanguage = "Mostrar el curso en el idioma del usuario";
+$AccessData = "Datos de acceso";
 ?>

+ 1 - 1
main/mySpace/company_reports.php

@@ -16,7 +16,7 @@ if ($userNotAllowed) {
     api_not_allowed(true);
 }
 
-$interbreadcrumb[] = array ('url' => 'index.php', 'name' => get_lang('MySpace'));
+$interbreadcrumb[] = array ('url' => api_is_student_boss()?'#':'index.php', 'name' => get_lang('MySpace'));
 $tool_name = get_lang('Report');
 
 $this_section = SECTION_TRACKING;

+ 1 - 1
main/mySpace/company_reports_resumed.php

@@ -16,7 +16,7 @@ if ($userNotAllowed) {
     api_not_allowed(true);
 }
 
-$interbreadcrumb[] = array('url' => 'index.php', 'name' => get_lang('MySpace'));
+$interbreadcrumb[] = array('url' => api_is_student_boss()?'#':'index.php', 'name' => get_lang('MySpace'));
 
 $tool_name = get_lang('Report');
 $this_section = SECTION_TRACKING;

+ 2 - 2
main/mySpace/myStudents.php

@@ -91,7 +91,7 @@ if (isset($_GET['details'])) {
                 );
             } else {
                 $interbreadcrumb[] = array (
-                    "url" => "index.php",
+                    "url" => api_is_student_boss()?"#":"index.php",
                     "name" => get_lang('MySpace')
                 );
                 if (isset ($_GET['id_coach']) && intval($_GET['id_coach']) != 0) {
@@ -129,7 +129,7 @@ if (isset($_GET['details'])) {
         }
     } else {
         $interbreadcrumb[] = array (
-            "url" => "index.php",
+            "url" => api_is_student_boss()?"#":"index.php",
             "name" => get_lang('MySpace')
         );
         if (isset ($_GET['id_coach']) && intval($_GET['id_coach']) != 0) {

+ 1 - 1
main/mySpace/student.php

@@ -19,7 +19,7 @@ api_block_anonymous_users();
 
 $this_section = SECTION_TRACKING;
 
-$interbreadcrumb[] = array ("url" => "index.php", "name" => get_lang('MySpace'));
+$interbreadcrumb[] = array ("url" => api_is_student_boss()?"#":"index.php", "name" => get_lang('MySpace'));
 
 if (isset($_GET["user_id"]) && $_GET["user_id"] != "" && !isset($_GET["type"])) {
     $interbreadcrumb[] = array ("url" => "teachers.php", "name" => get_lang('Teachers'));

+ 5 - 0
main/newscorm/final_item_template/template.html

@@ -0,0 +1,5 @@
+<div>
+    Congratulations! You have finished this learning path
+</div>
+((certificate)) <br />
+((skill))

+ 210 - 50
main/newscorm/learnpath.class.php

@@ -266,7 +266,9 @@ class learnpath
                             error_log(
                                 'New LP - learnpath::__construct() - ' .
                                 'aicc object with id ' . $my_item_id .
-                                ' set in items[]', 0);
+                                ' set in items[]',
+                                0
+                            );
                         }
                     }
                     break;
@@ -594,16 +596,16 @@ class learnpath
         $params = array(
             "c_id" => $course_id,
             "lp_id" => $this->get_id(),
-            "item_type" =>$typeCleaned ,
+            "item_type" => $typeCleaned,
             "ref" => '',
-            "title" =>$title ,
+            "title" => $title,
             "description" => $description,
             "path" => $id,
             "max_score" => $max_score,
             "parent_item_id" => $parent,
             "previous_item_id" => $previous,
             "next_item_id" => intval($next),
-            "display_order" => $display_order +1,
+            "display_order" => $display_order + 1,
             "prerequisite" => $prerequisites,
             "max_time_allowed" => $max_time_allowed,
             'min_score' => 0,
@@ -1127,7 +1129,7 @@ class learnpath
         );
 
         $link_info = GradebookUtils::is_resource_in_course_gradebook(api_get_course_id(), 4 , $id, api_get_session_id());
-        if ($link_info) {
+        if ($link_info !== false) {
             GradebookUtils::remove_resource_from_course_gradebook($link_info->getId());
         }
 
@@ -1957,7 +1959,7 @@ class learnpath
             error_log('New LP - In learnpath::get_last()', 0);
         }
         //This is just in case the lesson doesn't cointain a valid scheme, just to avoid "Notices"
-        if ($this->index > 0) {
+        if (count($this->ordered_items) > 0) {
             $this->index = count($this->ordered_items) - 1;
             return $this->ordered_items[$this->index];
         }
@@ -1984,7 +1986,7 @@ class learnpath
         $lp_id = $this->lp_id;
         $mycurrentitemid = $this->get_current_item_id();
 
-         if ($this->mode == 'fullscreen') {
+        if ($this->mode == 'fullscreen') {
             $navbar = '
                   <span id="'.$idBar.'" class="buttons">
                     <a class="icon-toolbar" href="lp_controller.php?action=stats&'.api_get_cidreq(true).'&lp_id='.$lp_id.'" onclick="window.parent.API.save_asset();return true;" target="content_name" title="stats" id="stats_link">
@@ -3427,7 +3429,7 @@ class learnpath
             }
 
             // Fixed issue BT#1272 - If the item type is a Chamilo Item (quiz, link, etc), then change the lp type to thread it as a normal Chamilo LP not a SCO.
-            if (in_array($lp_item_type, array('quiz', 'document', 'link', 'forum', 'thread', 'student_publication'))) {
+            if (in_array($lp_item_type, array('quiz', 'document', 'final_item', 'link', 'forum', 'thread', 'student_publication'))) {
                 $lp_type = 1;
             }
 
@@ -3457,10 +3459,10 @@ class learnpath
                             break;
                         case 'link':
                             if (Link::is_youtube_link($file)) {
-                                $src  = Link::get_youtube_video_id($file);
+                                $src = Link::get_youtube_video_id($file);
                                 $file = api_get_path(WEB_CODE_PATH).'newscorm/embed.php?type=youtube&source='.$src;
                             } elseif (Link::isVimeoLink($file)) {
-                                $src  = Link::getVimeoLinkId($file);
+                                $src = Link::getVimeoLinkId($file);
                                 $file = api_get_path(WEB_CODE_PATH).'newscorm/embed.php?type=vimeo&source='.$src;
                             } else {
                                 // If the current site is HTTPS and the link is
@@ -3530,11 +3532,11 @@ class learnpath
                             break;
                         }
 
-                            $tmp_array = explode('/', $file);
-                            $document_name = $tmp_array[count($tmp_array) - 1];
-                            if (strpos($document_name, '_DELETED_')) {
-                                $file = 'blank.php?error=document_deleted';
-                            }
+			$tmp_array = explode('/', $file);
+			$document_name = $tmp_array[count($tmp_array) - 1];
+			if (strpos($document_name, '_DELETED_')) {
+			    $file = 'blank.php?error=document_deleted';
+			}
 
                     break;
                 case 2 :
@@ -5577,7 +5579,7 @@ class learnpath
      */
     public function return_new_tree($update_audio = 'false', $drop_element_here = false)
     {
-    $return = '';
+        $return = '';
         $is_allowed_to_edit = api_is_allowed_to_edit(null,true);
 
         $course_id = api_get_course_int_id();
@@ -5634,6 +5636,11 @@ class learnpath
                 )
             );
 
+	        // Detect if type is FINAL_ITEM to set path_id to SESSION
+            if ($arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
+                $_SESSION['pathItem'] = $arrLP[$i]['path'];
+            }
+
             if (($i % 2) == 0) {
                 $oddClass = 'row_odd';
             } else {
@@ -5649,7 +5656,11 @@ class learnpath
                 if (file_exists('../img/lp_' . $icon_name . '.gif')) {
                     $icon = Display::return_icon('lp_' . $icon_name . '.gif');
                 } else {
-                    $icon = Display::return_icon('folder_document.gif');
+                    if ($arrLP[$i]['item_type'] === TOOL_LP_FINAL_ITEM) {
+                        $icon = Display::return_icon('certificate.png');
+                    } else {
+                        $icon = Display::return_icon('folder_document.gif');
+                    }
                 }
             }
 
@@ -5685,13 +5696,15 @@ class learnpath
 
             if ($is_allowed_to_edit) {
                 if (!$update_audio || $update_audio <> 'true') {
-                    $move_icon .= '<a class="moved" href="#">';
-                    $move_icon .= Display::return_icon('move_everywhere.png', get_lang('Move'), array(), ICON_SIZE_TINY);
-                    $move_icon .= '</a>';
+                    if ($arrLP[$i]['item_type'] !== TOOL_LP_FINAL_ITEM) {
+                        $move_icon .= '<a class="moved" href="#">';
+                        $move_icon .= Display::return_icon('move_everywhere.png', get_lang('Move'), array(), ICON_SIZE_TINY);
+                        $move_icon .= '</a>';
+                    }
                 }
 
                 // No edit for this item types
-                if (!in_array($arrLP[$i]['item_type'], array('sco', 'asset'))) {
+                if (!in_array($arrLP[$i]['item_type'], array('sco', 'asset', 'final_item'))) {
                     if (!in_array($arrLP[$i]['item_type'], array('dokeos_chapter', 'dokeos_module'))) {
                         $edit_icon .= '<a href="'.api_get_self().'?'.api_get_cidreq().'&action=edit_item&view=build&id=' . $arrLP[$i]['id'] . '&lp_id=' . $this->lp_id . '&path_item=' . $arrLP[$i]['path'] . '" class="btn btn-default">';
                         $edit_icon .= Display::return_icon('edit.png', get_lang('LearnpathEditModule'), array(), ICON_SIZE_TINY);
@@ -5890,14 +5903,22 @@ class learnpath
                 if (isset($_REQUEST['id']) && $key == $_REQUEST['id']) {
                     $active = 'active';
                 }
-                $return  .= Display::tag('li', Display::div($item['data'], array('class'=>"item_data $active")).$sub_list, array('id'=>$key, 'class'=>'record li_container'));
+                $return  .= Display::tag(
+                    'li',
+                    Display::div($item['data'], array('class'=>"item_data $active")).$sub_list,
+                    array('id'=>$key, 'class'=>'record li_container')
+                );
             } else {
                 // Sections
                 if (isset($item['children'])) {
                     $data = self::print_recursive($item['children'], $default_data, $default_content);
                 }
                 $sub_list = Display::tag('ul', $sub_list.$data, array('id'=>'UL_'.$key, 'class'=>'record li_container'));
-                $return .= Display::tag('li', Display::div($item['data'], array('class'=>'item_data')).$sub_list, array('id'=>$key, 'class'=>'record li_container'));
+                $return .= Display::tag(
+                    'li',
+                    Display::div($item['data'], array('class'=>'item_data')).$sub_list,
+                    array('id'=>$key, 'class'=>'record li_container')
+                );
             }
         }
 
@@ -6081,7 +6102,6 @@ class learnpath
 
        $creatorId = empty($creatorId) ? api_get_user_id() : $creatorId;
        $sessionId = api_get_session_id();
-        //$dir = '/';
         // Generates folder
         $result = $this->generate_lp_folder($courseInfo);
         $dir = $result['dir'];
@@ -6091,16 +6111,16 @@ class learnpath
             $dir = isset ($_GET['dir']) ? $_GET['dir'] : $postDir; // Please, do not modify this dirname formatting.
             // Please, do not modify this dirname formatting.
             if (strstr($dir, '..')) {
-            $dir = '/';
+                $dir = '/';
             }
             if (!empty($dir[0]) && $dir[0] == '.') {
-            $dir = substr($dir, 1);
+                $dir = substr($dir, 1);
             }
             if (!empty($dir[0]) && $dir[0] != '/') {
-            $dir = '/' . $dir;
+                $dir = '/'.$dir;
             }
             if (isset($dir[strlen($dir) - 1]) && $dir[strlen($dir) - 1] != '/') {
-            $dir .= '/';
+                $dir .= '/';
             }
         } else {
             $parentInfo = DocumentManager::get_document_data_by_id($parentId, $courseInfo['code']);
@@ -6423,6 +6443,22 @@ class learnpath
                     $return .= $this->display_manipulate($item_id, $row['item_type']);
                     $return .= $this->display_link_form('edit', $item_id, $row);
                     break;
+                case TOOL_LP_FINAL_ITEM :
+                    $_SESSION['finalItem'] = true;
+                    $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.id = lp.path
+                            WHERE
+                                lp.c_id = $course_id AND
+                                doc.c_id = $course_id AND
+                                lp.id = " . intval($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 'dokeos_module' :
                     if (isset ($_GET['view']) && $_GET['view'] == 'build') {
                         $return .= $this->display_manipulate($item_id, $row['item_type']);
@@ -6475,19 +6511,23 @@ class learnpath
         // Get all the links.
         $links = $this->get_links();
 
-        // Get al the student publications.
+        // Get all the student publications.
         $works = $this->get_student_publications();
 
-        // Get al the forums.
+        // Get all the forums.
         $forums = $this->get_forums(null, $course_code);
 
+        // Get the final item form (see BT#11048).
+        $finish = $this->getFinalItemForm();
+
         $headers = array(
             Display::return_icon('folder_document.png', get_lang('Documents'), array(), ICON_SIZE_BIG),
             Display::return_icon('quiz.png',  get_lang('Quiz'), array(), ICON_SIZE_BIG),
             Display::return_icon('links.png', get_lang('Links'), array(), ICON_SIZE_BIG),
             Display::return_icon('works.png', get_lang('Works'), array(), ICON_SIZE_BIG),
             Display::return_icon('forum.png', get_lang('Forums'), array(), ICON_SIZE_BIG),
-            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), array(), ICON_SIZE_BIG)
+            Display::return_icon('add_learnpath_section.png', get_lang('NewChapter'), array(), ICON_SIZE_BIG),
+            Display::return_icon('certificate.png', get_lang('Certificate'), [], ICON_SIZE_BIG),
         );
 
         echo Display::display_normal_message(get_lang('ClickOnTheLearnerViewToSeeYourLearningPath'));
@@ -6498,7 +6538,7 @@ class learnpath
         );
         echo Display::tabs(
             $headers,
-            array($documents, $exercises, $links, $works, $forums, $chapter), 'resource_tab'
+            array($documents, $exercises, $links, $works, $forums, $chapter, $finish), 'resource_tab'
         );
 
         return true;
@@ -7802,10 +7842,10 @@ class learnpath
 
         //POSITION
         for ($i = 0; $i < count($arrLP); $i++) {
-            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id) {
-                if (isset($extra_info['previous_item_id']) && $extra_info['previous_item_id'] == $arrLP[$i]['id'])
+            if ($arrLP[$i]['parent_item_id'] == $parent && $arrLP[$i]['id'] != $id || $arrLP[$i]['item_type'] == TOOL_LP_FINAL_ITEM) {
+                if (isset($extra_info['previous_item_id']) && $extra_info['previous_item_id'] == $arrLP[$i]['id'] || $action == 'add') {
                     $s_selected_position = $arrLP[$i]['id'];
-                elseif ($action == 'add') $s_selected_position = $arrLP[$i]['id'];
+                }
                 $arrHide[$arrLP[$i]['id']]['value'] = get_lang('After') . ' "' . $arrLP[$i]['title'] . '"';
             }
         }
@@ -7817,6 +7857,7 @@ class learnpath
             $padding = isset($value['padding']) ? $value['padding']: 20;
             $position->addOption($value['value'], $key, 'style="padding-left:' . $padding . 'px;"');
         }
+
         $position->setSelected($s_selected_position);
 
         if (is_array($arrLP)) {
@@ -7835,7 +7876,6 @@ class learnpath
             }
 
             $arrHide = array();
-
             for ($i = 0; $i < count($arrLP); $i++) {
                 if ($arrLP[$i]['id'] != $id && $arrLP[$i]['item_type'] != 'dokeos_chapter') {
                     if (isset($extra_info['previous_item_id']) && $extra_info['previous_item_id'] == $arrLP[$i]['id'])
@@ -8395,7 +8435,7 @@ class learnpath
                 // Commented the message cause should not show it.
                 //$lang = get_lang('TitleManipulateModule');
                 break;
-
+            case TOOL_LP_FINAL_ITEM:
             case TOOL_DOCUMENT :
                 // Commented the message cause should not show it.
                 //$lang = get_lang('TitleManipulateDocument');
@@ -8469,7 +8509,7 @@ class learnpath
             $return .= get_lang('File').': '.$document_data['absolute_path_from_document'];
         }
 
-        if ($item_type == TOOL_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'];
         }
@@ -9078,7 +9118,7 @@ class learnpath
                         Display::return_icon('preview_view.png', get_lang('Preview')),
                         api_get_path(WEB_CODE_PATH).'work/work_list_all.php?'.api_get_cidreq().'&id='.$work['iid'],
                         ['target' => '_blank']
-        );
+                    );
 
                     $return .= '<li class="lp_resource_element" data_id="'.$work['iid'].'" data_type="'.TOOL_STUDENTPUBLICATION.'" title="'.Security :: remove_XSS(cut(strip_tags($work['title']), 80)).'">';
                     $return .= '<a class="moved" href="#">';
@@ -9123,7 +9163,7 @@ class learnpath
         );
         $return .= Display::url(
             get_lang('CreateANewForum'),
-            api_get_path(REL_CODE_PATH) . 'forum/index.php?' . api_get_cidreq() . '&' . http_build_query([
+            api_get_path(WEB_CODE_PATH) . 'forum/index.php?' . api_get_cidreq() . '&' . http_build_query([
                 'action' => 'add',
                 'content' => 'forum',
                 'lp_id' => $this->lp_id
@@ -9133,16 +9173,16 @@ class learnpath
         $return .= '</li>';
 
         $return .= '<script>
-                    function toggle_forum(forum_id){
-                        if(document.getElementById("forum_"+forum_id+"_content").style.display == "none"){
-                            document.getElementById("forum_"+forum_id+"_content").style.display = "block";
-                            document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('remove.gif').'";
-                        } else {
-                            document.getElementById("forum_"+forum_id+"_content").style.display = "none";
-                            document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('add.gif').'";
-                        }
-                    }
-                </script>';
+            function toggle_forum(forum_id){
+                if(document.getElementById("forum_"+forum_id+"_content").style.display == "none"){
+                    document.getElementById("forum_"+forum_id+"_content").style.display = "block";
+                    document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('remove.gif').'";
+                } else {
+                    document.getElementById("forum_"+forum_id+"_content").style.display = "none";
+                    document.getElementById("forum_"+forum_id+"_opener").src = "' . Display::returnIconPath('add.gif').'";
+                }
+            }
+        </script>';
 
         foreach ($a_forums as $forum) {
             if (!empty($forum['forum_id'])) {
@@ -11022,6 +11062,126 @@ EOD;
         return $forumId;
     }
 
+    /**
+     * Check and obtain the lp final item if exist
+     *
+     * @return array lp items
+     */
+    private function getFinalItem()
+    {
+
+        if (empty($this->items)) {
+            return null;
+        }
+
+        foreach ($this->items as $item) {
+            if ($item->type !== 'final_item') {
+                continue;
+            }
+
+            return $item;
+        }
+    }
+
+    /**
+    * Get the LP Final Item Template
+     *
+     * @return html
+     */
+    private function getFinalItemTemplate()
+    {
+        return file_get_contents(api_get_path(SYS_CODE_PATH) . 'newscorm/final_item_template/template.html');
+    }
+
+    /**
+     * Get the LP Final Item Url
+     *
+     * @return String
+     */
+    private function getSavedFinalItem()
+    {
+        $finalItem = $this->getFinalItem();
+        $doc = DocumentManager::get_document_data_by_id($finalItem->path, $this->cc);
+
+        return file_get_contents($doc['absolute_path']);
+    }
+
+    /**
+     * Get the LP Final Item form
+     *
+     * @return html
+     */
+    public function getFinalItemForm()
+    {
+        $finalItem = $this->getFinalItem();
+        $title = '';
+        $content = '';
+
+        if ($finalItem) {
+            $title = $finalItem->title;
+            $buttonText = get_lang('Save');
+            $content = $this->getSavedFinalItem();
+        } else {
+            $buttonText = get_lang('LPCreateDocument');
+            $content = $this->getFinalItemTemplate();
+        }
+
+        $courseInfo = api_get_course_info();
+        $result = $this->generate_lp_folder($courseInfo);
+        $relative_path = api_substr($result['dir'], 1, strlen($result['dir']));
+        $relative_prefix = '../../';
+
+        $editorConfig = [
+            'ToolbarSet' => 'LearningPathDocuments',
+            'Width' => '100%',
+            'Height' => '500',
+            'FullPage' => true,
+            'CreateDocumentDir' => $relative_prefix,
+            'CreateDocumentWebDir' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/',
+            'BaseHref' => api_get_path(WEB_COURSE_PATH) . api_get_course_path() . '/document/' . $relative_path
+        ];
+
+        $url = api_get_self() . '?' . api_get_cidreq() . '&' . http_build_query([
+            'type' => 'document',
+            'lp_id' => $this->lp_id
+        ]);
+
+        $form = new FormValidator('final_item', 'POST', $url);
+        $form->addText('title', get_lang('Title'));
+        $form->addButtonSave($buttonText);
+        $form->addHtml('<div class="alert alert-info">Variables :</br></br> <b>((certificate))</b> </br> <b>((skill))</b></div>');
+        $renderer = $form->defaultRenderer();
+        $renderer->setElementTemplate('<div class="editor-lp">&nbsp;{label}{element}</div>', 'content_lp');
+        $form->addHtmlEditor('content_lp', null, null, true, $editorConfig, true);
+        $form->addHidden('action', 'add_final_item');
+        $form->addHidden('path', isset($_SESSION['pathItem']) ? $_SESSION['pathItem'] : '');
+        $form->addHidden('previous', $this->get_last());
+
+        $form->setDefaults(['title' => $title, 'content_lp' => $content]);
+
+        if ($form->validate()) {
+            $values = $form->exportValues();
+
+            $lastItemId = $this->get_last();
+
+            if (!$finalItem) {
+                $documentId = $this->create_document($this->course_info, $values['content_lp'], $values['title']);
+                $this->add_item(
+                    0,
+                    $lastItemId,
+                    'final_item',
+                    $documentId,
+                    $values['title'],
+                    ''
+                );
+            } else {
+                $this->edit_document($this->course_info);
+            }
+        }
+
+        return $form->returnForm();
+    }
+
     /**
      * Returns an HTML-formatted link to a resource, to incorporate directly into
      * the new learning path tool.

+ 5 - 5
main/newscorm/lp_add_item.php

@@ -268,7 +268,7 @@ if (isset($new_item_id) && is_numeric($new_item_id)) {
         case 'module':
             echo $learnPath->display_item_form($type, get_lang('EnterDataNewModule'));
             break;
-        case 'document':
+        case TOOL_DOCUMENT:
             if (isset($_GET['file']) && is_numeric($_GET['file'])) {
                 echo $learnPath->display_document_form('add', 0, $_GET['file']);
             } else {
@@ -278,20 +278,20 @@ if (isset($new_item_id) && is_numeric($new_item_id)) {
         case 'hotpotatoes':
             echo $learnPath->display_hotpotatoes_form('add', 0, $_GET['file']);
             break;
-        case 'quiz':
+        case TOOL_QUIZ:
             echo Display::display_warning_message(get_lang('ExerciseCantBeEditedAfterAddingToTheLP'));
             echo $learnPath->display_quiz_form('add', 0, $_GET['file']);
             break;
-        case 'forum':
+        case TOOL_FORUM:
             echo $learnPath->display_forum_form('add', 0, $_GET['forum_id']);
             break;
         case 'thread':
             echo $learnPath->display_thread_form('add', 0, $_GET['thread_id']);
             break;
-        case 'link':
+        case TOOL_LINK:
             echo $learnPath->display_link_form('add', 0, $_GET['file']);
             break;
-        case 'student_publication':
+        case TOOL_STUDENTPUBLICATION:
             $extra = isset($_GET['file']) ? $_GET['file'] : null;
             echo $learnPath->display_student_publication_form('add', 0, $extra);
             break;

+ 47 - 4
main/newscorm/lp_controller.php

@@ -336,6 +336,8 @@ if (isset($_POST['title'])) {
     }
 }
 
+$redirectTo = null;
+
 switch ($action) {
     case 'add_item':
         if (!$is_allowed_to_edit) {
@@ -355,12 +357,20 @@ switch ($action) {
 
                 //Updating the lp.modified_on
                 $learnPath->set_modified_on();
-                $postTimeFromSession = Session::read('post_time');
+                $postTimeFromSession = Session::read('post_time');               
+          
                 if (isset($postTimeFromSession) && $postTimeFromSession == $_POST['post_time']) {
                     // Check post_time to ensure ??? (counter-hacking measure?)
                     require 'lp_add_item.php';
                 } else {
                     Session::write('post_time', $_POST['post_time']);
+
+                    $directoryParentId = isset($_POST['directory_parent_id']) ? $_POST['directory_parent_id'] : 0;
+
+                    if (empty($directoryParentId)) {
+                        $learnPath->generate_lp_folder($courseInfo);
+                    }
+
                     $parent = isset($_POST['parent']) ? $_POST['parent'] : '';
                     $previous = isset($_POST['previous']) ? $_POST['previous'] : '';
                     $type = isset($_POST['type']) ? $_POST['type'] : '';
@@ -374,7 +384,11 @@ switch ($action) {
                             $document_id = $_POST['path'];
                         } else {
                             $document_id = $learnPath->create_document(
-                                $_course
+                                $_course,
+                                $_POST['content_lp'],
+                                $_POST['title'],
+                                'html',
+                                $directoryParentId
                             );
                         }
                         $new_item_id = $learnPath->add_item(
@@ -1114,8 +1128,7 @@ switch ($action) {
         if (!$lp_found) {
             error_log('New LP - No learnpath given for content', 0);
             require 'lp_list.php';
-        }
-        else {
+        } else {
             $learnPath->save_last();
             $learnPath->set_current_item($_GET['item_id']);
             $learnPath->start_current_item();
@@ -1430,6 +1443,29 @@ switch ($action) {
             'lp_id' => $_SESSION['oLP']->lp_id
         ]));
         exit;
+        break;
+    case 'add_final_item':
+        if (!$lp_found) {
+            Display::addFlash(
+                Display::return_message(get_lang('NoLPFound'), 'error')
+            );
+            break;
+        }
+
+        Session::write('refresh', 1);
+
+        if (!isset($_POST['submit']) || empty($post_title)) {
+            break;
+        }
+
+        $learnPath->getFinalItemForm();
+
+        $redirectTo = api_get_self() . '?' . http_build_query([
+            'action' => 'add_item',
+            'type' => 'step',
+            'lp_id' => intval$learnPath->lp_id)
+        ]);
+        break;
     default:
         if ($debug > 0) error_log('New LP - default action triggered', 0);
         require 'lp_list.php';
@@ -1441,3 +1477,10 @@ if (!empty($learnPath)) {
     Session::write('lpobject', serialize($learnPath));
     if ($debug > 0) error_log('New LP - lpobject is serialized in session', 0);
 }
+
+
+if (!empty($redirectTo)) {
+    header("Location: $redirectTo");
+    exit;
+}
+

+ 128 - 0
main/newscorm/lp_final_item.php

@@ -0,0 +1,128 @@
+<?php
+
+$_in_course = true;
+require_once '../inc/global.inc.php';
+$current_course_tool  = TOOL_GRADEBOOK;
+
+api_protect_course_script(true);
+
+$courseCode = api_get_course_id();
+$userId = api_get_user_id();
+$sessionId = api_get_session_id();
+$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
+$lpId = isset($_GET['lp_id']) ? intval($_GET['lp_id']) : 0;
+
+if (!$id && !$lpId) {
+    Display::display_warning_message(get_lang('FileNotFound'));
+    exit;
+}
+
+$catLoad = Category::load(null, null, $courseCode, null, null, $sessionId, 'ORDER By id');
+
+if (!$catLoad) {
+    Display::display_warning_message(get_lang('FileNotFound'));
+    exit;
+}
+
+$categoryId = $catLoad[0]->get_id();
+$link = LinkFactory::load(null, null, $lpId, null, $courseCode, $categoryId);
+$downloadCertificateLink = '';
+$viewCertificateLink = '';
+$badgeLink = '';
+
+if ($link) {
+    $cat = new Category();
+    $catCourseCode     = CourseManager::get_course_by_category($categoryId);
+    $show_message  = $cat->show_message_resource_delete($catCourseCode);
+
+    if ($show_message == '') {
+        if (!api_is_allowed_to_edit() && !api_is_excluded_user_type()) {
+            $certificate = Category::register_user_certificate(
+                $categoryId,
+                $userId
+            );
+            if (isset($certificate['pdf_url']) && isset($certificate['certificate_link']) && isset($certificate['badge_link'])) {
+                $downloadCertificateLink .= Display::url(Display::returnFontAwesomeIcon('file-pdf-o') .
+                    get_lang('DownloadCertificatePdf'),
+                    $certificate['pdf_url'],
+                    ['class' => 'btn btn-default']
+                );
+                $viewCertificateLink .= $certificate['certificate_link'];
+                $downloadCertificateLink = "
+                    <div class='panel panel-default'>
+                        <div class='panel-body'>
+                            <h3 class='text-center'>".get_lang('NowDownloadYourCertificateClickHere')."</h3>
+                            <div class='text-center'>$downloadCertificateLink $viewCertificateLink</div>
+                        </div>
+                    </div>
+                ";
+
+                $skillRelUser = new SkillRelUser();
+                $courseId = api_get_course_int_id();
+                $userSkills = $skillRelUser->get_user_skills($userId, $courseId, $sessionId);
+                $skillList = '';
+
+                if ($userSkills) {
+                    $skill = new Skill();
+                    foreach ($userSkills as $userSkill) {
+                        $oneSkill = $skill->get($userSkill['skill_id']);
+                        $skillList .= "
+                            <div class='row'>
+                                <div class='col-md-2 col-xs-6'>
+                                    <div class='thumbnail'>
+                                      <img class='skill-badge-img' src='".$oneSkill['web_icon_path']."' >
+                                    </div>
+                                </div>
+                                <div class='col-md-8 col-xs-6'>
+                                    <h5><b>".$oneSkill['name']."</b></h5>
+                                    ".$oneSkill['description']."
+                                </div>
+                                <div class='col-md-2 col-xs-12'>
+                                    <h5><b>".get_lang('ShareWithYourFriends')."</b></h5>
+                                    <a href='http://www.facebook.com/sharer.php?u=".api_get_path(WEB_PATH)."badge/".$oneSkill['id']."/user/".$userId."' target='_new'>
+                                        <em class='fa fa-facebook-square fa-3x text-info' aria-hidden='true'></em>
+                                    </a>
+                                    <a href='https://twitter.com/home?status=".api_get_path(WEB_PATH)."badge/".$oneSkill['id']."/user/".$userId."' target='_new'>
+                                        <em class='fa fa-twitter-square fa-3x text-light' aria-hidden='true'></em>
+                                    </a>
+                                </div>
+                            </div>
+                        ";
+                    }
+                    $badgeLink .= "
+                        <div class='panel panel-default'>
+                            <div class='panel-body'>
+                                <h3 class='text-center'>".get_lang('AdditionallyYouHaveObtainedTheFollowingSkills')."</h3>
+                                $skillList
+                            </div>
+                        </div>
+                    ";
+                }
+
+                $documentInfo = DocumentManager::get_document_data_by_id(
+                    $id,
+                    $courseCode,
+                    true,
+                    $sessionId
+                );
+
+                $finalItemTemplate = file_get_contents($documentInfo['absolute_path']);
+
+                $finalItemTemplate = str_replace('((certificate))', $downloadCertificateLink, $finalItemTemplate);
+                $finalItemTemplate = str_replace('((skill))', $badgeLink, $finalItemTemplate);
+            } else {
+                Display::display_warning_message(get_lang('LearnpathPrereqNotCompleted'));
+                $finalItemTemplate = '';
+            }
+
+            $currentScore = Category::getCurrentScore($userId, $categoryId, $courseCode, $sessionId, true);
+            Category::registerCurrentScore($currentScore, $userId, $categoryId);
+        }
+    }
+}
+
+// Instance a new template : No page tittle, No header, No footer
+$tpl = new Template(null, false, false);
+$tpl->assign('content', $finalItemTemplate);
+$tpl->display_one_col_template();
+

+ 3 - 0
main/template/default/user_portal/index.tpl

@@ -0,0 +1,3 @@
+{% for item in items %}
+    {{ item }}
+{% endfor %}

+ 5 - 0
main/template/default/user_portal/index_grid.tpl

@@ -0,0 +1,5 @@
+{% for item in items %}
+    <div class="well">
+        {{ item }}
+    </div>
+{% endfor %}

+ 65 - 65
main/template/default/user_portal/session.tpl

@@ -21,83 +21,83 @@
     {% endif %}
 
     <div class="sessions panel-body">
-        {% if session.show_simple_session_info %}
-            <div class="row">
-                <div class="col-md-7">
-                    <h3>
-                        {{ session.title ~ session.notifications }}
-                    </h3>
+    {% if session.show_simple_session_info %}
+        <div class="row">
+            <div class="col-md-7">
+                <h3>
+                    {{ session.title ~ session.notifications }}
+                </h3>
 
-                    {% if session.show_description %}
-                        <div>
-                            {{ session.description }}
-                        </div>
-                    {% endif %}
+                {% if session.show_description %}
+                    <div>
+                        {{ session.description }}
+                    </div>
+                {% endif %}
 
-                    {% if session.subtitle %}
-                        <small>{{ session.subtitle }}</small>
-                    {% endif %}
+                {% if session.subtitle %}
+                    <small>{{ session.subtitle }}</small>
+                {% endif %}
 
-                    {% if session.teachers %}
-                        <h5 class="teacher-name">{{ "teacher.png"|icon(16) ~ session.teachers }}</h5>
-                    {% endif %}
+                {% if session.teachers %}
+                    <h5 class="teacher-name">{{ "teacher.png"|icon(16) ~ session.teachers }}</h5>
+                {% endif %}
 
-                    {% if session.coaches %}
-                        <h5 class="teacher-name">{{ "teacher.png"|icon(16) ~ session.coaches }}</h5>
-                    {% endif %}
-                </div>
+                {% if session.coaches %}
+                    <h5 class="teacher-name">{{ "teacher.png"|icon(16) ~ session.coaches }}</h5>
+                {% endif %}
+            </div>
 
-                {% if session.show_actions %}
-                    <div class="col-md-5 text-right">
-                        <a href="{{ _p.web_main ~ "session/resume_session.php?id_session=" ~ session.id }}">
-                            <img src="{{ "edit.png"|icon(22) }}" width="22" height="22" alt="{{ "Edit"|get_lang }}" title="{{ "Edit"|get_lang }}">
-                        </a>
+            {% if session.show_actions %}
+                <div class="col-md-5 text-right">
+                    <a href="{{ _p.web_main ~ "session/resume_session.php?id_session=" ~ session.id }}">
+                        <img src="{{ "edit.png"|icon(22) }}" width="22" height="22" alt="{{ "Edit"|get_lang }}" title="{{ "Edit"|get_lang }}">
+                    </a>
+                </div>
+            {% endif %}
+        </div>
+    {% else %}
+        <div class="row">
+            <div class="col-md-12">
+                {% if session.subtitle %}
+                    <div class="subtitle-session">
+                        <em class="fa fa-clock-o"></em> {{ session.subtitle }}
                     </div>
                 {% endif %}
-            </div>
-        {% else %}
-            <div class="row">
-                <div class="col-md-12">
-                    {% if session.subtitle %}
-                        <div class="subtitle-session">
-                            <em class="fa fa-clock-o"></em> {{ session.subtitle }}
-                        </div>
-                    {% endif %}
-                    {% if session.show_description %}
-                        <div class="description-session">
-                            {{ session.description }}
-                        </div>
-                    {% endif %}
-                    <div class="sessions-items">
-                        {% for item in session.courses %}
-                            <div class="row">
-                                <div class="col-md-2">
-                                    {% if item.link %}
-                                        <a href="{{ item.link }}" class="thumbnail">{{ item.icon }}</a>
-                                    {% else %}
-                                        {{ item.icon }}
-                                    {% endif %}
-                                </div>
-                                <div class="col-md-10">
-                                    {{ item.title }}
+                {% if session.show_description %}
+                    <div class="description-session">
+                        {{ session.description }}
+                    </div>
+                {% endif %}
+                <div class="sessions-items">
+                    {% for item in session.courses %}
+                        <div class="row">
+                            <div class="col-md-2">
+                                {% if item.link %}
+                                    <a href="{{ item.link }}" class="thumbnail">{{ item.icon }}</a>
+                                {% else %}
+                                    {{ item.icon }}
+                                {% endif %}
+                            </div>
+                            <div class="col-md-10">
+                                {{ item.title }}
 
-                                    {% if item.coaches|length > 0 %}
-                                        <img src="{{ 'teacher.png'|icon(16) }}" width="16" height="16">
+                                {% if item.coaches|length > 0 %}
+                                    <img src="{{ 'teacher.png'|icon(16) }}" width="16" height="16">
 
-                                        {% for coach in item.coaches %}
-                                            {{ loop.index > 1 ? ' | ' }}
+                                    {% for coach in item.coaches %}
+                                        {{ loop.index > 1 ? ' | ' }}
 
-                                            <a href="{{ _p.web_ajax ~ 'user_manager.ajax.php?' ~ {'a': 'get_user_popup', 'user_id': coach.user_id}|url_encode() }}" data-title="{{ coach.full_name }}" class="ajax">
-                                                {{ coach.full_name }}
-                                            </a>
-                                        {% endfor %}
-                                    {% endif %}
-                                </div>
+                                        <a href="{{ _p.web_ajax ~ 'user_manager.ajax.php?' ~ {'a': 'get_user_popup', 'user_id': coach.user_id}|url_encode() }}" data-title="{{ coach.full_name }}" class="ajax">
+                                            {{ coach.full_name }}
+                                        </a>
+                                    {% endfor %}
+                                {% endif %}
                             </div>
-                        {% endfor %}
-                    </div>
+                        </div>
+                    {% endfor %}
                 </div>
             </div>
-        {% endif %}
+        </div>
+    {% endif %}
     </div>
 </div>

+ 1 - 0
main/user/user.php

@@ -445,6 +445,7 @@ if (api_is_allowed_to_edit(null, true)) {
     }
 }
 
+// $is_allowed_in_course is first defined in local.inc.php
 if (!$is_allowed_in_course) {
     api_not_allowed(true);
 }

+ 1 - 1
main/work/work.lib.php

@@ -2069,7 +2069,7 @@ function get_work_user_list(
                 $work_date = api_convert_and_format_date($work['sent_date']);
 
                 $work['sent_date_from_db'] = $work['sent_date'];
-                $work['sent_date'] = '<div class="date-time">' . date_to_str_ago(api_get_local_time($work['sent_date'])) . ' ' . $add_string . ' ' . $work_date . '</div>';
+                $work['sent_date'] = '<div class="date-time">' . date_to_str_ago($work['sent_date']). ' ' . $add_string . ' ' . $work_date . '</div>';
 
                 // Actions.
                 $correction = '';

+ 0 - 2
plugin/ticket/config.install.php

@@ -8,7 +8,5 @@
  */
 
 require_once '../../main/inc/global.inc.php';
-require_once api_get_path(LIBRARY_PATH).'plugin.class.php';
-
 require_once 'src/ticket.class.php';
 require_once 'src/ticket_plugin.class.php';

+ 2 - 0
plugin/ticket/config.php

@@ -4,6 +4,7 @@
 /**
  * @package chamilo.plugin.ticket
  */
+
 /* Tables names constants */
 define('PLUGIN_NAME', 'ticket');
 define('TABLE_TICKET_ASSIGNED_LOG', 'plugin_ticket_assigned_log');
@@ -13,6 +14,7 @@ define('TABLE_TICKET_PRIORITY', 'plugin_ticket_priority');
 define('TABLE_TICKET_PROJECT', 'plugin_ticket_project');
 define('TABLE_TICKET_STATUS', 'plugin_ticket_status');
 define('TABLE_TICKET_TICKET', 'plugin_ticket_ticket');
+define('TABLE_TICKET_CATEGORY_REL_USER', 'plugin_ticket_category_rel_user');
 define('TABLE_TICKET_MESSAGE_ATTACHMENTS', 'plugin_ticket_message_attachments');
 
 /* Ticket status constants */

+ 14 - 4
plugin/ticket/database.php

@@ -1,4 +1,6 @@
 <?php
+/* For licensing terms, see /license.txt */
+
 /**
  * Contains the SQL for the tickets management plugin database structure
  */
@@ -42,6 +44,7 @@ $categoRow = array(
     $objPlugin->get_lang('VirtualCampus') => $objPlugin->get_lang('TicketsAboutVirtualCampus'),
     $objPlugin->get_lang('OnlineEvaluation') => $objPlugin->get_lang('TicketsAboutOnlineEvaluation')
 );
+
 $i = 1;
 foreach ($categoRow as $category => $description) {
     //Online evaluation requires a course
@@ -67,7 +70,7 @@ foreach ($categoRow as $category => $description) {
     Database::insert($table, $attributes);
     $i++;
 }
-//END default categories
+
 $table = Database::get_main_table(TABLE_TICKET_MESSAGE);
 $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
         id int UNSIGNED NOT NULL AUTO_INCREMENT,
@@ -117,6 +120,7 @@ $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
         sys_lastedit_datetime datetime DEFAULT NULL,
         PRIMARY KEY (id))";
 Database::query($sql);
+
 //Default Priorities
 $defaultPriorities = array(
     'NRM' => $objPlugin->get_lang('PriorityNormal'),
@@ -134,7 +138,6 @@ foreach ($defaultPriorities as $pId => $priority) {
     Database::insert($table, $attributes);
     $i++;
 }
-//End
 
 $table = Database::get_main_table(TABLE_TICKET_PROJECT);
 $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
@@ -150,6 +153,7 @@ $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
         sys_lastedit_datetime datetime DEFAULT NULL,
         PRIMARY KEY (id))";
 Database::query($sql);
+
 //Default Project Table Ticket
 $attributes = array(
     'id' => 1,
@@ -157,7 +161,6 @@ $attributes = array(
     'name' => 'Ticket System'
 );
 Database::insert($table, $attributes);
-//END
 
 //STATUS
 $table = Database::get_main_table(TABLE_TICKET_STATUS);
@@ -168,6 +171,7 @@ $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
         description varchar(255) DEFAULT NULL,
         PRIMARY KEY (id))";
 Database::query($sql);
+
 //Default status
 $defaultStatus = array(
     'NAT' => $objPlugin->get_lang('StatusNew'),
@@ -187,7 +191,6 @@ foreach ($defaultStatus as $abr => $status) {
     Database::insert($table, $attributes);
     $i ++;
 }
-//END
 
 $table = Database::get_main_table(TABLE_TICKET_TICKET);
 $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
@@ -217,6 +220,13 @@ $sql = "CREATE TABLE IF NOT EXISTS ".$table." (
         KEY FK_ticket_category (project_id,category_id))";
 Database::query($sql);
 
+$table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
+$sql = "CREATE TABLE IF NOT EXISTS ".$table." (
+        id int UNSIGNED PRIMARY KEY NOT NULL AUTO_INCREMENT,
+        category_id INT NOT NULL,
+        user_id INT NOT NULL
+)";
+
 //Menu main tabs
 $rsTab = $objPlugin->addTab('Ticket', 'plugin/ticket/src/myticket.php');
 

+ 1 - 1
plugin/ticket/install.php

@@ -7,5 +7,5 @@
  * @package chamilo.plugin.ticket
  */
 
-require_once dirname(__FILE__).'/config.php';
+require_once __DIR__.'/config.php';
 TicketPlugin::create()->install();

+ 13 - 0
plugin/ticket/lang/english.php

@@ -83,5 +83,18 @@ $strings['AreYouSure'] = "Are you sure?";
 $strings['allow_student_add'] = "Allow students to generate tickets";
 $strings['PleaseBeforeRegisterATicketSelectOneUser'] = "Please select a user before you register a ticket.";
 $strings['RequestConfirmation'] = "Request confirmation";
+
 $strings['TicketUpdated'] = "Ticket updated";
 $strings['TicketClosed'] = "Ticket closed";
+
+$strings['TicketXCreated'] = "Ticket <b>%s</b>created";
+$strings['allow_category_edition'] = "Allow category edition";
+$strings['warn_admin_no_user_in_category'] = "Warn admin if category doesn't have users related";
+$strings['send_warning_to_all_admins'] = "Send warning to all admins, if category doesn't have users related";
+$strings['WarningCategoryXDoesntHaveUsers'] = "Warning: The category '%s' doesn't have users assigned";
+$strings['TicketInformation'] = 'Ticket information';
+$strings['CategoryWithNoUserNotificationSentToAdmins'] = 'Category <b>%s</b> with no user sent to all administrators.';
+$strings['TicketXAssignedToUserX'] = 'Ticket <b>#%s</b> assigned to user <b>%s</b>';
+$strings['TicketXCreatedWithNoCategory'] = 'Ticket <b>#%s</b> created with no category';
+
+

+ 1 - 1
plugin/ticket/plugin.php

@@ -4,5 +4,5 @@
 /**
  * @package chamilo.plugin.ticket
  */
-require_once dirname(__FILE__).'/config.php';
+require_once __DIR__.'/config.php';
 $plugin_info = TicketPlugin::create()->get_info();

+ 3 - 1
plugin/ticket/src/assign_tickets.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  *
  * @package chamilo.plugin.ticket
@@ -64,7 +65,8 @@ while ($row = Database::fetch_assoc($result_forum)) {
 echo '</select></div><div>';
 echo '<div class="row">
         <div class="formw">
-        <button class="save" name="edit" type="button" value="' . get_lang('Edit') . '" onClick="save(' . "$id" . ');">' . get_lang('Edit') . '</button>
+        <button class="save" name="edit" type="button" value="' . get_lang('Edit') . '" onClick="save(' . "$id" . ');">' .
+            get_lang('Edit') . '</button>
         </div>
     </div>';
 echo '</form>';

+ 152 - 0
plugin/ticket/src/categories.php

@@ -0,0 +1,152 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ * This script is the Tickets plugin main entry point
+ * @package chamilo.plugin.ticket
+ */
+
+$cidReset = true;
+// needed in order to load the plugin lang variables
+$course_plugin = 'ticket';
+require_once '../config.php';
+
+$plugin = TicketPlugin::create();
+
+api_protect_admin_script(true);
+
+$tool_name = $plugin->get_lang('LastEdit');
+
+
+$libPath = api_get_path(LIBRARY_PATH);
+$webLibPath = api_get_path(WEB_LIBRARY_PATH);
+
+$this_section = 'tickets';
+unset($_SESSION['this_section']);
+
+$table = new SortableTable(
+    'TicketCategories',
+    array('TicketManager', 'getCategoriesCount'),
+    array('TicketManager', 'getCategories'),
+    1
+);
+
+if ($table->per_page == 0) {
+    $table->per_page = 20;
+}
+
+$formToString = '';
+$id = isset($_GET['id']) ? intval($_GET['id']) : 0;
+
+if (isset($_GET['action'])) {
+    global $table;
+    $action = $_GET['action'];
+    switch ($action) {
+        case 'delete':
+            TicketManager::deleteCategory($id);
+
+            Display::addFlash(Display::return_message(get_lang('Deleted')));
+            header("Location: ".api_get_self());
+            break;
+        case 'add':
+            $url = api_get_self().'?action=add';
+            $form = TicketManager::getCategoryForm($url);
+            $formToString = $form->returnForm();
+            if ($form->validate()) {
+                $values =$form->getSubmitValues();
+
+                $params = [
+                    'name' => $values['name'],
+                    'description' => $values['description'],
+                    'total_tickets' => 0,
+                    'sys_insert_user_id' => api_get_user_id(),
+                    'sys_insert_datetime' => api_get_utc_datetime()
+                ];
+                TicketManager::addCategory($params);
+
+                Display::addFlash(Display::return_message(get_lang('Added')));
+
+                header("Location: ".api_get_self());
+                exit;
+            }
+            break;
+        case 'edit':
+            $url = api_get_self().'?action=edit&id='.$id;
+            $form = TicketManager::getCategoryForm($url);
+
+            $cat = TicketManager::getCategory($_GET['id']);
+            $form->setDefaults($cat);
+            $formToString = $form->returnForm();
+            if ($form->validate()) {
+                $values =$form->getSubmitValues();
+
+                $params = [
+                    'name' => $values['name'],
+                    'description' => $values['description'],
+                    'sys_lastedit_datetime' => api_get_utc_datetime(),
+                    'sys_lastedit_user_id' => api_get_user_id()
+                ];
+                $cat = TicketManager::updateCategory($_GET['id'], $params);
+                Display::addFlash(Display::return_message(get_lang('Updated')));
+                header("Location: ".api_get_self());
+                exit;
+            }
+            break;
+        default:
+            break;
+    }
+}
+
+$user_id = api_get_user_id();
+$isAdmin = api_is_platform_admin();
+
+/**
+ * Build the modify-column of the table
+ * @param   int     The user id
+ * @param   string  URL params to add to table links
+ * @param   array   Row of elements to alter
+ * @return string Some HTML-code with modify-buttons
+ */
+function modify_filter($id, $params, $row)
+{
+    $result = Display::url(
+        Display::return_icon('edit.png', get_lang('Edit')),
+        "categories.php?action=edit&id={$row['id']}"
+    );
+
+    $result .= Display::url(
+        Display::return_icon('user.png', get_lang('AssignUser')),
+        "categories_add_user.php?id={$row['id']}"
+    );
+
+    $result .= Display::url(
+        Display::return_icon('delete.png', get_lang('Delete')),
+        "categories.php?action=delete&id={$row['id']}"
+    );
+
+	return $result;
+}
+
+$table->set_header(0, '', false);
+$table->set_header(1, $plugin->get_lang('Title'), false);
+$table->set_header(2, get_lang('Description'), true, array("style" => "width:200px"));
+$table->set_header(3, $plugin->get_lang('TotalTickets'), false);
+$table->set_header(4, get_lang('Actions'), true);
+$table->set_column_filter(4, 'modify_filter');
+
+$interbreadcrumb[] = array('url' => 'myticket.php', 'name' => $plugin->get_lang('MyTickets'));
+
+Display::display_header($plugin->get_lang('Categories'));
+
+$items = [
+    [
+        'url' => 'categories.php?action=add',
+        'content' => Display::return_icon('new_folder.png', null, null, ICON_SIZE_MEDIUM),
+    ]
+];
+
+echo Display::actions($items);
+echo $formToString;
+echo $table->return_table();
+
+Display::display_footer();

+ 52 - 0
plugin/ticket/src/categories_add_user.php

@@ -0,0 +1,52 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+/**
+ *
+ * @package chamilo.plugin.ticket
+ */
+$cidReset = true;
+require_once '../config.php';
+$plugin = TicketPlugin::create();
+
+api_protect_admin_script(true);
+
+$categoryId = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : 0;
+if (empty($categoryId)) {
+    api_not_allowed(true);
+}
+
+$form = new FormValidator('edit', 'post', api_get_self().'?id='.$categoryId);
+$users = UserManager::get_user_list([], ['firstname']);
+$users = array_column($users, 'complete_name', 'user_id');
+
+$form->addElement(
+    'advmultiselect',
+    'users',
+    get_lang('Users'),
+    $users,
+    'style="width: 280px;"'
+);
+
+$usersAdded = TicketManager::getUsersInCategory($categoryId);
+if (!empty($usersAdded)) {
+    $usersAdded = array_column($usersAdded, 'user_id');
+}
+
+$form->setDefaults(['users' => $usersAdded]);
+// submit button
+$form->addButtonSave(get_lang('Save'));
+
+if ($form->validate()) {
+    $values = $form->exportValues();
+    TicketManager::deleteAllUserInCategory($categoryId);
+    TicketManager::addUsersToCategory($categoryId, $values['users']);
+    Display::addFlash(Display::return_message(get_lang('Updated')));
+    header("Location: ".api_get_self()."?id=".$categoryId);
+    exit;
+}
+
+$interbreadcrumb[] = array('url' => 'myticket.php', 'name' => $plugin->get_lang('MyTickets'));
+$interbreadcrumb[] = array('url' => 'categories.php', 'name' => get_lang('Categories'));
+Display::display_header();
+$form->display();

+ 1 - 3
plugin/ticket/src/course_user_list.php

@@ -18,8 +18,6 @@ foreach ($coursesList as $key => $course) {
     $courseInfo = CourseManager::get_course_information($course['code']);
     $arrCourseList[$courseInfo['code']] = $courseInfo['title'];
 }
-//End Course List
-
 
 $userLabel = Display::tag('label', get_lang('User'), array('class' => 'control-label'));
 $personName = api_get_person_name($userInfo['firstname'], $userInfo['lastname']);
@@ -33,13 +31,13 @@ $userInput = Display::tag(
     )
 );
 $userControl = Display::div($userInput, array('class' => 'controls'));
-
 $courseLabel = Display::tag('label', get_lang('Course'), array('class' => 'control-label'));
 $courseSelect = Display::select('course_id', $arrCourseList, 0, array(), false);
 $courseControl = Display::div($courseSelect, array('class' => 'controls'));
 
 $userDiv = Display::div($userLabel . " " . $userControl, array('class' => 'control-group'));
 $courseDiv = Display::div($courseLabel . " " . $courseControl, array('class' => 'control-group'));
+
 echo $userDiv;
 echo $courseDiv;
 

+ 17 - 8
plugin/ticket/src/myticket.php

@@ -12,8 +12,8 @@ $course_plugin = 'ticket';
 require_once '../config.php';
 
 $plugin = TicketPlugin::create();
-$tool_name = $plugin->get_lang('LastEdit');
 
+$tool_name = $plugin->get_lang('LastEdit');
 api_block_anonymous_users();
 
 $libPath = api_get_path(LIBRARY_PATH);
@@ -194,7 +194,8 @@ if (isset($_GET['action'])) {
             break;
     }
 }
-//$nameTools = api_xml_http_response_encode($plugin->get_lang('MyTickets'));
+
+// $nameTools = api_xml_http_response_encode($plugin->get_lang('MyTickets'));
 $user_id = api_get_user_id();
 $isAdmin = api_is_platform_admin();
 
@@ -288,12 +289,22 @@ if ($isAdmin) {
 
     echo '<div class="actions" >';
     if (api_is_platform_admin()) {
-        echo '<span class="fleft">' .
+        echo '<span class="left">' .
                 '<a href="' . api_get_path(WEB_PLUGIN_PATH) . 'ticket/src/new_ticket.php">' .
                     Display::return_icon('add.png', $plugin->get_lang('TckNew'), '', '32') . '</a>' .
                 '<a href="' . api_get_self() . '?action=export' . $get_parameter . $get_parameter2 . '">' .
-                    Display::return_icon('export_excel.png', get_lang('Export'), '', '32') . '</a>' .
-             '</span>';
+                    Display::return_icon('export_excel.png', get_lang('Export'), '', '32') . '</a>';
+
+
+        if ($plugin->get('allow_category_edition')) {
+            echo Display::url(
+                Display::return_icon('folder_document.gif'),
+                api_get_path(WEB_PLUGIN_PATH) . 'ticket/src/categories.php'
+            );
+        }
+
+        echo '</span>';
+
     }
     $form->display();
     echo '</div>';
@@ -392,7 +403,6 @@ if ($isAdmin) {
     }
 }
 
-
 if ($isAdmin) {
     $table->set_header(0, $plugin->get_lang('TicketNum'), true);
     $table->set_header(1, $plugin->get_lang('Date'), true);
@@ -403,7 +413,6 @@ if ($isAdmin) {
     $table->set_header(6, $plugin->get_lang('Status'), true);
     $table->set_header(7, $plugin->get_lang('Message'), true);
     $table->set_header(8, get_lang('Actions'), true);
-    $table->set_header(9, get_lang('Description'), true, array("style" => "width:200px"));
 } else {
     echo '<center><h1>' . $plugin->get_lang('MyTickets') . '</h1></center>';
     echo '<center><p>' . $plugin->get_lang('MsgWelcome') . '</p></center>';
@@ -416,7 +425,7 @@ if ($isAdmin) {
     $table->set_header(3, $plugin->get_lang('Category'));
     $table->set_header(4, $plugin->get_lang('Status'), false);
     $table->set_header(5, get_lang('Actions'), false);
-}
+}       
 
 $table->display();
 Display::display_footer();

+ 72 - 72
plugin/ticket/src/new_ticket.php

@@ -18,13 +18,6 @@ if (!api_is_platform_admin() &&
 
 api_block_anonymous_users();
 
-$scrollTop = '';
-if (api_is_platform_admin()) {
-    $scrollTol = '$("html, body").animate({
-                    scrollTop: $(".divTicket").offset().top - 20
-                  }, "slow");';
-}
-
 $htmlHeadXtra[] = '
 <script>
 function load_course_list (div_course, my_user_id, user_email) {
@@ -38,7 +31,6 @@ function load_course_list (div_course, my_user_id, user_email) {
             $("#user_id_request").val(my_user_id);
             $("#personal_email").val(user_email);
             $("#btnsubmit").attr("disabled", false);
-            ' . $scrollTol . '
         }
     });
 }
@@ -73,10 +65,7 @@ function validate() {
     document.getElementById("content").value= fckEditor1val;
     var selected = document.getElementById("category_id").selectedIndex;
     var id = document.getElementById("category_id").options[selected].value;
-    if (document.getElementById("user_id_request").value == "") {
-        alert("' . $plugin->get_lang("ValidUser") . '");
-        return false;
-    } else if(id == 0) {
+    if (id == 0) {
         alert("' . $plugin->get_lang("ValidType") . '");
         return false;
     } else if(document.getElementById("subject").value == "") {
@@ -86,8 +75,10 @@ function validate() {
         alert("' . $plugin->get_lang("ValidCourse") . '");
         return false;
     } else if(id != "CUR" && parseInt(course_required[id]) != 1  && !re.test(document.getElementById("personal_email").value)) {
-        alert("' . $plugin->get_lang("ValidEmail") . '");
-        return false;
+        if (document.getElementById("personal_email").value != "") {
+            alert("' . $plugin->get_lang("ValidEmail") . '");
+            return false;
+        }
     } else if(fckEditor1val == "") {
         alert("' . $plugin->get_lang("ValidMessage") . '");
         return false;
@@ -132,7 +123,7 @@ function add_image_form() {
     });
 
     img_remove = $("<img/>", {
-        src: "' . api_get_path(WEB_CODE_PATH) . 'img/delete.gif"
+        src: "' . Display::returnIconPath('delete.png') . '"
     });
 
     new_filepath_id = $("#filepath_" + counter_image);
@@ -200,7 +191,6 @@ function js_array($array, $name, $key)
 function show_form_send_ticket()
 {
     global $types, $plugin;
-    echo '<div class="divTicket">';
 
     // Category List
     $categoryList = array();
@@ -263,8 +253,7 @@ function show_form_send_ticket()
         "",
         array(
             'enctype' => 'multipart/form-data',
-            'onsubmit' => 'return validate()',
-            'class' => 'span8 offset1 form-horizontal'
+            'onsubmit' => 'return validate()'
         )
     );
 
@@ -318,70 +307,62 @@ function show_form_send_ticket()
     );
 
     $form->addElement(
-        'html',
-        Display::div(
-            '',
+        'text',
+        'subject',
+        get_lang('Subject'),
             array(
-                'id' => 'user_request'
-            )
+            'id' => 'subject'
         )
     );
 
-    $form->addElement(
-        'select',
-        'status_id',
-        get_lang('Status'),
-        $statusList,
-        $statusAttributes
+    $form->addHtmlEditor(
+        'content',
+        get_lang('Message'),
+        false,
+        false,
+        array(
+            'ToolbarSet' => 'Profile',
+            'Height' => '250'
+        )
     );
 
-    $form->addElement(
-        'select',
-        'source_id',
-        $plugin->get_lang('Source'),
-        $sourceList,
-        $sourceAttributes
-    );
 
     $form->addElement(
-        'text',
-        'subject',
-        get_lang('Subject'),
-        array(
-            'id' => 'subject',
-            'style' => 'width: 550px;'
-        )
+            'SelectAjax',
+            'user_id',
+            get_lang('Assign'),
+            null,
+            ['url' => api_get_path(WEB_AJAX_PATH).'user_manager.ajax.php?a=get_user_like']
     );
+    //}
+
+    
 
     $form->addElement(
         'text',
         'personal_email',
         $plugin->get_lang('PersonalEmail'),
         array(
-            'id' => 'personal_email',
-            'style' => 'width: 550px;'
+            'id' => 'personal_email'
         )
     );
 
-    $form->addHtmlEditor(
-        'content',
-        get_lang('Message'),
-        false,
-        false,
+
+    $form->addLabel('',
+        Display::div(
+            '',
         array(
-            'ToolbarSet' => 'Profile',
-            'Width' => '600',
-            'Height' => '250'
+                'id' => 'user_request'
+            )
         )
     );
 
     $form->addElement(
-        'text',
-        'phone',
-        get_lang('Phone') . ' (' . $plugin->get_lang('Optional') . ')',
-        array(
-            'id' => 'phone'
-        )
+        'select',
+        'status_id',
+        get_lang('Status'),
+        $statusList,
+        $statusAttributes
     );
 
     $form->addElement(
@@ -395,17 +376,33 @@ function show_form_send_ticket()
         )
     );
 
-    $form->addElement('html', '<span id="filepaths">');
-    $form->addElement('html', '<div id="filepath_1">');
+    $form->addElement(
+        'select',
+        'source_id',
+        $plugin->get_lang('Source'),
+        $sourceList,
+        $sourceAttributes
+    );
+
+    $form->addElement(
+        'text',
+        'phone',
+        get_lang('Phone') . ' (' . $plugin->get_lang('Optional') . ')',
+        array(
+            'id' => 'phone'
+        )
+    );
+
+
     $form->addElement('file', 'attach_1', get_lang('FilesAttachment'));
-    $form->addElement('html', '</div>');
-    $form->addElement('html', '</span>');
+    $form->addLabel('', '<span id="filepaths"><div id="filepath_1"></div></span>');
 
-    $form->addElement('html', '<div class="controls">');
-    $form->addElement('html', '<span id="link-more-attach" >');
-    $form->addElement('html', '<span class="label label-info" onclick="return add_image_form()">' . get_lang('AddOneMoreFile') . '</span>');
-    $form->addElement('html', '</span>');
-    $form->addElement('html', '(' . sprintf(get_lang('MaximunFileSizeX'), format_file_size(api_get_setting('message.message_max_upload_filesize'))) . ')');
+    $form->addLabel('',
+        '<span id="link-more-attach">
+         <span class="btn btn-success" onclick="return add_image_form()">' . get_lang('AddOneMoreFile') . '</span>
+         </span>
+         ('.sprintf(get_lang('MaximunFileSizeX'), format_file_size(api_get_setting('message_max_upload_filesize'))).')
+    ');
 
     $form->addElement('html', '<br/>');
     $form->addElement(
@@ -415,7 +412,7 @@ function show_form_send_ticket()
         null,
         null,
         null,
-        'save',
+        'btn btn-primary',
         array(
             'id' => 'btnsubmit'
         )
@@ -435,18 +432,19 @@ function save_ticket()
     if ($_POST['phone'] != "") {
         $content .= '<p style="color:red">&nbsp;' . get_lang('Phone') . ': ' . Security::remove_XSS($_POST['phone']). '</p>';
     }
-    $course_id = $_POST['course_id'];
+    $course_id = isset($_POST['course_id']) ? $_POST['course_id'] : 0;
     $project_id = $_POST['project_id'];
     $subject = $_POST['subject'];
     $other_area = (int) $_POST['other_area'];
     $email = $_POST['email'];
     $personal_email = $_POST['personal_email'];
     $source = $_POST['source_id'];
-    $user_id = $_POST['user_id_request'];
+    $user_id = isset($_POST['user_id']) ? $_POST['user_id'] : 0;
     $priority = $_POST['priority_id'];
     $status = $_POST['status_id'];
     $file_attachments = $_FILES;
     $responsible = (api_is_platform_admin() ? api_get_user_id() : 0);
+  
     if (TicketManager::insert_new_ticket(
         $category_id,
         $course_id,
@@ -581,6 +579,8 @@ function get_user_data($from, $number_of_items, $column, $direction)
 if (!isset($_POST['compose'])) {
     if (api_is_platform_admin()) {
         Display::display_header(get_lang('ComposeMessage'));
+
+        /*
         $message = $plugin->get_lang('PleaseBeforeRegisterATicketSelectOneUser');
         Display::display_warning_message($message);
         echo '
@@ -617,7 +617,7 @@ if (!isset($_POST['compose'])) {
         $table->set_header(5, get_lang('Email'));
         $table->set_header(6, get_lang('Action'));
         $table->display();
-        echo '</div>';
+        echo '</div>';*/
     } else {
         $userInfo = api_get_user_info();
         $htmlHeadXtra[] = "

+ 6 - 2
plugin/ticket/src/report.php

@@ -4,6 +4,7 @@
 /**
  * @package chamilo.plugin.ticket
  */
+
 $language_file = array('trad4all');
 $cidReset = true;
 require_once '../config.php';
@@ -14,7 +15,7 @@ api_block_anonymous_users();
 if (!api_is_allowed_to_edit()) {
     api_not_allowed();
 }
-//$nameTools = api_xml_http_response_encode(get_lang('Soporte Virtual'));
+
 $this_section = 'Reports';
 unset($_SESSION['this_section']);
 
@@ -24,7 +25,8 @@ $(document).ready(function(){
     $( "#keyword_start_date_start" ).datepicker({ dateFormat: ' . "'yy-mm-dd'" . ' });
     $( "#keyword_start_date_end" ).datepicker({ dateFormat: ' . "'yy-mm-dd'" . ' });
 });
-function validate(){
+
+function validate() {
     if( $("#keyword_start_date_start").val() != "" &&  $("#keyword_start_date_end").val() != ""){
         datestart = $("#keyword_start_date_start").val();
         dateend = $("#keyword_start_date_end").val();
@@ -35,6 +37,7 @@ function validate(){
         }
     }
 }
+
 function load_course_list (div_course,my_user_id) {
 	 $.ajax({
 		contentType: "application/x-www-form-urlencoded",
@@ -84,6 +87,7 @@ $tools['quiz'] = array('id' => 'quiz', 'name' => get_lang('Quiz'));
 $tools['student_publication'] = array('id' => 'student_publication', 'name' => get_lang('Student_publication'));
 $tools['user'] = array('id' => 'user', 'name' => get_lang('User'));
 $tools['forum'] = array('id' => 'forum', 'name' => get_lang('Forum'));
+
 /**
  * Returns the escaped string.
  * @param string $s

+ 389 - 60
plugin/ticket/src/ticket.class.php

@@ -35,6 +35,173 @@ class TicketManager
         return $types;
     }
 
+    /**
+     * @param $from
+     * @param $numberItems
+     * @param $column
+     * @param $direction
+     * @return array
+     */
+    public static function getCategories($from, $numberItems, $column, $direction)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+        $sql = "SELECT id, name, description, total_tickets
+                FROM $table";
+
+        if (!in_array($direction, array('ASC','DESC'))) {
+            $direction = 'ASC';
+        }
+        $column = intval($column);
+        $from 	= intval($from);
+        $numberItems = intval($numberItems);
+
+        //$sql .= " ORDER BY col$column $direction ";
+        $sql .= " LIMIT $from,$numberItems";
+
+        $result = Database::query($sql);
+        $types = array();
+        while ($row = Database::fetch_array($result)) {
+            $types[] = $row;
+        }
+
+        return $types;
+    }
+
+    /**
+     * @param int $id
+     * @return array|mixed
+     */
+    public static function getCategory($id)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+        $id = intval($id);
+        $sql = "SELECT id, name, description, total_tickets
+                FROM $table WHERE id = $id";
+
+        $result = Database::query($sql);
+        $category = Database::fetch_array($result);
+
+        return $category;
+    }
+
+    /**
+     * @return int
+     */
+    public static function getCategoriesCount()
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+
+        $sql = "SELECT count(id) count
+                FROM $table ";
+
+        $result = Database::query($sql);
+        $category = Database::fetch_array($result);
+
+        return $category['count'];
+    }
+
+    /**
+     * @param int $id
+     * @param array $params
+     */
+    public static function updateCategory($id, $params)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+        $id = intval($id);
+        Database::update($table, $params, ['id = ?' => $id]);
+    }
+
+    /**
+     * @param int $id
+     * @param array $params
+     */
+    public static function addCategory($params)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+        Database::insert($table, $params);
+    }
+
+    /**
+     * @param int $id
+     */
+    public static function deleteCategory($id)
+    {
+        $id = intval($id);
+
+        $table = Database::get_main_table(TABLE_TICKET_TICKET);
+        $sql = "UPDATE $table SET category_id = NULL WHERE category_id = $id";
+        Database::query($sql);
+
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY);
+        $sql = "DELETE FROM $table WHERE id = $id";
+        Database::query($sql);
+    }
+
+    /**
+     * @param int $categoryId
+     * @param array $users
+     */
+    public static function addUsersToCategory($categoryId, $users)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
+        if (empty($users) || empty($categoryId)) {
+            return false;
+        }
+
+        foreach ($users as $userId) {
+            if (self::userIsAssignedToCategory($userId, $categoryId) == false) {
+                $params = [
+                    'category_id' => $categoryId,
+                    'user_id' => $userId
+                ];
+                Database::insert($table, $params);
+            }
+        }
+    }
+
+    /**
+     * @param int $userId
+     * @param int $categoryId
+     *
+     * @return bool
+     */
+    public static function userIsAssignedToCategory($userId, $categoryId)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
+        $userId = intval($userId);
+        $categoryId = intval($categoryId);
+        $sql = "SELECT * FROM $table WHERE category_id = $categoryId AND user_id = $userId";
+        $result = Database::query($sql);
+
+        return Database::num_rows($result) > 0;
+    }
+
+    /**
+     * @param int $categoryId
+     *
+     * @return array
+     */
+    public static function getUsersInCategory($categoryId)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
+        $categoryId = intval($categoryId);
+        $sql = "SELECT * FROM $table WHERE category_id = $categoryId";
+        $result = Database::query($sql);
+
+        return Database::store_result($result);
+    }
+
+    /**
+     * @param int $categoryId
+     */
+    public static function deleteAllUserInCategory($categoryId)
+    {
+        $table = Database::get_main_table(TABLE_TICKET_CATEGORY_REL_USER);
+        $categoryId = intval($categoryId);
+        $sql = "DELETE FROM $table WHERE category_id = $categoryId";
+        Database::query($sql);
+    }
+
     /**
      * Get all possible tickets statuses
      * @return array
@@ -78,7 +245,7 @@ class TicketManager
         $email,
         $subject,
         $content,
-        $personalEmail = "",
+        $personalEmail = '',
         $file_attachments,
         $source = 'VRT',
         $priority = 'NRM',
@@ -91,6 +258,9 @@ class TicketManager
         $table_support_category = Database::get_main_table(
             TABLE_TICKET_CATEGORY
         );
+
+        $now = api_get_utc_datetime();
+        $user_id = api_get_user_id();
         $course_id = intval($course_id);
         $category_id = intval($category_id);
         $project_id = intval($project_id);
@@ -107,8 +277,6 @@ class TicketManager
         $personalEmail = Database::escape_string($personalEmail);
         $status = Database::escape_string($status);
 
-        $now = api_get_utc_datetime();
-        $user_id = api_get_user_id();
         if ($status == '') {
             $status = NEWTCK;
             if ($other_area > 0) {
@@ -116,10 +284,31 @@ class TicketManager
             }
         }
 
-        if ($request_user == '' && $source == 'VRT') {
+        if (!empty($category_id)) {
+            if (empty($request_user)) {
+                $usersInCategory = TicketManager::getUsersInCategory($category_id);
+                if (!empty($usersInCategory) && count($usersInCategory) > 0) {
+                    $userCategoryInfo = $usersInCategory[0];
+                    if (isset($userCategoryInfo['user_id'])) {
+                        $assigned_user = $userCategoryInfo['user_id'];
+                    }
+                }
+            }
+        }
+
+        if (empty($request_user)) {
             $request_user = $user_id;
+        }        
+        
+        $assignedUserInfo = api_get_user_info($assigned_user);
+
+        if (empty($assignedUserInfo)) {
+
+            return false;
         }
-        $sql_insert_ticket = "INSERT INTO $table_support_tickets
+
+        // insert_ticket
+        $sql = "INSERT INTO $table_support_tickets
             (
             project_id,
             category_id,
@@ -143,58 +332,173 @@ class TicketManager
             '$personalEmail',
             '$status',
             '$now',
-            $user_id,
+             $request_user,
             '$now',
-            '$user_id',
+            '$request_user',
             '$now',
             '$source'
         )";
-        Database::query($sql_insert_ticket);
+        Database::query($sql);
         $ticket_id = Database::insert_id();
 
-        if ($assigned_user != 0) {
-            self::assign_ticket_user($ticket_id, $assigned_user);
-        }
-
         if ($ticket_id != 0) {
             $ticket_code = "A" . str_pad(
                 (int) $ticket_id, 11, "0", STR_PAD_LEFT
             );
-            $sql_update_code = "UPDATE $table_support_tickets
-                                SET ticket_code = '$ticket_code'
-                                WHERE ticket_id = '$ticket_id'";
-            Database::query($sql_update_code);
+
+            if ($assigned_user != 0) {
+                self::assign_ticket_user($ticket_id, $assigned_user);
+
+                Display::addFlash(Display::return_message(
+                    sprintf(
+                        $plugin->get_lang('TicketXAssignedToUserX'),
+                        $ticket_code,
+                        $assignedUserInfo['complete_name']
+                    ),
+                    'normal',
+                    false
+                ));
+            } else {
+                Display::addFlash(Display::return_message(
+                    sprintf(
+                        $plugin->get_lang('TicketXCreated'),
+                        $ticket_code
+                    ),
+                    'normal',
+                    false
+                ));
+            }
+
+            // Update code
+            $sql = "UPDATE $table_support_tickets
+                    SET ticket_code = '$ticket_code'
+                    WHERE ticket_id = '$ticket_id'";
+            Database::query($sql);
+
             $data_files = array();
-            $sql_update_total = "UPDATE $table_support_category
-                                 SET total_tickets = total_tickets +1
-                                 WHERE category_id = '$category_id';";
-            Database::query($sql_update_total);
-            if (self::insert_message($ticket_id, $subject, $content, $file_attachments, $request_user)) {
+            // Update total
+            $sql = "UPDATE $table_support_category
+                    SET total_tickets = total_tickets +1
+                    WHERE category_id = '$category_id'";
+            Database::query($sql);
+
+            if (self::insert_message(
+                $ticket_id,
+                $subject,
+                $content,
+                $file_attachments,
+                $request_user)
+            ) {
+                $user = api_get_user_info($request_user);
+                $helpDeskMessage =
+                    '<table>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('User') . '</b></td>
+                                <td width="400px">' . $user['firstname']. ' ' . $user['lastname'] . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Username') . '</b></td>
+                                <td width="400px">' . $user['username'] . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Email') . '</b></td>
+                                <td width="400px">' . $user['email'] . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Phone') . '</b></td>
+                                <td width="400px">' . $user['phone'] . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Date') . '</b></td>
+                                <td width="400px">' . api_convert_and_format_date($now, DATE_TIME_FORMAT_LONG) . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Title') . '</b></td>
+                                <td width="400px">' . $subject . '</td>
+                            </tr>
+                            <tr>
+                                <td width="100px"><b>' . get_lang('Description') . '</b></td>
+                                <td width="400px">' . $content . '</td>
+                            </tr>
+                        </table>';
+
+                if (empty($category_id)) {
+                    if ($plugin->get('send_warning_to_all_admins')) {
+                        $warningSubject = sprintf(
+                            $plugin->get_lang('TicketXCreatedWithNoCategory'),
+                            $ticket_code
+                        );
+                        Display::addFlash(Display::return_message($warningSubject));
+
+                        $admins = UserManager::get_all_administrators();
+                        foreach ($admins as $userId => $data) {
+                            if ($data['active']) {
+                                MessageManager::send_message_simple(
+                                    $userId,
+                                    $warningSubject,
+                                    $message
+                                );
+                            }
+                        }
+                    }
+                } else {
+
+                    $categoryInfo = TicketManager::getCategory($category_id);
+                    $usersInCategory = TicketManager::getUsersInCategory($category_id);
+
+                    $message = '<h2>'.$plugin->get_lang('TicketInformation').'</h2><br />'.$helpDeskMessage;
+
+                    if ($plugin->get('warn_admin_no_user_in_category')) {
+                        $usersInCategory = TicketManager::getUsersInCategory($category_id);
+                        if (empty($usersInCategory)) {
+                            $subject = sprintf(
+                                $plugin->get_lang('WarningCategoryXDoesntHaveUsers'),
+                                $categoryInfo['name']
+                            );
+
+                            if ($plugin->get('send_warning_to_all_admins')) {
+                                Display::addFlash(Display::return_message(
+                                    sprintf(
+                                        $plugin->get_lang('CategoryWithNoUserNotificationSentToAdmins'),
+                                        $categoryInfo['name']
+                                    ),
+                                    null,
+                                    false
+                                ));
+
+                                $admins = UserManager::get_all_administrators();
+                                foreach ($admins as $userId => $data) {
+                                    if ($data['active']) {
+                                        MessageManager::send_message_simple(
+                                            $userId,
+                                            $subject,
+                                            $message
+                                        );
+                                    }
+                                }
+                            } else {
+                                Display::addFlash(Display::return_message($subject));
+                            }
+                        }
+                    }
+
+                    // Send notification to all users
+                    if (!empty($usersInCategory)) {
+                        foreach ($usersInCategory as $data) {
+                            if ($data['user_id']) {
+                                MessageManager::send_message_simple(
+                                    $data['user_id'],
+                                    $subject,
+                                    $message
+                                );
+                            }
+                        }
+                    }
+                }
+
+
                 global $data_files;
                 if ($other_area) {
-                    $user = api_get_user_info($request_user);
-                    $helpDeskMessage = '<table>
-                                            <tr>
-                                                <td width="100px"><b>' . get_lang('User') . '</b></td>
-                                                <td width="400px">' . $user['firstname']. ' ' . $user['lastname'] . '</td>
-                                            </tr>
-                                            <tr>
-                                                <td width="100px"><b>' . get_lang('Username') . '</b></td>
-                                                <td width="400px">' . $user['username'] . '</td>
-                                            </tr>
-                                            <tr>
-                                                <td width="100px"><b>' . get_lang('Date') . '</b></td>
-                                                <td width="400px">' . api_convert_and_format_date($now, DATE_TIME_FORMAT_LONG) . '</td>
-                                            </tr>
-                                            <tr>
-                                                <td width="100px"><b>' . get_lang('Topic') . '</b></td>
-                                                <td width="400px">' . $subject . '</td>
-                                            </tr>
-                                            <tr>
-                                                <td width="100px"><b>' . get_lang('Description') . '</b></td>
-                                                <td width="400px">' . $content . '</td>
-                                            </tr>
-                                        </table>';
 
                     // Send email to "other area" email
                     api_mail_html(
@@ -227,6 +531,9 @@ class TicketManager
                     );
                 }
 
+
+
+
                 return true;
             } else {
 
@@ -336,8 +643,8 @@ class TicketManager
         $obj = Database::fetch_object($result);
         $message_id = $obj->total_messages + 1;
         $now = api_get_utc_datetime();
-
-        $sql_insert_message = "INSERT INTO $table_support_messages (
+        // insert msg
+        $sql = "INSERT INTO $table_support_messages (
             ticket_id,
             message_id,
             subject,
@@ -360,16 +667,19 @@ class TicketManager
             '" . $now . "',
             '$status'
         )";
-        Database::query($sql_insert_message);
-        $sql_update_total_message = "UPDATE $table_support_tickets
-                        SET sys_lastedit_user_id ='$user_id',
-                            sys_lastedit_datetime ='$now',
-                            total_messages = (
-                                SELECT COUNT(*) as total_messages
-                                  FROM $table_support_messages
-                                  WHERE ticket_id ='$ticket_id'
-                            ) WHERE ticket_id ='$ticket_id' ";
-        Database::query($sql_update_total_message);
+        Database::query($sql);
+
+        // update_total_message
+        $sql = "UPDATE $table_support_tickets
+                SET sys_lastedit_user_id ='$user_id',
+                    sys_lastedit_datetime ='$now',
+                    total_messages = (
+                        SELECT COUNT(*) as total_messages
+                        FROM $table_support_messages
+                        WHERE ticket_id ='$ticket_id'
+                    )
+                WHERE ticket_id ='$ticket_id' ";
+        Database::query($sql);
 
         $sql_message_att_id = "SELECT COUNT(*) as total_attach
                 FROM $table_support_message_attachments
@@ -519,6 +829,7 @@ class TicketManager
                 status.name AS col7,
                 ticket.total_messages AS col8,
                 msg.message AS col9,
+                msg.subject AS subject,
                 ticket.request_user AS user_id,
                 ticket.assigned_last_user AS responsible
             FROM $table_support_tickets ticket,
@@ -634,6 +945,7 @@ class TicketManager
                 )";
             }
         }
+
         if ($keyword_unread == 'yes') {
             $sql .= " AND ticket.ticket_id IN (SELECT ticket.ticket_id
                 FROM $table_support_tickets ticket,
@@ -741,7 +1053,7 @@ class TicketManager
                 }
 
                 $ticket = array(
-                    $row['col0'],
+                    $row['col0'].' '.$row['subject'],
                     api_format_date($row['col1'], '%d/%m/%y - %I:%M:%S %p'),
                     api_format_date($row['col2'], '%d/%m/%y - %I:%M:%S %p'),
                     $row['col3'],
@@ -750,7 +1062,7 @@ class TicketManager
                     $row['col7'],
                     $row['col8'],
                     $actions,
-                    $row['col9']
+                    //$row['col9']
                 );
             } else {
                 $actions = "";
@@ -758,7 +1070,7 @@ class TicketManager
                                 'synthese_view.gif', get_lang('Info')
                         ) . '</a>&nbsp;&nbsp;';
                 $row['col0'] = Display::return_icon(
-                                $img_source, get_lang('Info')
+                        $img_source, get_lang('Info')
                         ) . '<a href="ticket_details.php?ticket_id=' . $row['col0'] . '">' . $row['ticket_code'] . '</a>';
                 $now = api_strtotime(api_get_utc_datetime());
                 $last_edit_date = api_strtotime($row['sys_lastedit_datetime']);
@@ -1009,12 +1321,15 @@ class TicketManager
         if (Database::num_rows($result) > 0) {
             while ($row = Database::fetch_assoc($result)) {
                 $row['course'] = null;
+                $row['start_date_from_db'] = $row['start_date'];
                 $row['start_date'] = api_convert_and_format_date(
                         api_get_local_time($row['start_date']), DATE_TIME_FORMAT_LONG, _api_get_timezone()
                 );
+                $row['end_date_from_db'] = $row['end_date'];
                 $row['end_date'] = api_convert_and_format_date(
                         api_get_local_time($row['end_date']), DATE_TIME_FORMAT_LONG, _api_get_timezone()
                 );
+                $row['sys_lastedit_datetime_from_db'] = $row['sys_lastedit_datetime'];
                 $row['sys_lastedit_datetime'] = api_convert_and_format_date(
                         api_get_local_time($row['sys_lastedit_datetime']), DATE_TIME_FORMAT_LONG, _api_get_timezone()
                 );
@@ -1239,6 +1554,7 @@ class TicketManager
         $sql .= "  AND ticket.project_id != '' ";
         $res = Database::query($sql);
         $obj = Database::fetch_object($res);
+
         return $obj->unread;
     }
 
@@ -1512,7 +1828,6 @@ class TicketManager
             }
         }
 
-        //$sql .= " ORDER BY col$column $direction";
         $sql .= " LIMIT $from,$number_of_items";
 
         $result = Database::query($sql);
@@ -1548,4 +1863,18 @@ class TicketManager
 
         return $tickets;
     }
+
+    /**
+     * @param string $url
+     * @return FormValidator
+     */
+    public static function getCategoryForm($url)
+    {
+        $form = new FormValidator('category', 'post', $url);
+        $form->addText('name', get_lang('Name'));
+        $form->addHtmlEditor('description', get_lang('Description'));
+        $form->addButtonUpdate(get_lang('Save'));
+
+        return $form;
+    }
 }

+ 11 - 3
plugin/ticket/src/ticket_assign_log.php

@@ -4,7 +4,9 @@
 /**
  * @package chamilo.plugin.ticket
  */
+
 require_once '../config.php';
+
 $plugin = TicketPlugin::create();
 
 $ticket_id = intval($_POST['ticket_id']);
@@ -22,9 +24,15 @@ $history = TicketManager::get_assign_log($ticket_id);
     ?>
     <?php for ($k = 0; $k < count($history); $k++) { ?>
         <tr>
-            <td width="125px"><?php echo api_convert_encoding($history[$k]['assignuser'], 'UTF-8', $charset); ?></td>
-            <td width="100px"><?php echo api_convert_encoding($history[$k]['assigned_date'], 'UTF-8', $charset); ?></td>
-            <td width="125px"><?php echo api_convert_encoding($history[$k]['insertuser'], 'UTF-8', $charset); ?></td>
+            <td width="125px">
+                <?php echo api_convert_encoding($history[$k]['assignuser'], 'UTF-8', $charset); ?>
+            </td>
+            <td width="100px">
+                <?php echo api_convert_encoding($history[$k]['assigned_date'], 'UTF-8', $charset); ?>
+            </td>
+            <td width="125px">
+                <?php echo api_convert_encoding($history[$k]['insertuser'], 'UTF-8', $charset); ?>
+            </td>
         </tr>
     <?php } ?>
 </table>

+ 79 - 71
plugin/ticket/src/ticket_details.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  *
  * @package chamilo.plugin.ticket
@@ -30,13 +31,13 @@ $(document).ready(function(){
 		width: 600,
 		modal: true,
 		buttons: {
-                    ' . get_lang('Accept') . ': function(){
-                        $("#frmResponsable").submit()
-                    },
-                    ' . ucfirst(get_lang('Close')) . ': function() {
-                        $(this).dialog("close");
-                    }
-                }
+            ' . get_lang('Accept') . ': function(){
+                $("#frmResponsable").submit()
+            },
+            ' . ucfirst(get_lang('Close')) . ': function() {
+                $(this).dialog("close");
+            }
+            }
         });
 
         $("a#assign").click(function () {
@@ -117,7 +118,7 @@ function add_image_form() {
     });
 
     img_remove = $("<img/>", {
-        src: "' . api_get_path(WEB_CODE_PATH) . 'img/delete.gif"
+        src: "' . Display::returnIconPath('delete.png').'"
     });
 
     new_filepath_id = $("#filepath_" + counter_image);
@@ -223,10 +224,16 @@ if (isset($_REQUEST['action'])) {
     }
 }
 
+$titulo = 'Ticket #' . $ticket['ticket']['ticket_code'];
+$firstMessage = is_array($ticket['messages']) ? $ticket['messages'][0] : '';
+$subTitle = '';
+if (!empty($firstMessage) && isset($firstMessage['subject'])) {
+    $subTitle = $firstMessage['subject'];
+}
+
 if (!isset($_POST['compose'])) {
-    if (isset($_POST['close'])) {
-        $_GET['ticket_id'] = $_POST['ticket_id'];
-        TicketManager::close_ticket($_GET['ticket_id'], $user_id);
+    if (isset($_REQUEST['close'])) {
+        TicketManager::close_ticket($_REQUEST['ticket_id'], $user_id);
         $ticket['ticket']['status_id'] = 'CLS';
         $ticket['ticket']['status'] = $plugin->get_lang('Closed');
     }
@@ -239,22 +246,21 @@ if (!isset($_POST['compose'])) {
     if ($ticket['ticket']['status_id'] != 'REE' && $ticket['ticket']['status_id'] != 'CLS' && $isAdmin) {
         if (intval($ticket['ticket']['assigned_last_user']) == $user_id) {
             if ($ticket['ticket']['status_id'] != 'CLS') {
-                $form_close_ticket.= '<form enctype="multipart/form-data" action="' . api_get_self() . '?ticket_id=' . $ticket['ticket']['ticket_id'] . '" method="post" name="close_ticket" id="close_ticket" >';
-                $form_close_ticket.= '<input type="hidden" name="ticket_id" value="' . $ticket['ticket']['ticket_id'] . '"/>
-                                        <button class="minus" name="close" type="submit" id="close" >' . get_lang('Close') . '</button>';
-                $form_close_ticket.= '</form>';
+                $form_close_ticket.= '<a href="' . api_get_self() . '?close=1&ticket_id=' . $ticket['ticket']['ticket_id'] . '" id="close" class="btn btn-danger" >';
+                $form_close_ticket.= get_lang('Close') . '</a>';
             }
         }
     }
-    $titulo = '<center><h1>Ticket #' . $ticket['ticket']['ticket_code'] . '</h1></center>';
+
     $img_assing = '';
     if ($isAdmin && $ticket['ticket']['status_id'] != 'CLS' && $ticket['ticket']['status_id'] != 'REE') {
         if ($ticket['ticket']['assigned_last_user'] != 0 && $ticket['ticket']['assigned_last_user'] == $user_id) {
-            $img_assing = '<a href="' . api_get_self() . '?ticket_id=' . $ticket['ticket']['ticket_id'] . '&amp;action=unassign" id="unassign">
-                            <img src="' . api_get_path(WEB_CODE_PATH) . 'img/admin_star.png"  style="height: 32px; width: 32px;" border="0" title="Unassign" align="center"/>
+            $img_assing = '<a class="btn btn-warning" href="' . api_get_self() . '?ticket_id=' . $ticket['ticket']['ticket_id'] . '&amp;action=unassign" id="unassign">
+                           '.get_lang('Unassign').'
                            </a>';
         } else {
-            $img_assing = '<a href="#" id="assign"><img src="' . api_get_path(WEB_CODE_PATH) . 'img/admin_star_na.png" style="height: 32px; width: 32px;" title="Assign" align="center"/></a>';
+            $img_assing = '<a href="#" id="assign" class="btn btn-success">'.get_lang('Assign').'</a>';
+
         }
     }
     $bold = '';
@@ -267,31 +273,43 @@ if (!isset($_POST['compose'])) {
                 }
               </style>";
     }
+    if ($isAdmin) {
+        $senderData = get_lang('AddedBy') . ' ' . $ticket['ticket']['user_url'] . ' (' . $ticket['usuario']['username'] . ').';
+    } else {
+        $senderData = get_lang('AddedBy') . ' ' . $ticket['usuario']['complete_name'] . ' (' . $ticket['usuario']['username'] . ').';
+    }
 
-    echo '<div style="margin-left:20%;margin-right:20%;">
+    echo '
 			<table width="100%" >
 				<tr>
-	              <td colspan="3" style="width:65%">' . $titulo . '</td>
-	              <td style="width: 15%">' . $img_assing . '</td>
-	              <td style="width: 15%">' . $form_close_ticket . '</td>
+	              <td colspan="3" style="width:65%">
+	              <h1>' . $titulo . ' '.$form_close_ticket.' '.$img_assing.' </h1>
+	              <h2>'.$subTitle.'</h2>
+	              <p>
+	                '.$senderData.' ' .
+                    get_lang('Created') . ' '.
+                    Display::url(
+                        date_to_str_ago($ticket['ticket']['start_date_from_db']),
+                        '#',
+                        ['title' => $ticket['ticket']['start_date'], 'class' => 'boot-tooltip']
+                    ).'. '.
+                    $plugin->get_lang('TicketUpdated').' '.
+                    Display::url(
+                        date_to_str_ago($ticket['ticket']['sys_lastedit_datetime_from_db']),
+                        '#',
+                        ['title' => $ticket['ticket']['sys_lastedit_datetime'], 'class' => 'boot-tooltip']
+                    ).'
+	              </p>
+	              </td>
 	            </tr>
-	         	<tr>
-	              <td style="width:45%;" ><p>' . get_lang('Sent') . ': ' . $ticket['ticket']['start_date'] . '</p></td>
-	              <td style="width:50px;"></td>
-	              <td style="width:45%;" ><p>' . $plugin->get_lang('LastResponse') . ': ' . $ticket['ticket']['sys_lastedit_datetime'] . '</p></td>
-	              <td colspan="2"></td>
+	            <tr>
+	               <td><p><b>' . get_lang('Category') . ': </b>' . $ticket['ticket']['name'] . '</p></td>
 	            </tr>
 	            <tr>
-	               <td><p>' . get_lang('Subject') . ': ' . $ticket['messages'][0]['subject'] . '</p></td>
-	               <td></td>
-	               <td><p ' . $bold . '>' . get_lang('Status') . ': ' . $ticket['ticket']['status'] . '</p></td>
-	               <td colspan="2"></td>
+	               <td><p ' . $bold . '><b>' . get_lang('Status') . ':</b> ' . $ticket['ticket']['status'] . '</p></td>
 	            </tr>
 	            <tr>
-	                <td><p>' . get_lang('Category') . ': ' . $ticket['ticket']['name'] . '</p></td>
-	                <td></td>
-	                <td ><p>' . $plugin->get_lang('Priority') . ':' . $ticket['ticket']['priority'] . '<p></td>
-	                <td colspan="2"></td>
+	                <td><p><b>' . $plugin->get_lang('Priority') . ': </b>' . $ticket['ticket']['priority'] . '<p></td>
 	            </tr>';
     if ($ticket['ticket']['course_url'] != null) {
         echo '<tr>
@@ -301,17 +319,18 @@ if (!isset($_POST['compose'])) {
 	            <td colspan="2"></td>
 	          </tr>';
     }
-    if ($isAdmin) {
-        echo '<tr>
-		<td><p>' . get_lang('User') . ': &nbsp;' . $user_info = $ticket['ticket']['user_url'] . ' (' . $ticket['usuario']['username'] . ')</p></td>
- 	      </tr>';
-    }
-    //select admins
+    echo '<tr>
+            <td><b>' . get_lang('Description') . ':</b> <br />
+            '.$firstMessage['message'].'</td>
+         </tr>
+        ';
+
+    // select admins
     $select_admins = '<select  class ="chzn-select" style="width: 350px; " name = "admins" id="admins" ">';
 
     $admins = UserManager::get_user_list_like(array("status" => "1"), array("username"), true);
     foreach ($admins as $admin) {
-        $select_admins.= "<option value = '" . $admin['user_id'] . "' " . (($user_id == $admin['user_id']) ? ("selected='selected'") : "") . ">" . $admin['lastname'] . " ," . $admin['firstname'] . "</option>";
+        $select_admins.= "<option value = '" . $admin['user_id'] . "' " . (($user_id == $admin['user_id']) ? ("selected='selected'") : "") . ">" . $admin['lastname'] . ", " . $admin['firstname'] . "</option>";
     }
     $select_admins .= "</select>";
     echo '<div id="dialog-form" title="' . $plugin->get_lang('AssignTicket') . '" >';
@@ -323,23 +342,24 @@ if (!isset($_POST['compose'])) {
 			</div>
 		  </form>';
     echo '</div>';
-    echo '</table></div>';
+    echo '</table>';
     $messages = $ticket['messages'];
-    echo "<div class='row'>";
-    echo "<div class='span8 offset2'>";
+
+    unset($messages[0]);
     foreach ($messages as $message) {
         $type = "success";
 
         if ($message['admin']) {
             $type = "normal";
             if ($isAdmin) {
-                $message['message'].='<br/><b>' . $plugin->get_lang('AttendedBy') . ': ' . $message['user_created'] . " - " . api_convert_and_format_date(api_get_local_time($message['sys_insert_datetime']), DATE_TIME_FORMAT_LONG, _api_get_timezone()) . "</b>";
+                $message['message'] .= '<br/><b>' . $plugin->get_lang('AttendedBy') . ': ' . $message['user_created'] . " - " . api_convert_and_format_date(api_get_local_time($message['sys_insert_datetime']), DATE_TIME_FORMAT_LONG, _api_get_timezone()) . "</b>";
             }
         }else {
-            $message['message'].='<b>' . get_lang('Sent') . ': ' . api_convert_and_format_date(api_get_local_time($message['sys_insert_datetime']), DATE_TIME_FORMAT_LONG, _api_get_timezone()) . "</b>";
+            $message['message'] .= '<br /><b>' . get_lang('Sent') . ':</b> ' . api_convert_and_format_date(api_get_local_time($message['sys_insert_datetime']), DATE_TIME_FORMAT_LONG, _api_get_timezone());
         }
 
-        $receivedMessage = '<b>' . get_lang('Subject') . ': </b> ' . $message['subject'] . '<br/> <b>' . get_lang('Message') . ':</b>' . $message['message'] . '<br/>';
+        $receivedMessage = '<b>' . get_lang('Subject') . ': </b> ' . $message['subject'] . '<br/>
+                            <b>' . get_lang('Message') . ':</b><br/>' . $message['message'] . '<br/>';
         $attachementLinks = "";
 
         if (isset($message['atachments'])) {
@@ -354,9 +374,8 @@ if (!isset($_POST['compose'])) {
         $entireMessage = $receivedMessage . $attachementLinks;
         echo Display::return_message($entireMessage, $type, false);
     }
-    echo "</div>";
-    echo "</div>";
-    $subject = get_lang('ReplyShort') .": " . $message['subject'];
+
+    $subject = get_lang('ReplyShort') .": " . $firstMessage['subject'];
     $user_admin = api_is_platform_admin();
     if ($ticket['ticket']['status_id'] != 'REE' AND $ticket['ticket']['status_id'] != 'CLS') {
         if (!$isAdmin && $ticket['ticket']['status_id'] != 'XCF') {
@@ -438,25 +457,14 @@ function show_form_send_message()
         );
     }
 
-    $form->addElement('html', '<span id="filepaths">');
-    $form->addElement('html', '<div id="filepath_1">');
     $form->addElement('file', 'attach_1', get_lang('FilesAttachment'));
-    $form->addElement('html', '</div>');
-    $form->addElement('html', '</span>');
-
-    $form->addElement('html', '<div class="controls">');
-    $form->addElement('html', '<span id="link-more-attach" >');
-    $form->addElement('html', '<span class="label label-info" onclick="return add_image_form()">' . get_lang('AddOneMoreFile') . '</span>');
-    $form->addElement('html', '</span>');
-    $form->addElement(
-        'html',
-        '('.sprintf(
-            get_lang('MaximunFileSizeX'),
-            format_file_size(
-                api_get_setting('message.message_max_upload_filesize')
-            )
-        ).')'
-    );
+    $form->addLabel('', '<span id="filepaths"><div id="filepath_1"></div></span>');
+    $form->addLabel('',
+        '<span id="link-more-attach">
+         <span class="btn btn-success" onclick="return add_image_form()">' . get_lang('AddOneMoreFile') . '</span>
+         </span>
+         ('.sprintf(get_lang('MaximunFileSizeX'), format_file_size(api_get_setting('message_max_upload_filesize'))).')
+    ');
 
     $form->addElement('html', '<br/>');
     $form->addElement(
@@ -466,7 +474,7 @@ function show_form_send_message()
         null,
         null,
         null,
-        'save'
+        'btn btn-primary'
     );
 
     $form->display();

+ 18 - 6
plugin/ticket/src/ticket_plugin.class.php

@@ -12,21 +12,32 @@ class TicketPlugin extends Plugin
     /**
      * Set the result
      * @staticvar null $result
-     * @return type
+     * @return TicketPlugin
      */
-    static function create()
+    public static function create()
     {
         static $result = null;
         return $result ? $result : $result = new self();
     }
 
+    /**
+     * TicketPlugin constructor.
+     */
     protected function __construct()
     {
         $settings = array(
             'tool_enable' => 'boolean',
-            'allow_student_add' => 'boolean'
+            'allow_student_add' => 'boolean',
+            'allow_category_edition' => 'boolean',
+            'warn_admin_no_user_in_category' => 'boolean',
+            'send_warning_to_all_admins' => 'boolean'
+        );
+
+        parent::__construct(
+            '2.0',
+            'Kenny Rodas Chavez, Genesis Lopez, Francis Gonzales, Yannick Warnier, Julio Montoya',
+            $settings
         );
-        parent::__construct('1.0', 'Kenny Rodas Chavez, Genesis Lopez, Francis Gonzales, Yannick Warnier', $settings);
     }
 
     /**
@@ -36,7 +47,6 @@ class TicketPlugin extends Plugin
     {
         // Create database tables and insert a Tab
         require_once api_get_path(SYS_PLUGIN_PATH) . PLUGIN_NAME . '/database.php';
-
     }
 
     /**
@@ -58,7 +68,7 @@ class TicketPlugin extends Plugin
         $settings = $this->get_settings();
         $plugSetting = current($settings);
 
-        //Delete settings
+        // Delete settings
         $sql = "DELETE FROM $tblSettings WHERE variable = 'ticket_tool_enable'";
         Database::query($sql);
 
@@ -87,4 +97,6 @@ class TicketPlugin extends Plugin
             echo "<script>location.href = '" . $_SERVER['REQUEST_URI'] . "';</script>";
         }
     }
+
+
 }

+ 4 - 3
plugin/ticket/src/tutor_report.lib.php

@@ -32,8 +32,9 @@ function initializeReport($course_code)
     if (intval($obj->cant) != $weeksCount) {
 
         if (intval($obj->cant) > $weeksCount) {
-            $sql = "DELETE FROM $table_reporte_semanas WHERE  week_id > $weeksCount AND course_code = '$course_code'";
-            Database::query("DELETE FROM $table_reporte_semanas WHERE  week_id > $weeksCount AND course_code = '$course_code'");
+            $sql = "DELETE FROM $table_reporte_semanas
+                    WHERE  week_id > $weeksCount AND course_code = '$course_code'";
+            Database::query($sql);
         } else {
             for ($i = $obj->cant + 1; $i <= $weeksCount; $i++) {
                 if (!Database::query("INSERT INTO $table_reporte_semanas (week_id, course_code, forum_id, work_id, quiz_id, pc_id)
@@ -139,7 +140,6 @@ function showResults($courseInfo, $weeksCount, $page)
             <option value="14" ' . (($weeksCount == 14) ? 'selected="selected"' : "") . '>14 weeks</option>
             </select>';
 
-
     if ($weeksCount == 14) {
         $html .= '<span style="float:right;"><a href="tutor.php?page=' . (($page == 1) ? 2 : 1) . '">' . (($page == 1) ? "Siguiente" : "Anterior") . '</a></span>';
     }
@@ -196,6 +196,7 @@ function showStudentResult($datos, $pagina)
         $fila.= '<td align="center">' . (($dato['thread_ok'] == 1) ? Display::return_icon('check.png') : Display::return_icon('aspa.png')) . '</td>';
     }
     $fila.= '</tr>';
+
     return $fila;
 }
 

+ 1 - 1
plugin/ticket/start.php

@@ -6,7 +6,7 @@
  * @package chamilo.plugin.ticket
  */
 $course_plugin = 'ticket'; //needed in order to load the plugin lang variables
-require_once dirname(__FILE__).'/config.php';
+require_once __DIR__.'/config.php';
 $tool_name = get_lang('Ticket');
 $tpl = new Template($tool_name);
 

+ 1 - 1
plugin/ticket/uninstall.php

@@ -7,5 +7,5 @@
  * the global database and the courses tables
  * @package chamilo.plugin.ticket
  */
-require_once dirname(__FILE__).'/config.php';
+require_once __DIR.'/config.php';
 TicketPlugin::create()->uninstall();

+ 51 - 0
src/Chamilo/ContactBundle/Admin/CategoryAdmin.php

@@ -0,0 +1,51 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\Admin;
+
+use Sonata\AdminBundle\Admin\Admin;
+use Sonata\AdminBundle\Datagrid\ListMapper;
+use Sonata\AdminBundle\Form\FormMapper;
+use Sonata\AdminBundle\Datagrid\DatagridMapper;
+
+/**
+ * Class UserAdmin
+ * @package Chamilo\ContactBundle\Admin
+ */
+class CategoryAdmin extends Admin
+{
+    /**
+     * @param ListMapper $listMapper
+     */
+    protected function configureListFields(ListMapper $listMapper)
+    {
+        $listMapper
+            //->add('headline', null, array('identifier' => true))
+            ->add('name', null, array('identifier' => true))
+            ->add('email')
+        ;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configureFormFields(FormMapper $formMapper)
+    {
+        $formMapper
+            ->add('name')
+            ->add('email')
+        ;
+    }
+
+    /**
+     * @param DatagridMapper $datagridMapper
+     */
+    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
+    {
+        $datagridMapper
+            ->add('name')
+            ->add('email')
+        ;
+    }
+}
+

+ 15 - 0
src/Chamilo/ContactBundle/ChamiloContactBundle.php

@@ -0,0 +1,15 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * Class ChamiloContactBundle
+ * @package Chamilo\ChamiloContactBundle
+ */
+class ChamiloContactBundle extends Bundle
+{
+
+}

+ 93 - 0
src/Chamilo/ContactBundle/Controller/ContactController.php

@@ -0,0 +1,93 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\Controller;
+
+use Chamilo\ContactBundle\Entity\Category;
+use Chamilo\ContactBundle\Form\Type\ContactType;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Chamilo\UserBundle\Entity\User;
+
+/**
+ *
+ * Class ContactController
+ *
+ * @Route("/")
+ *
+ * @package Chamilo\ContactBundle\Controller
+ */
+class ContactController extends Controller
+{
+    /**
+     * @Route("/", name="contact")
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function indexAction(Request $request)
+    {
+        $type = new ContactType();
+        /** @var User $user */
+        $user = $this->getUser();
+        $data = [];
+
+        if ($user) {
+            $data = [
+                'firstname' => $user->getFirstname(),
+                'lastname' =>  $user->getFirstname(),
+                'email' =>  $user->getEmail(),
+
+            ];
+        }
+
+        $form = $this->createForm($type, $data);
+
+        if ($request->isMethod('POST')) {
+            $form->bind($request);
+
+            $em = $this->getDoctrine()->getManager();
+
+            $category = $form->get('category')->getData();
+            /** @var Category $category */
+            $category = $em->getRepository('ChamiloContactBundle:Category')->find($category);
+
+            if ($form->isValid()) {
+                $message = \Swift_Message::newInstance()
+                    ->setSubject($form->get('subject')->getData())
+                    ->setFrom($form->get('email')->getData())
+                    ->setTo($category->getEmail())
+                    ->setBody(
+                        $this->renderView(
+                            '@ChamiloContact/contact.html.twig',
+                            array(
+                                'ip' => $request->getClientIp(),
+                                'firstname' => $form->get('firstname')->getData(),
+                                'lastname' => $form->get('lastname')->getData(),
+                                'subject' => $form->get('subject')->getData(),
+                                'email' => $form->get('email')->getData(),
+                                'message' => $form->get('message')->getData()
+                            )
+                        )
+                    );
+
+                $this->get('mailer')->send($message);
+                $this->addFlash(
+                    'success', 
+                    'Your email has been sent! Thanks!'
+                );
+
+                return $this->redirect($this->generateUrl('contact'));
+            }
+        }
+
+        return $this->render(
+            '@ChamiloContact/index.html.twig',
+            array(
+                'form' => $form->createView()
+            )
+        );
+    }
+}

+ 26 - 0
src/Chamilo/ContactBundle/DependencyInjection/ChamiloContactExtension.php

@@ -0,0 +1,26 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\Config\FileLocator;
+use Symfony\Component\HttpKernel\DependencyInjection\Extension;
+use Symfony\Component\DependencyInjection\Loader;
+
+/**
+ * This is the class that loads and manages your bundle configuration
+ *
+ * To learn more see {@link http://symfony.com/doc/current/cookbook/bundles/extension.html}
+ */
+class ChamiloContactExtension extends Extension
+{
+    /**
+     * {@inheritDoc}
+     */
+    public function load(array $configs, ContainerBuilder $container)
+    {
+        $loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+        $loader->load('admin.yml');
+    }
+}

+ 81 - 0
src/Chamilo/ContactBundle/Entity/Category.php

@@ -0,0 +1,81 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+/**
+ * Class Category
+ *
+ * @ORM\Entity
+ * @ORM\Table(
+ *     name="contact_category"
+ * )
+ *
+ * @package Chamilo\FaqBundle\Entity
+ */
+class Category
+{
+    /**
+     * @var integer
+     *
+     * @ORM\Column(name="id", type="integer")
+     * @ORM\Id
+     * @ORM\GeneratedValue()
+     */
+    protected $id;
+
+    /**
+     * @var string
+     * @ORM\Column(name="name", type="string", nullable=false)
+     */
+    protected $name;
+
+    /**
+     * @var string
+     * @ORM\Column(name="email", type="string")
+     */
+    protected $email;
+
+    public function __toString()
+    {
+        return (string) $this->getName();
+    }
+
+    /**
+     * @return string
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $name
+     * @return Category
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+        return $this;
+    }
+
+    /**
+     * @return string
+     */
+    public function getEmail()
+    {
+        return $this->email;
+    }
+
+    /**
+     * @param string $email
+     * @return Category
+     */
+    public function setEmail($email)
+    {
+        $this->email = $email;
+        return $this;
+    }
+}

+ 13 - 0
src/Chamilo/ContactBundle/Entity/Contact.php

@@ -0,0 +1,13 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+
+class Contact
+{
+
+    
+
+}

+ 78 - 0
src/Chamilo/ContactBundle/Form/Type/ContactType.php

@@ -0,0 +1,78 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Chamilo\ContactBundle\Form\Type;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilderInterface;
+use Symfony\Component\Form\Extension\Core\Type\SubmitType;
+use Symfony\Component\OptionsResolver\OptionsResolverInterface;
+use Symfony\Component\Validator\Constraints\Collection;
+use Symfony\Component\Validator\Constraints\Email;
+use Symfony\Component\Validator\Constraints\Length;
+use Symfony\Component\Validator\Constraints\NotBlank;
+use Symfony\Bridge\Doctrine\Form\Type\EntityType;
+
+/**
+ * Class ContactType
+ * @package Chamilo\ContactBundle\Form\Type
+ */
+class ContactType extends AbstractType
+{
+    /**
+     * @param FormBuilderInterface $builder
+     * @param array $options
+     */
+    public function buildForm(FormBuilderInterface $builder, array $options)
+    {
+        $builder
+            ->add(
+                'category',
+                EntityType::class,
+                ['class' => 'Chamilo\ContactBundle\Entity\Category']
+            )
+            ->add('firstname')
+            ->add('lastname')
+            ->add('email')
+            ->add('subject')
+            ->add('message', 'textarea')
+            ->add('send', SubmitType::class, ['attr' => ['class' => 'btn btn-primary']])
+        ;
+    }
+
+    /**
+     * @param OptionsResolverInterface $resolver
+     */
+    public function setDefaultOptions(OptionsResolverInterface $resolver)
+    {
+        $collectionConstraint = new Collection(array(
+            'category' => array(
+                new NotBlank(array('message' => 'Category should not be blank.'))
+            ),
+            'firstname' => array(
+                new NotBlank(array('message' => 'firstname should not be blank.')),
+                new Length(array('min' => 2))
+            ),
+            'lastname' => array(
+                new NotBlank(array('message' => 'lastname should not be blank.')),
+                new Length(array('min' => 2))
+            ),
+            'email' => array(
+                new NotBlank(array('message' => 'Email should not be blank.')),
+                new Email(array('message' => 'Invalid email address.'))
+            ),
+            'subject' => array(
+                new NotBlank(array('message' => 'Subject should not be blank.')),
+                new Length(array('min' => 3))
+            ),
+            'message' => array(
+                new NotBlank(array('message' => 'Message should not be blank.')),
+                new Length(array('min' => 5))
+            )
+        ));
+
+        $resolver->setDefaults(array(
+            'constraints' => $collectionConstraint
+        ));
+    }
+}

+ 9 - 0
src/Chamilo/ContactBundle/Resources/config/admin.yml

@@ -0,0 +1,9 @@
+services:
+    sonata.admin.contact_category:
+        class: Chamilo\ContactBundle\Admin\CategoryAdmin
+        tags:
+            - { name: sonata.admin, manager_type: orm, group: "LMS", label: "Contact category" }
+        arguments:
+            - ~
+            - Chamilo\ContactBundle\Entity\Category
+            - ~

+ 3 - 0
src/Chamilo/ContactBundle/Resources/config/routing.yml

@@ -0,0 +1,3 @@
+contact:
+    resource: "@ChamiloContactBundle/Controller/ContactController.php"
+    type:     annotation

+ 11 - 0
src/Chamilo/ContactBundle/Resources/views/contact.html.twig

@@ -0,0 +1,11 @@
+{{ 'You had a new message from  %url%' | trans({'%url%': url(app.request.attributes.get('_route'))} ) }}
+
+{{ 'Firstname' | trans }} : {{ firstname }}
+{{ 'Lirstname' | trans }} : {{ lastname }}
+
+Email: {{ email }}
+IP: {{ ip }}
+
+{{ 'Subject' | trans }} : {{ subject }}
+{{ 'Message' | trans }} :
+{{ message|raw }}

+ 12 - 0
src/Chamilo/ContactBundle/Resources/views/index.html.twig

@@ -0,0 +1,12 @@
+{% extends '@ChamiloCore/layout_one_col.html.twig' %}
+
+{% block content %}
+
+<section id="main_content">
+    <div class="page-header"><h2>{{ 'Contact' | trans  }}</h2>
+    </div>
+    {{ form_start(form) }}
+    {{ form_widget(form, {'attr': {'class': ''}}) }}
+    {{ form_end(form) }}
+</section>
+{% endblock %}

+ 44 - 2
src/Chamilo/CoreBundle/EventListener/LegacyLoginListener.php

@@ -3,6 +3,8 @@
 
 namespace Chamilo\CoreBundle\EventListener;
 
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -45,6 +47,7 @@ class LegacyLoginListener implements EventSubscriberInterface
 
             return;
         }
+
         $token = $this->tokenStorage->getToken();
         if ($token) {
             $isGranted = $this->container->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY');
@@ -56,16 +59,44 @@ class LegacyLoginListener implements EventSubscriberInterface
                         /** @var User $user */
                         $user = $this->container->get('sonata.user.user_manager')->findOneBy($criteria);
                         if ($user) {
+                            $em = $this->container->get('doctrine');
                             /** @var User $completeUser */
-                            $completeUser = $this->container->get('doctrine')->getRepository('ChamiloUserBundle:User')->findOneBy($criteria);
+                            $completeUser = $em->getRepository('ChamiloUserBundle:User')->findOneBy($criteria);
                             $user->setLanguage($completeUser->getLanguage());
 
+                            $isAdminUser = $em->getRepository('ChamiloCoreBundle:Admin')->findOneBy(['userId' => $user->getId()]);
+                            if ($isAdminUser) {
+                                $user->setSuperAdmin(true);
+                            }
+
+                            $languages = ['german' => 'de', 'english' => 'en', 'spanish' => 'es', 'french' => 'fr'];
+                            $locale = isset($languages[$user->getLanguage()]) ? $languages[$user->getLanguage()] : '';
+                            if ($user && !empty($locale)) {
+
+                                error_log('legacyloginlistener');
+                                error_log($locale);
+                                $user->setLocale($locale);
+
+                                //$request->getSession()->set('_locale_user', $locale);
+
+                                // if no explicit locale has been set on this request, use one from the session
+                                $request->getSession()->set('_locale', $locale);
+                                $request->setLocale($locale);
+                            }
+
                             $token = new UsernamePasswordToken($user, null, "main", $user->getRoles());
 
                             $this->tokenStorage->setToken($token); //now the user is logged in
+
                             //now dispatch the login event
                             $event = new InteractiveLoginEvent($request, $token);
                             $this->container->get("event_dispatcher")->dispatch("security.interactive_login", $event);
+
+
+                            $this->container->get("event_dispatcher")->addListener(
+                                KernelEvents::RESPONSE, array($this, 'redirectUser')
+                            );
+
                         }
                     }
                 }
@@ -80,7 +111,18 @@ class LegacyLoginListener implements EventSubscriberInterface
     {
         return array(
             // must be registered before the default Locale listener
-            KernelEvents::REQUEST => array(array('onKernelRequest', 17)),
+            KernelEvents::REQUEST => array(array('onKernelRequest', 15)),
         );
     }
+
+    /**
+     * @param FilterResponseEvent $event
+     */
+    public function redirectUser(FilterResponseEvent $event)
+    {
+        $uri = $event->getRequest()->getUri();
+        // on effectue la redirection
+        $response = new RedirectResponse($uri);
+        $event->setResponse($response);
+    }
 }

+ 1 - 1
src/Chamilo/CoreBundle/Resources/config/routing.yml

@@ -29,7 +29,7 @@ legacy_javascript:
 
 # Legacy routes
 main:
-    path: /main/{name}
+    path: ../../main/{name}
     defaults: { _controller: ChamiloCoreBundle:Legacy:classic }
     requirements:
       name: .+

+ 1 - 1
src/Chamilo/CoreBundle/Resources/views/layout_one_col.html.twig

@@ -24,7 +24,7 @@
 
             {# Content #}
             {% block content %}
-            <section class="main_content">
+            <section id="main_content">
                 {% autoescape false %}
                     {{ content }}
                 {% endautoescape %}

+ 42 - 32
src/Chamilo/FaqBundle/Resources/views/Faq/index.html.twig

@@ -3,48 +3,58 @@
 {% block sonata_page_container %}
 <h1>FAQ</h1>
 
-<h2>Categories</h2>
+    <br />
+<div class="well">
 <ul>
-    {% for category in categories %}
-        {% if selectedCategory.getSlug is defined and selectedCategory.getSlug == category.getSlug %}
-            <li class="active">
-                <strong>{{ category.getHeadline|e }}</strong>
-            </li>
-        {% else %}
+{% for category in categories %}
+    <li class="active">
+        <h3>{{ category.getHeadline|e }}</h3>
+    </li>
+    <ul>
+    {% set list = [false] %}
+    {% if is_granted('IS_AUTHENTICATED_FULLY') %}
+        {% set list = [false, true] %}
+    {% endif %}
+
+    {% for question in category.questions if question.onlyAuthUsers in list %}
+        {% if question.isActive %}
             <li>
-                <a href="{{ path('faq', { 'categorySlug': category.getSlug }) }}">
-                    {{ category.getHeadline|e }}
+                <a href="#{{ question.id }}">
+                    {{ question.getHeadline|e }}
                 </a>
             </li>
         {% endif %}
     {% endfor %}
+    </ul>
+{% endfor %}
 </ul>
+</div>
+<br />
 
-{% if selectedCategory is not null %}
-    <h2>Questions</h2>
+<ul>
+{% for category in categories %}
+    <li class="active">
+        <h2>{{ category.getHeadline|e }}</h2>
+    </li>
     <ul>
-        {% set list = [false] %}
-        {% if is_granted('IS_AUTHENTICATED_FULLY') %}
-            {% set list = [false, true] %}
-        {% endif %}
+    {% set list = [false] %}
+    {% if is_granted('IS_AUTHENTICATED_FULLY') %}
+        {% set list = [false, true] %}
+    {% endif %}
 
-        {% for question in questions if question.onlyAuthUsers in list %}
-            {% if question.isActive %}
-                {% if selectedQuestion.getSlug is defined and selectedQuestion.getSlug == question.getSlug %}
-                    <li class="active">
-                        <strong>{{ question.getHeadline|e }}</strong>
-                        <div>{{ selectedQuestion.getBody }}</div>
-                    </li>
-                {% else %}
-                    <li>
-                        <a href="{{ path('faq', { 'categorySlug': selectedCategory.getSlug, 'questionSlug': question.getSlug }) }}">
-                            {{ question.getHeadline|e }}
-                        </a>
-                    </li>
-                {% endif %}
-            {% endif %}
-        {% endfor %}
+    {% for question in category.questions if question.onlyAuthUsers in list %}
+        {% if question.isActive %}
+            <li>
+               <h3>
+                   {{ question.getHeadline|e }}
+                </h3>
+                <a name="{{ question.id }}" id="{{ question.id }}"></a>
+                <p>{{ question.getBody }}</p>
+            </li>
+        {% endif %}
+    {% endfor %}
     </ul>
-{% endif %}
+{% endfor %}
+</ul>
 
 {% endblock %}

+ 10 - 10
src/Chamilo/ThemeBundle/Resources/views/Layout/footer.js.twig

@@ -3,17 +3,17 @@
     $(document).ready( function() {
         // Date time settings.
         moment.locale('{{ app.request.locale }}');
-        $.datepicker.setDefaults($.datepicker.regional["{{ app.request.locale }}"]);
-        $.datepicker.regional["local"] = $.datepicker.regional["{{ app.request.locale }}"];
+        //$.datepicker.setDefaults($.datepicker.regional["{{ app.request.locale }}"]);
+        //$.datepicker.regional["local"] = $.datepicker.regional["{{ app.request.locale }}"];
 
         // Chosen select
-        $(".chzn-select").chosen({
-            disable_search_threshold: 10,
-            no_results_text: '{{ 'SearchNoResultsFound' | trans }}',
-            placeholder_text_multiple: '{{ 'SelectSomeOptions' | trans }}',
-            placeholder_text_single: '{{ 'SelectAnOption' | trans }}',
-            width: "100%"
-        });
+        {#$(".chzn-select").chosen({#}
+            {#disable_search_threshold: 10,#}
+            {#no_results_text: '{{ 'SearchNoResultsFound' | trans }}',#}
+            {#placeholder_text_multiple: '{{ 'SelectSomeOptions' | trans }}',#}
+            {#placeholder_text_single: '{{ 'SelectAnOption' | trans }}',#}
+            {#width: "100%"#}
+        {#});#}
 
         // Bootstrap tabs.
         $('.tab-wrapper a').click(function (e) {
@@ -69,7 +69,7 @@
         $(".jp-jplayer audio").addClass('skip');
 
         // Mediaelement
-        jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer(/* Options */);
+        //jQuery('video:not(.skip), audio:not(.skip)').mediaelementplayer(/* Options */);
 
         // Table highlight.
         $("form .data_table input:checkbox").click(function () {

+ 9 - 9
src/Chamilo/ThemeBundle/Resources/views/Layout/header.js.twig

@@ -126,12 +126,12 @@ $(document).ready(function() {
         });
     }
 
-    $(".accordion_jquery").accordion({
-        autoHeight: false,
-        active: false, // all items closed by default
-        collapsible: true,
-        header: ".accordion-heading"
-    });
+    {#$(".accordion_jquery").accordion({#}
+        {#autoHeight: false,#}
+        {#active: false, // all items closed by default#}
+        {#collapsible: true,#}
+        {#header: ".accordion-heading"#}
+    {#});#}
 
     // Global popup
     $('body').on('click', 'a.ajax', function(e) {
@@ -212,7 +212,7 @@ $(document).ready(function() {
     );
 
     /* Make responsive image maps */
-    $('map').imageMapResize();
+   // $('map').imageMapResize();
 
     jQuery.fn.filterByText = function(textbox) {
         return this.each(function() {
@@ -260,10 +260,10 @@ $(document).scroll(function() {
         }
 
         if (fixed.attr('data-top') - fixed.outerHeight() <= $(this).scrollTop()) {
-            fixed.addClass('subnav-fixed');
+            fixed.addClass('navbar-fixed-top');
             fixed.css('width', '100%');
         } else {
-            fixed.removeClass('subnav-fixed');
+            fixed.removeClass('navbar-fixed-top');
             fixed.css('width', '200px');
         }
     }

+ 1 - 1
tests/scripts/fix_migrations_1.9.x_1.10.0.php

@@ -47,7 +47,7 @@ Database::query("UPDATE session SET id_coach = NULL WHERE id_coach = 0");
 Database::query("UPDATE session SET session_category_id = NULL WHERE session_category_id = 0");
 Database::query("ALTER TABLE session ADD CONSTRAINT FK_D044D5D4D1DC2CFC FOREIGN KEY (id_coach) REFERENCES user (id)");
 Database::query("ALTER TABLE session ADD CONSTRAINT FK_D044D5D4EE1F8395 FOREIGN KEY (session_category_id) REFERENCES session_category (id)");
-Database::query("ALTER TABLE session_rel_user ADD moved_status INT DEFAULT NULL, ADD moved_at DATETIME DEFAULT NULL, CHANGE id id INT AUTO_INCREMENT NOT NULL, CHANGE session_id session_id INT DEFAULT NULL, CHANGE user_id user_id INT DEFAULT NULL, CHANGE relation_type relation_type INT NOT NULL, CHANGE duration moved_to INT DEFAULT NULL");
+Database::query("ALTER TABLE session_rel_user ADD moved_status INT DEFAULT NULL, ADD moved_at DATETIME DEFAULT NULL, CHANGE id id INT AUTO_INCREMENT NOT NULL, CHANGE session_id session_id INT DEFAULT NULL, CHANGE user_id user_id INT DEFAULT NULL, CHANGE relation_type relation_type INT NOT NULL");
 Database::query("ALTER TABLE session_rel_user ENGINE=InnoDB");
 Database::query("ALTER TABLE session_rel_user ADD CONSTRAINT FK_B0D7D4C0613FECDF FOREIGN KEY (session_id) REFERENCES session (id)");
 Database::query("ALTER TABLE session_rel_user ADD CONSTRAINT FK_B0D7D4C0A76ED395 FOREIGN KEY (user_id) REFERENCES user (id)");

+ 10 - 3
user_portal.php

@@ -185,13 +185,20 @@ if (api_get_setting('go_to_course_after_login') == 'true') {
     }
 }
 
-
-//Show the chamilo mascot
+// Show the chamilo mascot
 if (empty($courseAndSessions['html']) && !isset($_GET['history'])) {
 	$controller->tpl->assign('welcome_to_course_block', $controller->return_welcome_to_course_block());
 }
 
-$controller->tpl->assign('content', $courseAndSessions['html']);
+$template = api_get_configuration_value('user_portal_tpl');
+if (empty($template)) {
+    $controller->tpl->assign('content', $courseAndSessions['html']);
+} else {
+    $controller->tpl->assign('items', $courseAndSessions['items']);
+    $userPortalTemplate = $controller->tpl->get_template('user_portal/'.$template);
+    $content = $controller->tpl->fetch($userPortalTemplate);
+    $controller->tpl->assign('content', $content);
+}
 
 if (api_get_setting('allow_browser_sniffer') == 'true') {
     if (isset($_SESSION['sniff_navigator']) && $_SESSION['sniff_navigator'] != "checked") {

+ 0 - 16
web/.htaccess

@@ -5,18 +5,6 @@
 # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
 DirectoryIndex app.php
 
-# By default, Apache does not evaluate symbolic links if you did not enable this
-# feature in your server configuration. Uncomment the following line if you
-# install assets as symlinks or if you experience problems related to symlinks
-# when compiling LESS/Sass/CoffeScript assets.
-# Options FollowSymlinks
-
-# Disabling MultiViews prevents unwanted negotiation, e.g. "/app" should not resolve
-# to the front controller "/app.php" but be rewritten to "/app.php/app".
-<IfModule mod_negotiation.c>
-    Options -MultiViews
-</IfModule>
-
 <IfModule mod_rewrite.c>
     RewriteEngine On
 
@@ -30,10 +18,6 @@ DirectoryIndex app.php
     RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
     RewriteRule ^(.*) - [E=BASE:%1]
 
-    # Sets the HTTP_AUTHORIZATION header removed by apache
-    RewriteCond %{HTTP:Authorization} .
-    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
-
     # Redirect to URI without front controller to prevent duplicate content
     # (with and without `/app.php`). Only do this redirect on the initial
     # rewrite by Apache and not on subsequent cycles. Otherwise we would get an