Browse Source

Minor - partial merge from 1.11.x

Julio Montoya 6 years ago
parent
commit
253a64f608
35 changed files with 704 additions and 411 deletions
  1. 65 3
      documentation/changelog.html
  2. 1 1
      main/admin/course_information.php
  3. 12 10
      main/admin/course_list.php
  4. 21 21
      main/admin/index.php
  5. 62 21
      main/admin/legal_add.php
  6. 7 6
      main/admin/user_add.php
  7. 1 0
      main/admin/user_edit.php
  8. 6 6
      main/admin/user_information.php
  9. 5 11
      main/admin/user_list.php
  10. 4 5
      main/admin/user_list_consent.php
  11. 1 0
      main/auth/inscription.php
  12. 1 0
      main/auth/profile.php
  13. 3 0
      main/calendar/ical_export.php
  14. 28 17
      main/coursecopy/copy_course_session.php
  15. 8 0
      main/inc/lib/course.lib.php
  16. 2 2
      main/inc/lib/exercise.lib.php
  17. 1 1
      main/inc/lib/social.lib.php
  18. 294 65
      main/inc/lib/template.lib.php
  19. 1 8
      main/session/add_many_session_to_category.php
  20. 3 3
      main/session/resume_session.php
  21. 12 2
      main/social/personal_data.php
  22. 0 48
      plugin/bbb/view/admin.tpl
  23. 0 176
      plugin/bbb/view/listing.tpl
  24. 1 4
      plugin/test2pdf/resources/css/style.css
  25. BIN
      plugin/test2pdf/resources/img/128/accept.png
  26. BIN
      plugin/test2pdf/resources/img/24/download.png
  27. BIN
      plugin/test2pdf/resources/img/24/info.png
  28. BIN
      plugin/test2pdf/resources/img/32/accept.png
  29. BIN
      plugin/test2pdf/resources/img/32/download.png
  30. BIN
      plugin/test2pdf/resources/img/32/info.png
  31. BIN
      plugin/test2pdf/resources/img/64/download.png
  32. BIN
      plugin/test2pdf/resources/img/64/info.png
  33. BIN
      plugin/test2pdf/resources/img/logo.png
  34. 94 1
      tests/behat/features/bootstrap/FeatureContext.php
  35. 71 0
      tests/behat/features/toolExercise.feature

+ 65 - 3
documentation/changelog.html

@@ -109,7 +109,7 @@
     <div class="version" aria-role="article" aria-label="1.11.8">
     <a name="1.11.8"></a>
 
-    <h1>Chamilo 1.11.8 - Sayaxché,  ??/08/2018</h1>
+    <h1>Chamilo 1.11.8 - Sayaxché,  12/08/2018</h1>
     <h3>Release notes - summary</h3>
     <p>Chamilo 1.11.8 is a minor, bug fix and security fix release on top of 1.11.6 that contains a series of
         small new features, including first support for European RGPD. Given the security fixes it contains,
@@ -204,6 +204,66 @@
         </ul>
         <h3>Improvements (minor features) and debug</h3>
         <ul aria-role="log" aria-live="off">
+            <li>[2018-08-11] (<a href="https://github.com/chamilo/chamilo-lms/commit/45ff6b6d89a9096061a03f2326363241fce8170b">45ff6b6d</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2622">GH#2622</a>) WYSIWYG: Add documentation, declare BETA and add French translation to the VR/360° video viewer in Chamilo</li>
+            <li>[2018-08-11] (<a href="https://github.com/chamilo/chamilo-lms/commit/1e9543a8e62b6213f79e1ed85887bb24b9006274">1e9543a8</a>) WYSIWYG: Add DOCTYPE html to HTML documents created through CKEditor</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/1629f2939d1ad6fdae21c4c6bb65bbe8ce8eed88">1629f293</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2622">GH#2622</a>) Global: Enable VRView mediaelement plugin by default</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/23e808f5a1d2f767a3adedbe9d28394b8d4194a4">23e808f5</a>) Admin: Fix access to add sessions to category</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/af61622f47b0a1cface0da1385bb4fcef25d88f0">af61622f</a>) Admin: Remove notice when deleting batch of users or courses with no user/course selected</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/513e5b66ad6314c33e15830063be14ec259425cf">513e5b66</a>) Display: Remove use of (some) .gif icons</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/5421757f2758032fd03872363f832b7113d7664d">5421757f</a>) Display: Remove deprecated (and ugly) info3.png/svg</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/b767f1b364dcf897c3273a97217877c170a29b52">b767f1b3</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2622">GH#2622</a>) Global: Add vrview plugin for mediaelement</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/223dcb92f0ff00916f391396b92d44549e911581">223dcb92</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2628">GH#2628</a>) Privacy: Enable GDPR features by default and allow disabling it by setting disable_gdpr in configuration.php</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/cb8d4032f4f01b3c2071e4b407b25d776c3bce16">cb8d4032</a>) Internal: Rename $strict variable to $returnEmptyIfNotFound for better understandability in get_lang()</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/cb8d4032f4f01b3c2071e4b407b25d776c3bce16">cb8d4032</a>) Internal: Rename $strict variable to $returnEmptyIfNotFound for better understandability in get_lang()</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/7f822a138cc5b910ec5a456473579816e98e2244">7f822a13</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Internal: Load the user language instead of the platform language</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/8ad5ae32bef70d8808f7bf4f2435f3800c2afbda">8ad5ae32</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Internal: Load the user language instead of the platform language</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/8572a22d6fd20d1fd11a3ce3e9c7624cf70ade3e">8572a22d</a>) Internal: Fix fatal error - vcalendar namespace fixed</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/ee3614c2d6a729a25884d3d9c828e7a1e112e74c">ee3614c2</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Legal extra fields are saved if a change is detected #2554 New version of legal will be created</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/a5d4241630fd6d0cf1238f7088061eb5d8647cee">a5d42416</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Check changes in the extra field in order to save them or not</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/006cf00b7d71a5ba77d3f7d7b50ecefe44d68e62">006cf00b</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Terms edition: Move the update explanation box lower into the page</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/dfe8548b187ebeeb39c184c8970e2b98a70d455a">dfe8548b</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Show translated version of privacy terms sections titles</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/407536472f49e450c8e9c12db1ddb87512d629c6">40753647</a>) Internal: Behat add test to check exercises with questions by categories</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/905f3facb7e88f9ed71adc604ef07bf304d4d1b8">905f3fac</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Internal: Update label of privacy terms extra fields to match those already defined in translation</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/47685b169b99f560e591fb0283e36d50bf0436fa">47685b16</a>) Extra fields: add support for the automated translation of extra field's display_text, if the term is found in the language terms</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/ab28b822fd990abd579527ce800ef206bcf8dabb">ab28b822</a>) Internal: Update get_lang() to use the "reserved" parameter as "strict". If the "strict" param is set to true, get_lang will return en empty string if it cannot find the given variable.</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/82ebde4843d3a0e83818c54763a3357ad95bac7d">82ebde48</a>) Admin: Add branch_sync.unique_id to elements reported to chamilo.org. Unique ID is generated upon install and is supposed to uniquely represent each portal, which will improve tracking and reduce redundancy of information in our statistics in the long-term future</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/ded8de9758505b31bedd6ef649ab688334de73d8">ded8de97</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Hide results from exercise and attempts as they might infringe access rules</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/ea907496f39dd858cd0aab885b0cbeb819bcaf50">ea907496</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Show only name of the chat course html file</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/e2e5102eee35eb7c28f8e2e0243048d73b1147db">e2e5102e</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add chat course file</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/38eba4dcbbbce0c1caafc414f8e385710564efa8">38eba4dc</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add info about chat from courses to personal data export</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/f6cb4e198533eaf9b3baa123b98358c54e66cfe6">f6cb4e19</a>) Privacy: Add extra fields from getTreatmentTypeList()</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/afcec41aae96c84efdf1d8155a0e6fa89ff30f67">afcec41a</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add more data in the export</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/7c601efb2c85a4053fc31a649b5806d97decf968">7c601efb</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Convert indexes in getTreatmentTypeList() to internal value title for extra_fields</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/cbf49ee5d6689068cb68c961f444d954e00bd7af">cbf49ee5</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Separate social and class groups</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/48add955bb5130c3168b5a0755d6c23b3307859a">48add955</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add more data in the export</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/eb413367362ebc7772eeaf8a098b6272e2f49f6d">eb413367</a>) Privacy: Add "no data" entry where needs be</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/e268209924bc680689740244441a49dc631442dd">e2682099</a>) Privacy: reverse name/url in Display::url() call for documents</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/26dfc5d0d93849d84d8e0e5afb7701a95e2086c5">26dfc5d0</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add more data in the export</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/f452a8f0d6609800cbd7caf87d22c8b789ac6312">f452a8f0</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Show extra fields only for HTML type</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/36521dbf3b7f6331a4abe7324934a0816de4fc6f">36521dbf</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: Add extra fields in terms</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/5b3915890c3b1894a6ce358679a2605437878e95">5b391589</a>) Privacy: Show extra fields when showing legal</li>
+            <li>[2018-08-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/fb8cb161fd0599984a5aed1ff17a3e303d9be6f8">fb8cb161</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2554">GH#2554</a>) Privacy: WIP - Add extra fields for terms and conditions</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/7a854cf973089f1a24c2c05ddcd110175074297f">7a854cf9</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Return created documents with a link to the corresponding documents an toggle visibility to display:none by default</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/eb4d446bff6e10415000d631de00eebe5afae660">eb4d446b</a>) Quiz: Avoid edit question categories in session course</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/1b13d5d8b1bcedb92a9a5bbb65f521047c725a82">1b13d5d8</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Add icons delete_terms.png</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/e3c24e3936efea31ce183904702bc80421728b55">e3c24e39</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2466">GH#2466</a>) Quiz: Fix delete hotpotatoes attempt</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/f5603e90a386a974c425f11b6333af53be353f9a">f5603e90</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2503">GH#2503</a>) Privacy: Add document, events and friends in personal data dump</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/5a7b97bef4ade766690ac4be69660d659bca7b66">5a7b97be</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Add label and temporary icon to user_list_consent.php</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/7a5f035a53af75f132d06d9e5b63df848b7572ed">7a5f035a</a>) Privacy: Rename some language terms to reduce risk of confusion</li>
+            <li>[2018-08-08] (<a href="https://github.com/chamilo/chamilo-lms/commit/5be220d079dc5d9479146ad6b91851537a87ecd4">5be220d0</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Reshape the withdrawal section</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/dc7d27ba70922da29ddde36b8355b68c681a49ea">dc7d27ba</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2620">GH#2620</a>) Fix escape queries on installation</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/7731a2c6fc8027585181aa0d72df9af21cf613d2">7731a2c6</a>) Update language variable InformationRightToBeForgottenText to InformationRightToBeForgottenLinkX to make link part of code</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/9d2ebcc6ef1e2145f67c55875ac2da4b3943cc42">9d2ebcc6</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2572">GH#2572</a>) Learning path: Fix iframe selector in lp view for ios</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/d905b5ac339970a1126eb24702b6a308c7ea456f">d905b5ac</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Block cron from browser</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/40ffb9361317178b4d378ff85871fb9489dfb2b1">40ffb936</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Fix link, add cron reminder of user requests</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/2848fa7d84d24bcb3c4df7ed9173e74a6466bd41">2848fa7d</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: You cannot "anonymize" yourself</li>
+            <li>[2018-08-07] (<a href="https://github.com/chamilo/chamilo-lms/commit/972141f070f0294c717dcdb3529b0e48fcc21e25">972141f0</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Fix extra field creation</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/de96de1a8c50f62fbb47d0071ebe2a329b740676">de96de1a</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2619">GH#2619</a>) Display: Fix course description in session about pages</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/000f4032bf8fbfa0e9ae961b39afe166aedee6ef">000f4032</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2412">GH#2412</a>) Internal: Fix get extra field values according the type</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/6cd142ffd650e7fe5aadce7fb56a1318653114cc">6cd142ff</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: If user sends remove account request, delete the remove legal request</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/531e4505434e0d8cd718fbc5517f56c30846bb4d">531e4505</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Add "erase account" extra fields</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/ecb18907a7fec22402411aa873382a4bd06cb07d">ecb18907</a>) Internal: Maintenance: Remove use of Course::unserialize() when exporting/importing course bk</li>
+            <li>[2018-08-06] (<a href="https://github.com/chamilo/chamilo-lms/commit/0683b23602b5fd0505e4d96442f573095c299597">0683b236</a>) Internal: Create the main.language.js file even if we're in CLI</li>
             <li>[2018-08-03] (<a href="https://github.com/chamilo/chamilo-lms/commit/c2b4f937520fe843f757b78bd77e88c10c091527">c2b4f937</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2616">GH#2616</a>) Quiz: Fix empty lines appearing when no question details set to appear</li>
             <li>[2018-08-03] (<a href="https://github.com/chamilo/chamilo-lms/commit/3400f430a5a70bbd49c261affe5288330abf2291">3400f430</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2572">GH#2572</a>) Learnpath: Quiz: Fix issue preventing save buttons for individual questions and end of test when quizzes are included into learning paths on Apple devices</li>
             <li>[2018-08-03] (<a href="https://github.com/chamilo/chamilo-lms/commit/618c56cd90e1f00fac11df9cb0c35dd4c68539da">618c56cd</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2555">GH#2555</a>) Privacy: Add "Delete legal agreement" button</li>
@@ -575,7 +635,6 @@
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/6d728f64d2cb1e8293282b1f9ae260050ce43c6c">6d728f64</a> - <a href="https://task.beeznest.com/issues/11338">BT#11338</a>) Plugin: Buy courses: Template: Fix service process</li>
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/f283df55c9b3c8c1713c43114067027bb0f1781f">f283df55</a> - <a href="https://task.beeznest.com/issues/11338">BT#11338</a>) Plugin: Buy courses: Template: Improvement of service detail interface</li>
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/121b21d8310eb77fc181aa343d204b1e2abd2c2a">121b21d8</a> - <a href="https://task.beeznest.com/issues/12835">BT#12835</a>) Internal: Move js code for webcam tool</li>
-            <li>[2017-12-15] (<a href="https://github.com/chamilo/chamilo-lms/commit/355066261c39f6eddff6c077935d8837d8af3a6f">35506626</a> - <a href="https://task.beeznest.com/issues/12835">BT#12835</a>) Document: Create template for document/webcam</li>
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/c57e4c0303663f537f5ea85ddb61783694795530">c57e4c03</a>) Plugin: Buy courses: Template: Fix style service_process.ptl</li>
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/7a2b9466eebb68812d6e7384bd9779a825cb04a1">7a2b9466</a> - <a href="https://github.com/chamilo/chamilo-lms/issues/2415">GH#2415</a>) Quiz: Improvement in the feedback icons in fill in the blanks questions</li>
             <li>[2018-04-18] (<a href="https://github.com/chamilo/chamilo-lms/commit/76fbb2b8799510136c8502bb6e1e6e333f065819">76fbb2b8</a>) Global: Protect pages against unauthorized users</li>
@@ -856,12 +915,15 @@
         </ul>
         <h3>Stylesheets and theming</h3>
         <ul aria-role="log" aria-live="off">
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/38e649ac17cf8c74dfe6b63af41e42c0f8cd1728">38e649ac</a>) Template: Fix footer</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/c267e71b7f2c12f5434e9c9592181aa0e740617c">c267e71b</a>) Template: Update fix show_footer and show_header tpl</li>
+            <li>[2018-08-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/248d9d9a43cdadca731427898ef818d25388aacc">248d9d9a</a>) Template: Improvement of template layout and css structure</li>
             <li>[2018-04-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/a12959e2139e7ba23f5031ff4fbedb534c359077">a12959e2</a>) Template: Added the variable home_include in the tpl layout_2_col.tpl</li>
             <li>[2018-04-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/d6c5d52a485e55ab97c3fc86c02b391c1c4fe64a">d6c5d52a</a> - <a href="https://support.chamilo.org/issues/11338">CT#11338</a>) Template: Add template variable home_include when including static HTML page through main menu. Add parameter to return_home_page(). Add SECTION_INCLUDE constant</li>
             <li>[2018-04-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/4507d6b92bec3259b7b06c0f79e9028e79fdfc41">4507d6b9</a>) Template: Fix duplicate plugin_main_top condition in layout_2_col.tpl</li>
             <li>[2018-04-10] (<a href="https://github.com/chamilo/chamilo-lms/commit/55969b05960fb67f703e0e1dbf56af28b30b7385">55969b05</a> - <a href="https://task.beeznest.com/issues/11338">BT#11338</a>) Template: Add macros for twig in tpl</li>
             <li>[2018-04-09] (<a href="https://github.com/chamilo/chamilo-lms/commit/1c1e68bba1b3fef89e8191f6fcdadecd16e84fb7">1c1e68bb</a> - <a href="https://task.beeznest.com/issues/11338">BT#11338</a>) Template: Add notice_block and help_block</li>
-
+            <li>[2017-12-15] (<a href="https://github.com/chamilo/chamilo-lms/commit/355066261c39f6eddff6c077935d8837d8af3a6f">35506626</a> - <a href="https://task.beeznest.com/issues/12835">BT#12835</a>) Template: Add template for document/webcam</li>
         </ul>
         <h3>Web services</h3>
         <ul aria-role="log" aria-live="off">

+ 1 - 1
main/admin/course_information.php

@@ -155,7 +155,7 @@ if (Database::num_rows($res) > 0) {
         $user[] = Display:: encrypted_mailto_link($obj->email, $obj->email);
         $user[] = $obj->course_status == 5 ? get_lang('Student') : get_lang('Teacher');
         $user[] = '<a href="user_information.php?user_id='.$obj->user_id.'">'.
-            Display::return_icon('synthese_view.gif', get_lang('UserInfo')).'</a>';
+            Display::return_icon('info2.png', get_lang('UserInfo')).'</a>';
         $users[] = $user;
     }
     $table = new SortableTableFromArray($users, 0, 20, 'user_table');

+ 12 - 10
main/admin/course_list.php

@@ -177,15 +177,15 @@ function get_course_data($from, $number_of_items, $column, $direction)
         $courseId = $course['id'];
 
         $actions = '<a href="course_information.php?code='.$courseCode.'">'.
-            Display::return_icon('synthese_view.gif', get_lang('Info')).'</a>&nbsp;'.
+            Display::return_icon('info2.png', get_lang('Info')).'</a>&nbsp;'.
             '<a href="'.$coursePath.$course['directory'].'/index.php">'.
-            Display::return_icon('course_home.gif', get_lang('CourseHomepage')).'</a>&nbsp;'.
+            Display::return_icon('course_home.png', get_lang('CourseHomepage')).'</a>&nbsp;'.
             '<a href="'.$path.'tracking/courseLog.php?'.api_get_cidreq_params($courseCode).'">'.
-            Display::return_icon('statistics.gif', get_lang('Tracking')).'</a>&nbsp;'.
+            Display::return_icon('statistics.png', get_lang('Tracking')).'</a>&nbsp;'.
             '<a href="'.$path.'admin/course_edit.php?id='.$courseId.'">'.
             Display::return_icon('edit.png', get_lang('Edit'), [], ICON_SIZE_SMALL).'</a>&nbsp;'.
             '<a href="'.$path.'coursecopy/create_backup.php?'.api_get_cidreq_params($courseCode).'">'.
-            Display::return_icon('backup.gif', get_lang('CreateBackup')).'</a>&nbsp;'.
+            Display::return_icon('backup.png', get_lang('CreateBackup')).'</a>&nbsp;'.
             '<a href="'.$path.'admin/course_list.php?delete_course='.$courseCode.'"  onclick="javascript: if (!confirm('."'".addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES))."'".')) return false;">'.
             Display::return_icon('delete.png', get_lang('Delete'), [], ICON_SIZE_SMALL).'</a>';
 
@@ -334,14 +334,16 @@ if (isset($_POST['action'])) {
     switch ($_POST['action']) {
         // Delete selected courses
         case 'delete_courses':
-            $course_codes = $_POST['course'];
-            if (count($course_codes) > 0) {
-                foreach ($course_codes as $course_code) {
-                    CourseManager::delete_course($course_code);
+            if (!empty($_POST['course'])) {
+                $course_codes = $_POST['course'];
+                if (count($course_codes) > 0) {
+                    foreach ($course_codes as $course_code) {
+                        CourseManager::delete_course($course_code);
+                    }
                 }
-            }
 
-            Display::addFlash(Display::return_message(get_lang('Deleted')));
+                Display::addFlash(Display::return_message(get_lang('Deleted')));
+            }
             break;
     }
 }

+ 21 - 21
main/admin/index.php

@@ -538,29 +538,29 @@ if (api_is_platform_admin()) {
         }
     }
 
-    // Data protection
-    $blocks['data_privacy']['icon'] = Display::return_icon(
-        'platform.png',
-        get_lang('Platform'),
-        [],
-        ICON_SIZE_MEDIUM,
-        false
-    );
-    $blocks['data_privacy']['label'] = api_ucfirst(get_lang('PersonalDataPrivacy'));
-    $blocks['data_privacy']['class'] = 'block-admin-platform';
-    $blocks['data_privacy']['editable'] = false;
-
-    $items = [];
-
-    $items[] = [
-        'url' => api_get_path(WEB_CODE_PATH).'admin/user_list_consent.php',
-        'label' => get_lang('UserList'),
-    ];
+    if (!api_get_configuration_value('disable_gdpr')) {
+        // Data protection
+        $blocks['data_privacy']['icon'] = Display::return_icon(
+            'platform.png',
+            get_lang('Platform'),
+            [],
+            ICON_SIZE_MEDIUM,
+            false
+        );
+        $blocks['data_privacy']['label'] = api_ucfirst(get_lang('PersonalDataPrivacy'));
+        $blocks['data_privacy']['class'] = 'block-admin-platform';
+        $blocks['data_privacy']['editable'] = false;
 
-    $blocks['data_privacy']['items'] = $items;
-    $blocks['data_privacy']['extra'] = null;
-    $blocks['data_privacy']['search_form'] = null;
+        $items = [];
+        $items[] = [
+            'url' => api_get_path(WEB_CODE_PATH).'admin/user_list_consent.php',
+            'label' => get_lang('UserList'),
+        ];
 
+        $blocks['data_privacy']['items'] = $items;
+        $blocks['data_privacy']['extra'] = null;
+        $blocks['data_privacy']['search_form'] = null;
+    }
     /* Chamilo.org */
     $blocks['chamilo']['icon'] = Display::return_icon(
         'platform.png',

+ 62 - 21
main/admin/legal_add.php

@@ -25,6 +25,26 @@ $term_preview = [
     'content' => '',
     'changes' => '',
 ];
+
+$extraField = new ExtraField('terms_and_condition');
+
+$types = LegalManager::getTreatmentTypeList();
+foreach ($types as $variable => $name) {
+    $label = 'PersonalData'.ucfirst($name).'Title';
+    $params = [
+        'variable' => $variable,
+        'display_text' => $label,
+        'field_type' => ExtraField::FIELD_TYPE_TEXTAREA,
+        'default_value' => '',
+        'visible' => true,
+        'changeable' => true,
+        'filter' => true,
+        'visible_to_self' => true,
+        'visible_to_others' => true,
+    ];
+    $extraField->save($params);
+}
+
 if ($form->validate()) {
     $check = Security::check_token('post');
     if ($check) {
@@ -32,22 +52,19 @@ if ($form->validate()) {
         $lang = $values['language'];
         //language id
         $lang = api_get_language_id($lang);
-
+        $type = 0;
         if (isset($values['type'])) {
             $type = $values['type'];
-        } else {
-            $type = 0;
         }
+        $content = '';
         if (isset($values['content'])) {
             $content = $values['content'];
-        } else {
-            $content = '';
         }
+        $changes = '';
         if (isset($values['changes'])) {
             $changes = $values['changes'];
-        } else {
-            $changes = '';
         }
+
         $submit = $values['send'];
 
         $default['content'] = $content;
@@ -55,24 +72,23 @@ if ($form->validate()) {
             if ($submit == 'back') {
                 header('Location: legal_add.php');
                 exit;
-            } elseif ($submit == 'save') {
-                $insert_result = LegalManager::add($lang, $content, $type, $changes);
-                if ($insert_result) {
-                    $message = get_lang('TermAndConditionSaved');
+            } elseif ($submit === 'save') {
+                $id = LegalManager::add($lang, $content, $type, $changes, $values);
+                if (!empty($id)) {
+                    Display::addFlash(Display::return_message(get_lang('TermAndConditionSaved'), 'success'));
                 } else {
-                    $message = get_lang('TermAndConditionNotSaved');
+                    Display::addFlash(Display::return_message(get_lang('TermAndConditionNotSaved'), 'warning'));
                 }
                 Security::clear_token();
                 $tok = Security::get_token();
-                Display::addFlash(Display::return_message($message));
                 header('Location: legal_list.php?sec_token='.$tok);
                 exit();
-            } elseif ($submit == 'preview') {
+            } elseif ($submit === 'preview') {
                 $defaults['type'] = $type;
                 $defaults['content'] = $content;
                 $defaults['changes'] = $changes;
                 $term_preview = $defaults;
-                $term_preview['type'] = intval($_POST['type']);
+                $term_preview['type'] = (int) $_POST['type'];
             } else {
                 $my_lang = $_POST['language'];
                 if (isset($_POST['language'])) {
@@ -103,6 +119,7 @@ $token = Security::get_token();
 $form->addElement('hidden', 'sec_token');
 $defaults['sec_token'] = $token;
 $form->addElement('header', get_lang('DisplayTermsConditions'));
+$jqueryReady = '';
 
 if (isset($_POST['language'])) {
     $form->addElement('static', Security::remove_XSS($_POST['language']));
@@ -117,7 +134,6 @@ if (isset($_POST['language'])) {
 
     $form->addElement('radio', 'type', '', get_lang('HTMLText'), '0');
     $form->addElement('radio', 'type', '', get_lang('PageLink'), '1');
-    $form->addElement('textarea', 'changes', get_lang('ExplainChanges'), ['width' => '20']);
 
     $preview = LegalManager::show_last_condition($term_preview);
 
@@ -126,6 +142,22 @@ if (isset($_POST['language'])) {
         $form->addElement('label', get_lang('Preview'), $preview);
     }
 
+    $termId = isset($term_preview['id']) ? $term_preview['id'] : 0;
+    $returnParams = $extraField->addElements(
+        $form,
+        $termId,
+        [],
+        false,
+        false,
+        [],
+        [],
+        true
+    );
+
+    $jqueryReady = $returnParams['jquery_ready_content'];
+
+    $form->addElement('textarea', 'changes', get_lang('ExplainChanges'), ['width' => '20']);
+
     // Submit & preview button
     $buttons = '<div class="row" align="center">
                 <div class="formw">
@@ -141,11 +173,19 @@ if (isset($_POST['language'])) {
 }
 
 $tool_name = get_lang('AddTermsAndConditions');
-$interbreadcrumb[] = ["url" => 'index.php', "name" => get_lang('PlatformAdmin')];
-Display :: display_header($tool_name);
+$interbreadcrumb[] = ['url' => 'index.php', 'name' => get_lang('PlatformAdmin')];
+
+// the $jquery_ready_content variable collects all functions that will be load in the $(document).ready javascript function
+$htmlHeadXtra[] = '<script>
+$(document).ready(function(){
+    '.$jqueryReady.'
+});
+</script>';
+
+Display::display_header($tool_name);
 
 echo '<script>
-function sendlang(){
+function sendlang() {
 	document.addlegal.sec_token.value=\''.$token.'\';
 	document.addlegal.submit();
 }
@@ -154,9 +194,10 @@ function sendlang(){
 // action menu
 echo '<div class="actions">';
 echo '<a href="'.api_get_path(WEB_CODE_PATH).'admin/legal_list.php">'.
-    Display::return_icon('search.gif', get_lang('EditTermsAndConditions'), '').get_lang('AllVersions').'</a>';
+    Display::return_icon('search.gif', get_lang('EditTermsAndConditions'), '').
+    get_lang('AllVersions').'</a>';
 echo '</div>';
 
 $form->setDefaults($defaults);
 $form->display();
-Display :: display_footer();
+Display::display_footer();

+ 7 - 6
main/admin/user_add.php

@@ -345,12 +345,12 @@ if ($form->validate()) {
         $email = $user['email'];
         $phone = $user['phone'];
         $username = $user['username'];
-        $status = intval($user['status']);
+        $status = (int) $user['status'];
         $language = $user['language'];
         $picture = $_FILES['picture'];
-        $platform_admin = intval($user['admin']['platform_admin']);
-        $send_mail = intval($user['mail']['send_mail']);
-        $hr_dept_id = isset($user['hr_dept_id']) ? intval($user['hr_dept_id']) : 0;
+        $platform_admin = (int) $user['admin']['platform_admin'];
+        $send_mail = (int) $user['mail']['send_mail'];
+        $hr_dept_id = isset($user['hr_dept_id']) ? (int) $user['hr_dept_id'] : 0;
 
         if (isset($extAuthSource) && count($extAuthSource) > 0 &&
             $user['password']['password_auto'] == '2'
@@ -368,14 +368,15 @@ if ($form->validate()) {
             $expiration_date = null;
         }
 
-        $active = intval($user['active']);
+        $active = (int) $user['active'];
         if (api_get_setting('login_is_email') == 'true') {
             $username = $email;
         }
 
         $extra = [];
         foreach ($user as $key => $value) {
-            if (substr($key, 0, 6) == 'extra_') { //an extra field
+            if (substr($key, 0, 6) == 'extra_') {
+                // An extra field
                 $extra[substr($key, 6)] = $value;
             }
         }

+ 1 - 0
main/admin/user_edit.php

@@ -329,6 +329,7 @@ $returnParams = $extraField->addElements(
 );
 $jqueryReadyContent = $returnParams['jquery_ready_content'];
 
+
 // the $jquery_ready_content variable collects all functions that will be load in the $(document).ready javascript function
 $htmlHeadXtra[] = '<script>
 $(document).ready(function(){

+ 6 - 6
main/admin/user_information.php

@@ -310,13 +310,13 @@ if (count($sessions) > 0) {
             }
 
             $tools = Display::url(
-                Display::return_icon('statistics.gif', get_lang('Stats')),
+                Display::return_icon('statistics.png', get_lang('Stats')),
                 api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?details=true&student='.$userId.'&id_session='.$id_session.'&course='.$courseInfo['code']
             );
             $tools .= '&nbsp;<a href="course_information.php?code='.$courseInfo['code'].'&id_session='.$id_session.'">'.
-                Display::return_icon('synthese_view.gif', get_lang('Overview')).'</a>'.
+                Display::return_icon('info2.png', get_lang('Overview')).'</a>'.
                 '<a href="'.$courseInfo['course_public_url'].'?id_session='.$id_session.'">'.
-                Display::return_icon('course_home.gif', get_lang('CourseHomepage')).'</a>';
+                Display::return_icon('course_home.png', get_lang('CourseHomepage')).'</a>';
 
             if (!empty($my_course['status']) && $my_course['status'] == STUDENT) {
                 $tools .= '<a href="user_information.php?action=unsubscribe_session_course&course_id='.$courseInfo['real_id'].'&user_id='.$userId.'&id_session='.$id_session.'">'.
@@ -423,14 +423,14 @@ if (Database::num_rows($res) > 0) {
         $courseToolInformation = null;
 
         $tools = Display::url(
-            Display::return_icon('statistics.gif', get_lang('Stats')),
+            Display::return_icon('statistics.png', get_lang('Stats')),
             api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?details=true&student='.$userId.'&id_session=0&course='.$courseCode
         );
 
         $tools .= '&nbsp;<a href="course_information.php?code='.$courseCode.'">'.
-            Display::return_icon('synthese_view.gif', get_lang('Overview')).'</a>'.
+            Display::return_icon('info2.png', get_lang('Overview')).'</a>'.
             '<a href="'.$courseInfo['course_public_url'].'">'.
-            Display::return_icon('course_home.gif', get_lang('CourseHomepage')).'</a>'.
+            Display::return_icon('course_home.png', get_lang('CourseHomepage')).'</a>'.
             '<a href="course_edit.php?id='.$course->c_id.'">'.
             Display::return_icon('edit.png', get_lang('Edit')).'</a>';
         if ($course->status == STUDENT) {

+ 5 - 11
main/admin/user_list.php

@@ -447,12 +447,6 @@ function get_user_data($from, $number_of_items, $column, $direction)
     $column = intval($column);
     $from = intval($from);
     $number_of_items = intval($number_of_items);
-
-    $preventSessionAdminsToManageAllUsers = api_get_setting('prevent_session_admins_to_manage_all_users');
-    if (api_is_session_admin() && $preventSessionAdminsToManageAllUsers === 'true') {
-        $sql .= " WHERE u.creator_id = ".api_get_user_id();
-    }
-
     $sql .= " ORDER BY col$column $direction ";
     $sql .= " LIMIT $from,$number_of_items";
 
@@ -575,9 +569,9 @@ function modify_filter($user_id, $url_params, $row)
     if (api_is_platform_admin()) {
         if (!$user_is_anonymous) {
             $result .= '<a href="user_information.php?user_id='.$user_id.'">'.
-                        Display::return_icon('synthese_view.gif', get_lang('Info')).'</a>&nbsp;&nbsp;';
+                        Display::return_icon('info2.png', get_lang('Info')).'</a>&nbsp;&nbsp;';
         } else {
-            $result .= Display::return_icon('synthese_view_na.gif', get_lang('Info')).'&nbsp;&nbsp;';
+            $result .= Display::return_icon('info2_na.png', get_lang('Info')).'&nbsp;&nbsp;';
         }
     }
 
@@ -599,12 +593,12 @@ function modify_filter($user_id, $url_params, $row)
 
     if ($current_user_status_label != $statusname[STUDENT]) {
         $result .= Display::return_icon(
-            'statistics_na.gif',
+            'statistics_na.png',
             get_lang('Reporting')
         ).'&nbsp;';
     } else {
         $result .= '<a href="../mySpace/myStudents.php?student='.$user_id.'">'.
-            Display::return_icon('statistics.gif', get_lang('Reporting')).
+            Display::return_icon('statistics.png', get_lang('Reporting')).
             '</a>&nbsp;';
     }
 
@@ -876,7 +870,7 @@ if (!empty($action)) {
                 exit;
                 break;
             case 'delete':
-                if (api_is_platform_admin()) {
+                if (api_is_platform_admin() && !empty($_POST['id'])) {
                     $number_of_selected_users = count($_POST['id']);
                     $number_of_affected_users = 0;
                     if (is_array($_POST['id'])) {

+ 4 - 5
main/admin/user_list_consent.php

@@ -311,10 +311,10 @@ function modify_filter($user_id, $url_params, $row)
     $token = Security::getTokenFromSession();
     $result = '';
     $result .= '<a href="user_information.php?user_id='.$user_id.'">'.
-        Display::return_icon('synthese_view.gif', get_lang('Info')).'</a>&nbsp;&nbsp;';
+        Display::return_icon('info2.png', get_lang('Info')).'</a>&nbsp;&nbsp;';
 
     $result .= Display::url(
-        Display::return_icon('message_new.png'),
+        Display::return_icon('message_new.png', get_lang('SendMessage')),
         api_get_path(WEB_CODE_PATH).'messages/new_message.php?send_to_user='.$user_id
     );
     $result .= '&nbsp;&nbsp;';
@@ -323,7 +323,7 @@ function modify_filter($user_id, $url_params, $row)
 
     if ($row[10] == $extraFieldId) {
         $result .= Display::url(
-            get_lang('RemoveTerms'),
+            Display::return_icon('delete_terms.png', get_lang('RemoveTerms')),
             api_get_self().'?user_id='.$user_id.'&action=delete_terms&sec_token='.$token
         );
         $result .= '&nbsp;&nbsp;';
@@ -633,8 +633,7 @@ if (api_is_platform_admin() &&
 ) {
     $actionsList['delete'] = get_lang('DeleteFromPlatform');
 }
-$actionsList['disable'] = get_lang('Disable');
-$actionsList['enable'] = get_lang('Enable');
+
 $table->set_form_actions($actionsList);
 
 $table_result = $table->return_table();

+ 1 - 0
main/auth/inscription.php

@@ -521,6 +521,7 @@ if (api_get_setting('allow_terms_conditions') === 'true' && $user_already_regist
         }
     }
 
+    // Get user language ignoring the platform language
     $language = api_get_interface_language();
     $language = api_get_language_id($language);
     $term_preview = LegalManager::get_last_condition($language);

+ 1 - 0
main/auth/profile.php

@@ -362,6 +362,7 @@ if (is_profile_editable()) {
     $form->freeze();
 }
 
+// Student cannot modified their user conditions
 $extraConditions = api_get_configuration_value('show_conditions_to_user');
 if ($extraConditions && isset($extraConditions['conditions'])) {
     $extraConditions = $extraConditions['conditions'];

+ 3 - 0
main/calendar/ical_export.php

@@ -1,6 +1,9 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use kigkonsult\iCalcreator\vcalendar;
+use kigkonsult\iCalcreator\vevent;
+
 /**
  * This file exclusively export calendar items to iCal or similar formats.
  *

+ 28 - 17
main/coursecopy/copy_course_session.php

@@ -294,17 +294,22 @@ if (Security::check_token('post') && (
         $destination_session = $_POST['destination_session'];
         $origin_session = $_POST['origin_session'];
 
-        $course = CourseSelectForm::get_posted_course(
-            'copy_course',
-            $origin_session,
-            $origin_course
-        );
+        if ($course_code != $origin_course) {
+            $course = CourseSelectForm::get_posted_course(
+                'copy_course',
+                $origin_session,
+                $origin_course
+            );
 
-        $cr = new CourseRestorer($course);
-        //$cr->set_file_option($_POST['same_file_name_option']);
-        $cr->restore($destination_course, $destination_session);
-        echo Display::return_message(get_lang('CopyFinished'), 'confirmation');
-        display_form();
+            $cr = new CourseRestorer($course);
+            //$cr->set_file_option($_POST['same_file_name_option']);
+            $cr->restore($destination_course, $destination_session);
+            echo Display::return_message(get_lang('CopyFinished'), 'confirmation');
+            display_form();
+        } else {
+            echo Display::return_message(get_lang('PleaseSelectACourse'), 'confirm');
+            display_form();
+        }
     } else {
         $arr_course_origin = [];
         $arr_course_destination = [];
@@ -333,14 +338,20 @@ if (Security::check_token('post') && (
                 $course_code = $arr_course_origin[0];
                 $course_destinatination = $arr_course_destination[0];
 
-                $course_origin = api_get_course_info($course_code);
-                $cb = new CourseBuilder('', $course_origin);
-                $course = $cb->build($origin_session, $course_code, $with_base_content);
-                $cr = new CourseRestorer($course);
-                $cr->restore($course_destinatination, $destination_session);
+                if ($course_code != $course_destinatination) {
+                    $course_origin = api_get_course_info($course_code);
+                    $cb = new CourseBuilder('', $course_origin);
+                    $course = $cb->build($origin_session, $course_code, $with_base_content);
+                    $cr = new CourseRestorer($course);
+                    $cr->restore($course_destinatination, $destination_session);
+
+                    echo Display::return_message(get_lang('CopyFinished'), 'confirm');
+                    display_form();
+                } else {
+                    echo Display::return_message(get_lang('PleaseSelectACourse'), 'confirm');
+                    display_form();
+                }
             }
-            echo Display::return_message(get_lang('CopyFinished'), 'confirm');
-            display_form();
         } else {
             echo Display::return_message(get_lang('YouMustSelectACourseFromOriginalSession'), 'error');
             display_form();

+ 8 - 0
main/inc/lib/course.lib.php

@@ -95,6 +95,9 @@ class CourseManager
     public static function create_course($params, $authorId = 0)
     {
         global $_configuration;
+
+        $hook = HookCreateCourse::create();
+
         // Check portal limits
         $access_url_id = 1;
         if (api_get_multiple_access_url()) {
@@ -152,6 +155,11 @@ class CourseManager
                 $courseId = AddCourse::register_course($params);
                 $courseInfo = api_get_course_info_by_id($courseId);
 
+                if ($hook) {
+                    $hook->setEventData(['course_info' => $course_info]);
+                    $hook->notifyCreateCourse(HOOK_EVENT_TYPE_POST);
+                }
+
                 if (!empty($courseInfo)) {
                     self::fillCourse($courseInfo, $params, $authorId);
 

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

@@ -1828,7 +1828,7 @@ HOTSPOT;
         );
         $TBL_USER = Database::get_main_table(TABLE_MAIN_USER);
 
-        $sql = "SELECT * FROM $TBL_TRACK_HOTPOTATOES thp
+        $sql = "SELECT *, thp.id AS thp_id FROM $TBL_TRACK_HOTPOTATOES thp
             JOIN $TBL_USER u ON thp.exe_user_id = u.user_id
             WHERE thp.c_id = $courseId AND exe_name LIKE '$in_hotpot_path%'";
 
@@ -1853,7 +1853,7 @@ HOTSPOT;
             $actions = null;
 
             if ($apiIsAllowedToEdit) {
-                $url = $urlBase.$data['id'].'&path='.$data['exe_name'];
+                $url = $urlBase.$data['thp_id'].'&path='.$data['exe_name'];
                 $actions = Display::url(
                     Display::return_icon('delete.png', get_lang('Delete')),
                     $url

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

@@ -1021,7 +1021,7 @@ class SocialManager extends UserManager
                 ';
             }
 
-            if (api_get_configuration_value('enable_gdpr')) {
+            if (!api_get_configuration_value('disable_gdpr')) {
                 $active = $show == 'personal-data' ? 'active' : null;
                 $personalData = '
                     <li class="personal-data-icon '.$active.'">

+ 294 - 65
main/inc/lib/template.lib.php

@@ -695,13 +695,17 @@ class Template
             'bootstrap-select/dist/css/bootstrap-select.min.css',
             'select2/dist/css/select2.min.css',
             'flag-icon-css/css/flag-icon.min.css',
+            'mediaelement/plugins/vrview/vrview.css',
         ];
 
         $features = api_get_configuration_value('video_features');
-        $defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen'];
+        $defaultFeatures = ['playpause', 'current', 'progress', 'duration', 'tracks', 'volume', 'fullscreen', 'vrview'];
 
         if (!empty($features) && isset($features['features'])) {
             foreach ($features['features'] as $feature) {
+                if ($feature === 'vrview') {
+                    continue;
+                }
                 $bowerCSSFiles[] = "mediaelement/plugins/$feature/$feature.css";
                 $defaultFeatures[] = $feature;
             }
@@ -840,7 +844,7 @@ class Template
             }
         }
 
-        if (api_get_setting('accessibility_font_resize') == 'true') {
+        if (api_get_setting('accessibility_font_resize') === 'true') {
             $js_files[] = 'fontresize.js';
         }
 
@@ -850,6 +854,7 @@ class Template
             'jquery/dist/jquery.min.js',
             'bootstrap/dist/js/bootstrap.min.js',
             'jquery-ui/jquery-ui.min.js',
+            'jqueryui-touch-punch/jquery.ui.touch-punch.min.js',
             'moment/min/moment-with-locales.js',
             'bootstrap-daterangepicker/daterangepicker.js',
             'jquery-timeago/jquery.timeago.js',
@@ -862,20 +867,24 @@ class Template
             $selectLink,
             'select2/dist/js/select2.min.js',
             "select2/dist/js/i18n/$isoCode.js",
+            'mediaelement/plugins/vrview/vrview.js',
         ];
 
         $features = api_get_configuration_value('video_features');
         if (!empty($features) && isset($features['features'])) {
             foreach ($features['features'] as $feature) {
+                if ($feature === 'vrview') {
+                    continue;
+                }
                 $bowerJsFiles[] = "mediaelement/plugins/$feature/$feature.js";
             }
         }
 
-        if (CHAMILO_LOAD_WYSIWYG == true) {
+        if (CHAMILO_LOAD_WYSIWYG === true) {
             $bowerJsFiles[] = 'ckeditor/ckeditor.js';
         }
 
-        if (api_get_setting('include_asciimathml_script') == 'true') {
+        if (api_get_setting('include_asciimathml_script') === 'true') {
             $bowerJsFiles[] = 'MathJax/MathJax.js?config=TeX-MML-AM_HTMLorMML';
         }
 
@@ -901,17 +910,41 @@ class Template
         if (!$disable_js_and_css_files) {
             $this->assign('js_file_to_string', $js_file_to_string);
 
-            $extra_headers = '<script>var _p = '.json_encode($this->getWebPaths(), JSON_PRETTY_PRINT).'</script>';
+            $extraHeaders = '<script>var _p = '.json_encode($this->getWebPaths(), JSON_PRETTY_PRINT).'</script>';
             //Adding jquery ui by default
-            $extra_headers .= api_get_jquery_ui_js();
-
-            //$extra_headers = '';
+            $extraHeaders .= api_get_jquery_ui_js();
             if (isset($htmlHeadXtra) && $htmlHeadXtra) {
                 foreach ($htmlHeadXtra as &$this_html_head) {
-                    $extra_headers .= $this_html_head."\n";
+                    $extraHeaders .= $this_html_head."\n";
                 }
             }
-            $this->assign('extra_headers', $extra_headers);
+
+            $ajax = api_get_path(WEB_AJAX_PATH);
+            $courseId = api_get_course_id();
+            if (empty($courseId)) {
+                $courseLogoutCode = '
+                <script>
+                function courseLogout() {
+                }
+                </script>';
+            } else {
+                $courseLogoutCode = "
+                <script>
+                var logOutUrl = '".$ajax."course.ajax.php?a=course_logout&".api_get_cidreq()."';
+                function courseLogout() {                
+                    $.ajax({
+                        async : false,
+                        url: logOutUrl,
+                        success: function (data) {
+                            return 1;
+                        }
+                    });
+                }
+                </script>";
+            }
+
+            $extraHeaders .= $courseLogoutCode;
+            $this->assign('extra_headers', $extraHeaders);
         }
     }
 
@@ -932,8 +965,7 @@ class Template
                 $js_files[] = 'chat/js/chat.js';
             }
         }
-        $js_file_to_string = null;
-
+        $js_file_to_string = '';
         foreach ($js_files as $js_file) {
             $js_file_to_string .= api_get_js($js_file);
         }
@@ -1371,6 +1403,59 @@ class Template
         return $this->responseCode;
     }
 
+    /**
+     * Assign HTML code to the 'bug_notification' template variable for the side tabs to report issues.
+     *
+     * @return bool Always return true because there is always a string, even if empty
+     */
+    public function assignBugNotification()
+    {
+        //@todo move this in the template
+        $rightFloatMenu = '';
+        $iconBug = Display::return_icon(
+            'bug.png',
+            get_lang('ReportABug'),
+            [],
+            ICON_SIZE_LARGE
+        );
+        if (api_get_setting('show_link_bug_notification') === 'true' && $this->user_is_logged_in) {
+            $rightFloatMenu = '<div class="report">
+		        <a href="https://github.com/chamilo/chamilo-lms/wiki/How-to-report-issues" target="_blank">
+                    '.$iconBug.'
+                </a>
+		        </div>';
+        }
+
+        if (api_get_setting('show_link_ticket_notification') === 'true' &&
+            $this->user_is_logged_in
+        ) {
+            // by default is project_id = 1
+            $defaultProjectId = 1;
+            $iconTicket = Display::return_icon(
+                'help.png',
+                get_lang('Ticket'),
+                [],
+                ICON_SIZE_LARGE
+            );
+            $courseInfo = api_get_course_info();
+            $courseParams = '';
+            if (!empty($courseInfo)) {
+                $courseParams = api_get_cidreq();
+            }
+            $url = api_get_path(WEB_CODE_PATH).
+                'ticket/tickets.php?project_id='.$defaultProjectId.'&'.$courseParams;
+            $rightFloatMenu .= '<div class="help">
+                <a href="'.$url.'" target="_blank">
+                    '.$iconTicket.'
+                </a>
+            </div>';
+        }
+
+        $this->assign('bug_notification', $rightFloatMenu);
+
+        return true;
+    }
+
     /**
      * Prepare the _c array for template files. The _c array contains
      * information about the current course.
@@ -1434,6 +1519,9 @@ class Template
      */
     private function getWebPaths()
     {
+        $queryString = empty($_SERVER['QUERY_STRING']) ? '' : $_SERVER['QUERY_STRING'];
+        $requestURI = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI'];
+
         return [
             'web' => api_get_path(WEB_PATH),
             'web_url' => api_get_web_url(),
@@ -1448,9 +1536,10 @@ class Template
             'web_lib' => api_get_path(WEB_LIBRARY_PATH),
             'web_upload' => api_get_path(WEB_UPLOAD_PATH),
             'web_self' => api_get_self(),
-            'web_query_vars' => api_htmlentities($_SERVER['QUERY_STRING']),
-            'web_self_query_vars' => api_htmlentities($_SERVER['REQUEST_URI']),
+            'web_query_vars' => api_htmlentities($queryString),
+            'web_self_query_vars' => api_htmlentities($requestURI),
             'web_cid_query' => api_get_cidreq(),
+            'web_rel_code' => api_get_path(REL_CODE_PATH),
         ];
     }
 
@@ -1538,59 +1627,13 @@ class Template
         $this->assign('prefetch', $prefetch);
         $this->assign('text_direction', api_get_text_direction());
         $this->assign('section_name', 'section-'.$this_section);
-        $this->assign('favico', $this->getPortalIcon($this->theme));
-        $this->setHelp();
-
-        //@todo move this in the template
-        $rightFloatMenu = '';
-        $iconBug = Display::return_icon(
-            'bug.png',
-            get_lang('ReportABug'),
-            [],
-            ICON_SIZE_LARGE
-        );
-        if (api_get_setting('show_link_bug_notification') == 'true' && $this->user_is_logged_in) {
-            $rightFloatMenu = '<div class="report">
-		        <a href="https://github.com/chamilo/chamilo-lms/wiki/How-to-report-issues" target="_blank">
-                    '.$iconBug.'
-                </a>
-		        </div>';
-        }
 
-        if (api_get_setting('show_link_ticket_notification') == 'true' && $this->user_is_logged_in) {
-            // by default is project_id = 1
-            $iconTicket = Display::return_icon(
-                'help.png',
-                get_lang('Ticket'),
-                [],
-                ICON_SIZE_LARGE
-            );
-            $courseInfo = api_get_course_info();
-            $courseParams = '';
-            if (!empty($courseInfo)) {
-                $courseParams = api_get_cidreq();
-            }
-            $url = api_get_path(WEB_CODE_PATH).'ticket/tickets.php?project_id=1&'.$courseParams;
-            $rightFloatMenu .= '<div class="help">
-		        <a href="'.$url.'" target="_blank">
-                    '.$iconTicket.'
-                </a>
-		    </div>';
-        }
+        $this->assignFavIcon(); //Set a 'favico' var for the template
+        $this->setHelp();
 
-        $this->assign('bug_notification', $rightFloatMenu);
+        $this->assignBugNotification(); //Prepare the 'bug_notification' var for the template
 
-        $resize = '';
-        if (api_get_setting('accessibility_font_resize') == 'true') {
-            $resize .= '<div class="resize_font">';
-            $resize .= '<div class="btn-group">';
-            $resize .= '<a title="'.get_lang('DecreaseFontSize').'" href="#" class="decrease_font btn btn-default"><em class="fa fa-font"></em></a>';
-            $resize .= '<a title="'.get_lang('ResetFontSize').'" href="#" class="reset_font btn btn-default"><em class="fa fa-font"></em></a>';
-            $resize .= '<a title="'.get_lang('IncreaseFontSize').'" href="#" class="increase_font btn btn-default"><em class="fa fa-font"></em></a>';
-            $resize .= '</div>';
-            $resize .= '</div>';
-        }
-        $this->assign('accessibility', $resize);
+        $this->assignAccessibilityBlock(); //Prepare the 'accessibility' var for the template
 
         // Preparing values for the menu
 
@@ -1621,8 +1664,14 @@ class Template
         $this->assign('message_link', $message_link);
         $this->assign('message_url', $message_url);
 
-        // Certificate Link
+        $pendingSurveyLink = '';
+        $show = api_get_configuration_value('show_pending_survey_in_menu');
+        if ($show) {
+            $pendingSurveyLink = api_get_path(WEB_CODE_PATH).'survey/pending.php';
+        }
+        $this->assign('pending_survey_url', $pendingSurveyLink);
 
+        // Certificate Link
         $allow = api_get_configuration_value('hide_my_certificate_link');
         if ($allow === false) {
             $certificateUrl = api_get_path(WEB_CODE_PATH).'gradebook/my_certificates.php';
@@ -1880,4 +1929,184 @@ class Template
         }
         // end of HTTP headers security block
     }
+
+    /**
+     * Assign favicon to the 'favico' template variable.
+     *
+     * @return bool Always return true because there is always at least one correct favicon.ico
+     */
+    private function assignFavIcon()
+    {
+        // Default root chamilo favicon
+        $favico = '<link rel="shortcut icon" href="'.api_get_path(WEB_PATH).'favicon.ico" type="image/x-icon" />';
+
+        //Added to verify if in the current Chamilo Theme exist a favicon
+        $favicoThemeUrl = api_get_path(SYS_CSS_PATH).$this->themeDir.'images/';
+
+        //If exist pick the current chamilo theme favicon
+        if (is_file($favicoThemeUrl.'favicon.ico')) {
+            $favico = '<link rel="shortcut icon" href="'.api_get_path(WEB_CSS_PATH).$this->themeDir.'images/favicon.ico" type="image/x-icon" />';
+        }
+
+        if (api_is_multiple_url_enabled()) {
+            $access_url_id = api_get_current_access_url_id();
+            if ($access_url_id != -1) {
+                $url_info = api_get_access_url($access_url_id);
+                $url = api_remove_trailing_slash(
+                    preg_replace('/https?:\/\//i', '', $url_info['url'])
+                );
+                $clean_url = api_replace_dangerous_char($url);
+                $clean_url = str_replace('/', '-', $clean_url);
+                $clean_url .= '/';
+                $homep = api_get_path(REL_PATH).'home/'.$clean_url; //homep for Home Path
+                $icon_real_homep = api_get_path(SYS_APP_PATH).'home/'.$clean_url;
+                //we create the new dir for the new sites
+                if (is_file($icon_real_homep.'favicon.ico')) {
+                    $favico = '<link rel="shortcut icon" href="'.$homep.'favicon.ico" type="image/x-icon" />';
+                }
+            }
+        }
+
+        $this->assign('favico', $favico);
+
+        return true;
+    }
+
+    /**
+     * Assign HTML code to the 'accessibility' template variable (usually shown above top menu).
+     *
+     * @return bool Always return true (even if empty string)
+     */
+    private function assignAccessibilityBlock()
+    {
+        $resize = '';
+        if (api_get_setting('accessibility_font_resize') == 'true') {
+            $resize .= '<div class="resize_font">';
+            $resize .= '<div class="btn-group">';
+            $resize .= '<a title="'.get_lang('DecreaseFontSize').'" href="#" class="decrease_font btn btn-default"><em class="fa fa-font"></em></a>';
+            $resize .= '<a title="'.get_lang('ResetFontSize').'" href="#" class="reset_font btn btn-default"><em class="fa fa-font"></em></a>';
+            $resize .= '<a title="'.get_lang('IncreaseFontSize').'" href="#" class="increase_font btn btn-default"><em class="fa fa-font"></em></a>';
+            $resize .= '</div>';
+            $resize .= '</div>';
+        }
+        $this->assign('accessibility', $resize);
+
+        return true;
+    }
+
+    /**
+     * Assign HTML code to the 'social_meta' template variable (usually shown above top menu).
+     *
+     * @return bool Always return true (even if empty string)
+     */
+    private function assignSocialMeta()
+    {
+        $socialMeta = '';
+        $metaTitle = api_get_setting('meta_title');
+        if (!empty($metaTitle)) {
+            $socialMeta .= '<meta name="twitter:card" content="summary" />'."\n";
+            $metaSite = api_get_setting('meta_twitter_site');
+            if (!empty($metaSite)) {
+                $socialMeta .= '<meta name="twitter:site" content="'.$metaSite.'" />'."\n";
+                $metaCreator = api_get_setting('meta_twitter_creator');
+                if (!empty($metaCreator)) {
+                    $socialMeta .= '<meta name="twitter:creator" content="'.$metaCreator.'" />'."\n";
+                }
+            }
+
+            // The user badge page emits its own meta tags, so if this is
+            // enabled, ignore the global ones
+            $userId = isset($_GET['user']) ? intval($_GET['user']) : 0;
+            $skillId = isset($_GET['skill']) ? intval($_GET['skill']) : 0;
+
+            if (!$userId && !$skillId) {
+                // no combination of user and skill ID has been defined,
+                // so print the normal or course-specific OpenGraph meta tags
+                // Check for a course ID
+                $courseId = api_get_course_int_id();
+                // Check session ID from session/id/about (see .htaccess)
+                $sessionId = isset($_GET['session_id']) ? intval($_GET['session_id']) : 0;
+
+                if ($courseId != false) {
+                    // If we are inside a course (even if within a session), publish info about the course
+                    $course = api_get_course_entity($courseId);
+                    // @TODO: support right-to-left in title
+                    $socialMeta .= '<meta property="og:title" content="'.$course->getTitle().' - '.$metaTitle.'" />'."\n";
+                    $socialMeta .= '<meta property="og:url" content="'.api_get_course_url($course->getCode()).'" />'."\n";
+
+                    $metaDescription = api_get_setting('meta_description');
+                    if (!empty($course->getDescription())) {
+                        $socialMeta .= '<meta property="og:description" content="'.$course->getDescription().'" />'."\n";
+                    } elseif (!empty($metaDescription)) {
+                        $socialMeta .= '<meta property="og:description" content="'.$metaDescription.'" />'."\n";
+                    }
+
+                    $picture = CourseManager::getPicturePath($course, true);
+                    if (!empty($picture)) {
+                        $socialMeta .= '<meta property="og:image" content="'.$picture.'" />'."\n";
+                    } else {
+                        $socialMeta .= $this->getMetaPortalImagePath();
+                    }
+                } elseif ($sessionId !== 0) {
+                    // If we are on a session "about" screen, publish info about the session
+                    $em = Database::getManager();
+                    $session = $em->find('ChamiloCoreBundle:Session', $sessionId);
+
+                    $socialMeta .= '<meta property="og:title" content="'.$session->getName().' - '.$metaTitle.'" />'."\n";
+                    $socialMeta .= '<meta property="og:url" content="'.api_get_path(WEB_PATH)."session/{$session->getId()}/about/".'" />'."\n";
+
+                    $sessionValues = new ExtraFieldValue('session');
+                    $sessionImage = $sessionValues->get_values_by_handler_and_field_variable($session->getId(), 'image')['value'];
+                    $sessionImageSysPath = api_get_path(SYS_UPLOAD_PATH).$sessionImage;
+
+                    if (!empty($sessionImage) && is_file($sessionImageSysPath)) {
+                        $sessionImagePath = api_get_path(WEB_UPLOAD_PATH).$sessionImage;
+                        $socialMeta .= '<meta property="og:image" content="'.$sessionImagePath.'" />'."\n";
+                    } else {
+                        $socialMeta .= $this->getMetaPortalImagePath();
+                    }
+                } else {
+                    // Otherwise (not a course nor a session, nor a user, nor a badge), publish portal info
+                    $socialMeta .= '<meta property="og:title" content="'.$metaTitle.'" />'."\n";
+                    $socialMeta .= '<meta property="og:url" content="'.api_get_path(WEB_PATH).'" />'."\n";
+
+                    $metaDescription = api_get_setting('meta_description');
+                    if (!empty($metaDescription)) {
+                        $socialMeta .= '<meta property="og:description" content="'.$metaDescription.'" />'."\n";
+                    }
+                    $socialMeta .= $this->getMetaPortalImagePath();
+                }
+            }
+        }
+
+        $this->assign('social_meta', $socialMeta);
+
+        return true;
+    }
+
+    /**
+     * Get platform meta image tag (check meta_image_path setting, then use the logo).
+     *
+     * @return string The meta image HTML tag, or empty
+     */
+    private function getMetaPortalImagePath()
+    {
+        // Load portal meta image if defined
+        $metaImage = api_get_setting('meta_image_path');
+        $metaImageSysPath = api_get_path(SYS_PATH).$metaImage;
+        $metaImageWebPath = api_get_path(WEB_PATH).$metaImage;
+        $portalImageMeta = '';
+        if (!empty($metaImage)) {
+            if (is_file($metaImageSysPath)) {
+                $portalImageMeta = '<meta property="og:image" content="'.$metaImageWebPath.'" />'."\n";
+            }
+        } else {
+            $logo = ChamiloApi::getPlatformLogoPath($this->theme);
+            if (!empty($logo)) {
+                $portalImageMeta = '<meta property="og:image" content="'.$logo.'" />'."\n";
+            }
+        }
+
+        return $portalImageMeta;
+    }
 }

+ 1 - 8
main/session/add_many_session_to_category.php

@@ -29,9 +29,6 @@ $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
 
 // setting the name of the tool
 $tool_name = get_lang('SubscribeSessionsToCategory');
-$id_session = isset($_GET['id_session']) ? intval($_GET['id_session']) : null;
-
-SessionManager::protectSession($id_session);
 
 $add_type = 'multiple';
 if (isset($_GET['add_type']) && $_GET['add_type'] != '') {
@@ -39,11 +36,7 @@ if (isset($_GET['add_type']) && $_GET['add_type'] != '') {
 }
 
 if (!api_is_platform_admin() && !api_is_session_admin()) {
-    $sql = 'SELECT session_admin_id FROM '.Database::get_main_table(TABLE_MAIN_SESSION).' WHERE id='.$id_session;
-    $rs = Database::query($sql);
-    if (Database::result($rs, 0, 0) != $_user['user_id']) {
-        api_not_allowed(true);
-    }
+    api_not_allowed(true);
 }
 
 $xajax->processRequests();

+ 3 - 3
main/session/resume_session.php

@@ -19,7 +19,7 @@ require_once __DIR__.'/../inc/global.inc.php';
 // setting the section (for the tabs)
 $this_section = SECTION_PLATFORM_ADMIN;
 
-$sessionId = isset($_GET['id_session']) ? intval($_GET['id_session']) : null;
+$sessionId = isset($_GET['id_session']) ? (int) $_GET['id_session'] : null;
 
 if (empty($sessionId)) {
     api_not_allowed(true);
@@ -113,7 +113,7 @@ switch ($action) {
 }
 
 $sessionHeader = Display::page_header(
-    Display::return_icon('session.png', get_lang('Session')).' '.$sessionInfo['name'],
+    Display::return_icon('session.png', get_lang('Session')).' '.$session->getName(),
     null,
     'h3'
 );
@@ -149,7 +149,7 @@ $courseListToShow .= '<table id="session-list-course" class="data_table">
   <th width="25%">'.get_lang('Actions').'</th>
 </tr>';
 
-if ($sessionInfo['nbr_courses'] == 0) {
+if ($session->getNbrCourses() === 0) {
     $courseListToShow .= '<tr>
 			<td colspan="4">'.get_lang('NoCoursesForThisSession').'</td>
 		</tr>';

+ 12 - 2
main/social/personal_data.php

@@ -12,12 +12,16 @@ require_once __DIR__.'/../inc/global.inc.php';
 
 api_block_anonymous_users();
 
-if (!api_get_configuration_value('enable_gdpr')) {
+if (api_get_configuration_value('disable_gdpr')) {
     api_not_allowed(true);
 }
 
 $userId = api_get_user_id();
-$userInfo = api_get_user_info();
+$userInfo = api_get_user_info($userId);
+
+if (empty($userInfo)) {
+    api_not_allowed(true);
+}
 
 $substitutionTerms = [
     'password' => get_lang('EncryptedData'),
@@ -93,6 +97,8 @@ switch ($action) {
             }
         }
         Display::addFlash(Display::return_message(get_lang('Saved')));
+        header('Location: '.api_get_self());
+        exit;
         break;
     case 'delete_account':
         if ($formDelete->validate()) {
@@ -146,6 +152,8 @@ switch ($action) {
             } else {
                 MessageManager::sendMessageToAllAdminUsers(api_get_user_id(), $subject, $content);
             }
+            header('Location: '.api_get_self());
+            exit;
         }
         break;
     case 'delete_legal':
@@ -197,6 +205,8 @@ switch ($action) {
             } else {
                 MessageManager::sendMessageToAllAdminUsers(api_get_user_id(), $subject, $content);
             }
+            header('Location: '.api_get_self());
+            exit;
         }
         break;
 }

+ 0 - 48
plugin/bbb/view/admin.tpl

@@ -1,48 +0,0 @@
-{{ search_form }}
-<table class="table table-hover table-striped">
-    <thead>
-        <tr>
-            <th>{{ 'CreatedAt'|get_lang }}</th>
-            <th>{{ 'Status'|get_lang }}</th>
-            <th>{{ 'Records'|get_plugin_lang('BBBPlugin') }}</th>
-            <th>{{ 'Course'|get_lang }}</th>
-            <th>{{ 'Session'|get_lang }}</th>
-            <th>{{ 'Participants'|get_lang }}</th>
-            <th>{{ 'Actions'|get_lang }}</th>
-        </tr>
-    </thead>
-        <tbody>
-        {% for meeting in meetings %}
-            <tr id="meeting-{{ meeting.id }}">
-                {% if meeting.visibility == 0 %}
-                    <td class="muted">{{ meeting.created_at }}</td>
-                {% else %}
-                    <td>{{ meeting.created_at }}</td>
-                {% endif %}
-                <td>
-                    {% if meeting.status == 1 %}
-                        <span class="label label-success">{{ 'MeetingOpened'|get_plugin_lang('BBBPlugin') }}</span>
-                    {% else %}
-                        <span class="label label-info">{{ 'MeetingClosed'|get_plugin_lang('BBBPlugin') }}</span>
-                    {% endif %}
-                </td>
-                <td>
-                    {% if meeting.record == 1 %}
-                        {# Record list #}
-                        {{ meeting.show_links }}
-                    {% else %}
-                        {{ 'NoRecording'|get_plugin_lang('BBBPlugin') }}
-                    {% endif %}
-                </td>
-                <td>{{ meeting.course ?: '-' }}</td>
-                <td>{{ meeting.session ?: '-' }}</td>
-                <td>
-                    {{ meeting.participants ? meeting.participants|join('<br>') : '-' }}
-                </td>
-                <td>
-                    {{ meeting.action_links }}
-                </td>
-            </tr>
-        {% endfor %}
-    </tbody>
-</table>

+ 0 - 176
plugin/bbb/view/listing.tpl

@@ -1,176 +0,0 @@
-<style type="text/css">
-    .conference .url{
-        padding: 5px;
-        margin-bottom: 5px;
-    }
-    .conference .share{
-        padding: 10px;
-        margin-top: 5px;
-        margin-bottom: 5px;
-        font-weight: bold;
-    }
-</style>
-<div class ="row">
-{% if bbb_status == true %}
-    <div class ="col-md-12" style="text-align:center">
-        {{ form }}
-        {% if show_join_button == true %}
-            {% if show_client_options %}
-                <div class="row">
-                    <div class="col-md-6">
-                        <div class="panel panel-default conference">
-                            <div class="panel-body">
-                                <div class="url">
-                                    <a class="btn btn-default" href="{{ enter_conference_links.0.url }}">
-                                        <img src="{{ enter_conference_links.0.icon }}" /><br>
-                                        {{ enter_conference_links.0.text }}
-                                    </a>
-                                </div>
-                                <div class="share">
-                                    {{ 'UrlMeetingToShare'| get_plugin_lang('BBBPlugin') }}
-                                </div>
-                                <div class="form-inline">
-                                    <div class="form-group">
-                                        <input id="share_button_flash" type="text"
-                                               style="width:300px"
-                                               class="form-control" readonly value="{{ conference_url }}&inteface=0">
-                                        <button onclick="copyTextToClipBoard('share_button_flash');" class="btn btn-default">
-                                            <span class="fa fa-copy"></span> {{ 'CopyTextToClipboard' | get_lang }}
-                                        </button>
-                                    </div>
-                                </div>
-                            </div>
-
-                        </div>
-                    </div>
-                    <div class="col-md-6">
-                        <div class="panel panel-default conference">
-                            <div class="panel-body">
-                                <div class="url">
-                                    <a class="btn btn-default" href="{{ enter_conference_links.1.url }}">
-                                        <img src="{{ enter_conference_links.1.icon }}" /><br>
-                                        {{ enter_conference_links.1.text }}
-                                    </a>
-                                </div>
-                                <div class="share">
-                                    {{ 'UrlMeetingToShare'| get_plugin_lang('BBBPlugin') }}
-                                </div>
-                                <div class="form-inline">
-                                    <div class="form-group">
-                                        <input id="share_button_html" type="text"
-                                               style="width:300px"
-                                               class="form-control" readonly value="{{ conference_url }}&inteface=1">
-
-                                        <button onclick="copyTextToClipBoard('share_button_html');" class="btn btn-default">
-                                            <span class="fa fa-copy"></span> {{ 'CopyTextToClipboard' | get_lang }}
-                                        </button>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                </div>
-            {% else %}
-                {{ enter_conference_links.0 }}
-                <br />
-                <strong>{{ 'UrlMeetingToShare'| get_plugin_lang('BBBPlugin') }}</strong>
-                <div class="well">
-                    <div class="form-inline">
-                        <div class="form-group">
-                            <input id="share_button"
-                                   type="text"
-                                   style="width:600px"
-                                   class="form-control" readonly value="{{ conference_url }}">
-                            <button onclick="copyTextToClipBoard('share_button');" class="btn btn-default">
-                                <span class="fa fa-copy"></span> {{ 'CopyTextToClipboard' | get_lang }}
-                            </button>
-                        </div>
-                    </div>
-                </div>
-            {% endif %}
-            <p>
-                <span id="users_online" class="label label-warning">
-                    {{ 'XUsersOnLine'| get_plugin_lang('BBBPlugin') | format(users_online) }}
-                </span>
-            </p>
-            {{ warning_inteface_msg }}
-            {% if max_users_limit > 0 %}
-                {% if conference_manager == true %}
-                    <p>{{ 'MaxXUsersWarning' | get_plugin_lang('BBBPlugin') | format(max_users_limit) }}</p>
-                {% elseif users_online >= max_users_limit/2 %}
-                    <p>{{ 'MaxXUsersWarning' | get_plugin_lang('BBBPlugin') | format(max_users_limit) }}</p>
-                {% endif %}
-            {% endif %}
-        </div>
-        {% elseif max_users_limit > 0 %}
-            {% if conference_manager == true %}
-                <p>{{ 'MaxXUsersReachedManager' | get_plugin_lang('BBBPlugin') | format(max_users_limit) }}</p>
-            {% elseif users_online > 0 %}
-                <p>{{ 'MaxXUsersReached' | get_plugin_lang('BBBPlugin') | format(max_users_limit) }}</p>
-            {% endif %}
-        {% endif %}
-    </div>
-
-    <div class ="col-md-12">
-        <div class="page-header">
-            <h2>{{ 'RecordList'| get_plugin_lang('BBBPlugin') }}</h2>
-        </div>
-
-        <table class="table">
-            <tr>
-                <!-- th>#</th -->
-                <th>{{ 'CreatedAt'| get_plugin_lang('BBBPlugin') }}</th>
-                <th>{{ 'Status'| get_lang }}</th>
-                <th>{{ 'Records'| get_plugin_lang('BBBPlugin') }}</th>
-
-                {% if allow_to_edit  %}
-                    <th>{{ 'Actions'| get_lang }}</th>
-                {% endif %}
-
-            </tr>
-            {% for meeting in meetings %}
-            <tr>
-                <!-- td>{{ meeting.id }}</td -->
-                {% if meeting.visibility == 0 %}
-                    <td class="muted">{{ meeting.created_at }}</td>
-                {% else %}
-                    <td>{{ meeting.created_at }}</td>
-                {% endif %}
-                <td>
-                    {% if meeting.status == 1 %}
-                        <span class="label label-success">{{ 'MeetingOpened'|get_plugin_lang('BBBPlugin') }}</span>
-                    {% else %}
-                        <span class="label label-info">{{ 'MeetingClosed'|get_plugin_lang('BBBPlugin') }}</span>
-                    {% endif %}
-                </td>
-                <td>
-                    {% if meeting.record == 1 %}
-                        {# Record list #}
-                        {{ meeting.show_links }}
-                    {% else %}
-                        {{ 'NoRecording'|get_plugin_lang('BBBPlugin') }}
-                    {% endif %}
-                </td>
-
-                {% if allow_to_edit %}
-                    <td>
-                    {% if meeting.status == 1 %}
-                        <a class="btn btn-default" href="{{ meeting.end_url }} ">
-                            {{ 'CloseMeeting'|get_plugin_lang('BBBPlugin') }}
-                        </a>
-                    {% else %}
-                        {{ meeting.action_links }}
-                    {% endif %}
-                    </td>
-                {% endif %}
-
-            </tr>
-            {% endfor %}
-        </table>
-    </div>
-{% else %}
-    <div class ="col-md-12" style="text-align:center">
-        {{ 'ServerIsNotRunning' | get_plugin_lang('BBBPlugin') | return_message('warning') }}
-    </div>
-{% endif %}
-</div>

+ 1 - 4
plugin/test2pdf/resources/css/style.css

@@ -1,6 +1,3 @@
-.ta-center{
-	text-align:center;	
-}
 #courses_table td.valign-middle{
 	vertical-align: middle;
-}
+}

BIN
plugin/test2pdf/resources/img/128/accept.png


BIN
plugin/test2pdf/resources/img/24/download.png


BIN
plugin/test2pdf/resources/img/24/info.png


BIN
plugin/test2pdf/resources/img/32/accept.png


BIN
plugin/test2pdf/resources/img/32/download.png


BIN
plugin/test2pdf/resources/img/32/info.png


BIN
plugin/test2pdf/resources/img/64/download.png


BIN
plugin/test2pdf/resources/img/64/info.png


BIN
plugin/test2pdf/resources/img/logo.png


+ 94 - 1
tests/behat/features/bootstrap/FeatureContext.php

@@ -1,5 +1,6 @@
 <?php
 
+use Behat\Gherkin\Node\TableNode;
 use Behat\MinkExtension\Context\MinkContext;
 
 /**
@@ -155,7 +156,7 @@ class FeatureContext extends MinkContext
     public function iHaveAPublicPasswordProtectedCourse($code, $password)
     {
         $this->visit('/main/admin/course_add.php');
-        $this->fillFields(new \Behat\Gherkin\Node\TableNode([
+        $this->fillFields(new TableNode([
             ['title', 'Password Protected'],
             ['visual_code', $code],
             ['visibility', 3]
@@ -445,4 +446,96 @@ class FeatureContext extends MinkContext
         }
         return false;
     }
+
+    /**
+     * @Given /^I am a student subscribed to session "([^"]*)"$/
+     *
+     * @param string$sessionName
+     */
+    public function iAmStudentSubscribedToXSession($sessionName)
+    {
+        $this->iAmAPlatformAdministrator();
+        $this->visit('/main/session/session_add.php');
+        $this->fillField('name', $sessionName);
+        $this->pressButton('Next step');
+        $this->selectOption('NoSessionCoursesList[]', 'TEMP (TEMP)');
+        $this->pressButton('add_course');
+        $this->pressButton('Next step');
+        $this->assertPageContainsText('Update successful');
+        $this->fillField('user_to_add', 'acostea');
+        $this->waitForThePageToBeLoaded();
+        $this->clickLink('Costea Andrea (acostea)');
+        $this->pressButton('Finish session creation');
+        $this->assertPageContainsText('Session overview');
+        //$this->assertPageContainsText('Costea Andrea (acostea)');
+        $this->iAmAStudent();
+    }
+
+    /**
+     * Example: Then I should see the table "#category_results":
+     *               | Categories    | Absolute score | Relative score |
+     *               | Categoryname2 | 50 / 70        | 71.43%         |
+     *               | Categoryname1 | 60 / 60        | 100%           |
+     *
+     * @Then /^I should see the table "([^"]*)":$/
+     *
+     * @param string    $tableId
+     * @param TableNode $tableData
+     *
+     * @throws Exception
+     */
+    public function assertPageContainsTable($tableId, TableNode $tableData)
+    {
+        $table = $this->getSession()->getPage()->find('css', $tableId);
+        $rows = $tableData->getRowsHash();
+        $i = 1;
+
+        $right = array_keys($rows);
+
+        foreach ($right as $text) {
+            $cell = $table->find('css', 'tr:nth-child('.$i.') :nth-child(1)');
+            $i++;
+
+            if (!$cell) {
+                throw new Exception('Cell not found.');
+            }
+
+            if ($cell->getText() != $text) {
+                throw new Exception('Table text not found.');
+            }
+        }
+
+        $i = 1;
+
+        foreach ($rows as $field => $cols) {
+            if (is_array($cols)) {
+                $j = 2;
+
+                foreach ($cols as $col) {
+                    $cell = $table->find('css', 'tr:nth-child('.$i.') :nth-child('.$j.')');
+                    $j++;
+
+                    if (!$cell) {
+                        throw new Exception('Cell not found.');
+                    }
+
+                    if ($cell->getText() != $col) {
+                        throw new Exception('Table text not found. Found "'.$cell->getText().'" <> "'.$col.'"');
+                    }
+                }
+            } else {
+                $cell = $table->find('css', 'tr:nth-child('.$i.') :nth-child(2)');
+
+                if (!$cell) {
+                    throw new Exception('Cell not found.');
+                }
+
+                if ($cell->getText() != $cols) {
+                    throw new Exception('Table text not found. Found "'.$cell->getText().'" <> "'.$cols.'"');
+                }
+            }
+
+            $i++;
+        }
+    }
 }

+ 71 - 0
tests/behat/features/toolExercise.feature

@@ -310,6 +310,77 @@ Feature: Exercise tool
     Then I should see "Exercise copied"
     And I should see "Exercise 1 - Copy"
 
+  Scenario: Import exercise to test questions categories
+    Given I am on "/main/exercise/upload_exercise.php?cidReq=TEMP"
+    And I should see "Import quiz from Excel"
+    And I attach the file "/tests/behat/uploadable_files/exercise.xls" to "user_upload_quiz"
+    When I press "Upload"
+    And wait for the page to be loaded
+    Then I should see "Exercise for Behat test"
+
+  Scenario: Try exercise with categorized questions as student
+    Given I am a student subscribed to session "Session Exercise"
+    And I am on "/user_portal.php"
+    And I follow "Session Exercise"
+    And wait for the page to be loaded
+    And I follow "tabs2"
+    And I follow "TEMP"
+    And I am on "/main/exercise/exercise.php?cidReq=TEMP"
+    And I follow "Exercise for Behat test"
+    And I follow "Start test"
+    When wait for the page to be loaded
+    And I press "Next question"
+    And I check "oligarchy"
+    And I check "oligopoly"
+    And I check "timocracy"
+    And I check "autocracy"
+    And I press "Next question"
+    And I check the "semantics" radio button
+    And I press "Next question"
+    And I check the "RNASL" radio button
+    And I press "Next question"
+    And I check the "10" radio button
+    And I press "Next question"
+    And fill in the following:
+      | choice_id_6_0 | words  |
+      | choice_id_6_1 | fill   |
+      | choice_id_6_2 | blanks |
+    And I press "Next question"
+    And I select "A" from "choice_id_7_1"
+    And I select "B" from "choice_id_7_2"
+    And I select "C" from "choice_id_7_3"
+    And I press "Next question"
+    And I check "1"
+    And I press "Next question"
+    And I press "End test"
+    Then I should see "Score for the test: 190 / 190"
+    And I should see the table "#category_results":
+      | Categories    | Absolute score | Relative score |
+      | Categoryname2 | 50 / 70        | 71.43%         |
+      | Categoryname1 | 60 / 60        | 100%           |
+      | none          | 80 / 60        | 133.33%        |
+      | Total         | 190 / 190      | 100%           |
+
+  Scenario: Teacher see exercise results by categories
+    Given I am on "/user_portal.php"
+    And I follow "Session Exercise"
+    And wait for the page to be loaded
+    And I follow "tabs2"
+    And I follow "TEMP"
+    And I am on "/main/exercise/exercise.php?cidReq=TEMP"
+    And I follow "Exercise for Behat test"
+    And I follow "Results and feedback"
+    Then I should see "Learner score"
+    And wait for the page to be loaded
+    And I follow "Grade activity"
+    Then I should see "Score for the test: 190 / 190"
+    And I should see the table "#category_results":
+      | Categories    | Absolute score | Relative score |
+      | Categoryname2 | 50 / 70        | 71.43%         |
+      | Categoryname1 | 60 / 60        | 100%           |
+      | none          | 80 / 60        | 133.33%        |
+      | Total         | 190 / 190      | 100%           |
+
   Scenario: Delete an exercise
     Given I am on "/main/exercise/exercise.php?cidReq=TEMP"
     And I follow "Delete"