Pārlūkot izejas kodu

Merge with 1.11.x

jmontoyaa 8 gadi atpakaļ
vecāks
revīzija
3f685b412f
100 mainītis faili ar 811 papildinājumiem un 257 dzēšanām
  1. 0 2
      app/Migrations/Schema/V110/Version110.php
  2. 8 0
      app/Migrations/Schema/V111/Version111.php
  3. 127 0
      app/Resources/public/assets/fullcalendar/CONTRIBUTING.md
  4. 10 0
      app/Resources/public/assets/fullcalendar/README.md
  5. 5 0
      app/Resources/public/assets/fullcalendar/dist/fullcalendar.print.min.css
  6. 5 0
      app/Resources/public/assets/fullcalendar/dist/gcal.min.js
  7. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale-all.js
  8. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/af.js
  9. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar-dz.js
  10. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar-ly.js
  11. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar-ma.js
  12. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar-sa.js
  13. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar-tn.js
  14. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ar.js
  15. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/bg.js
  16. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ca.js
  17. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/cs.js
  18. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/da.js
  19. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/de-at.js
  20. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/de.js
  21. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/el.js
  22. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/en-au.js
  23. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/en-ca.js
  24. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/en-gb.js
  25. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/en-ie.js
  26. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/en-nz.js
  27. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/es-do.js
  28. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/es.js
  29. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/eu.js
  30. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/fa.js
  31. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/fi.js
  32. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/fr-ca.js
  33. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/fr-ch.js
  34. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/fr.js
  35. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/gl.js
  36. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/he.js
  37. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/hi.js
  38. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/hr.js
  39. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/hu.js
  40. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/id.js
  41. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/is.js
  42. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/it.js
  43. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ja.js
  44. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/kk.js
  45. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ko.js
  46. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/lb.js
  47. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/lt.js
  48. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/lv.js
  49. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/mk.js
  50. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ms-my.js
  51. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ms.js
  52. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/nb.js
  53. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/nl-be.js
  54. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/nl.js
  55. 1 0
      app/Resources/public/assets/fullcalendar/dist/locale/nn.js
  56. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/pl.js
  57. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/pt-br.js
  58. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/pt.js
  59. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ro.js
  60. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/ru.js
  61. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/sk.js
  62. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/sl.js
  63. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/sr-cyrl.js
  64. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/sr.js
  65. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/sv.js
  66. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/th.js
  67. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/tr.js
  68. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/uk.js
  69. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/vi.js
  70. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/zh-cn.js
  71. 0 0
      app/Resources/public/assets/fullcalendar/dist/locale/zh-tw.js
  72. 1 1
      bower.json
  73. 7 2
      main/admin/index.php
  74. 30 12
      main/announcements/announcements.php
  75. 1 1
      main/attendance/index.php
  76. 1 1
      main/auth/profile.php
  77. 12 17
      main/calendar/agenda.php
  78. 9 13
      main/calendar/agenda_js.php
  79. 3 5
      main/calendar/agenda_list.php
  80. 1 2
      main/calendar/ical_export.php
  81. 1 1
      main/course_home/activity.php
  82. 4 6
      main/course_home/course_home.php
  83. 4 5
      main/course_info/infocours.php
  84. 5 17
      main/course_info/tools.php
  85. 1 3
      main/coursecopy/recycle_course.php
  86. 249 47
      main/cron/import_csv.php
  87. 2 1
      main/document/downloadfolder.inc.php
  88. 1 1
      main/exercise/TestCategory.php
  89. 8 1
      main/exercise/admin.php
  90. 31 14
      main/exercise/exercise.class.php
  91. 5 5
      main/exercise/exercise_admin.php
  92. 81 51
      main/exercise/exercise_result.class.php
  93. 25 0
      main/exercise/matching.class.php
  94. 1 1
      main/exercise/question_list_admin.inc.php
  95. 15 24
      main/forum/editpost.php
  96. 99 14
      main/forum/forumfunction.inc.php
  97. 2 5
      main/forum/iframe_thread.php
  98. 15 0
      main/forum/index.php
  99. 18 4
      main/forum/viewforum.php
  100. 17 1
      main/forum/viewforumcategory.php

+ 0 - 2
app/Migrations/Schema/V110/Version110.php

@@ -570,7 +570,6 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('enabled_mathjax', 'false', 'No')");
 
         $this->addSql("INSERT INTO language (original_name, english_name, isocode, dokeos_folder, available) VALUES ('Føroyskt', 'faroese', 'fo', 'faroese', 0), ('Tagalog', 'tagalog', 'tl', 'tagalog',1), ('Tibetan', 'tibetan', 'bo', 'tibetan', 0), ('isiXhosa', 'xhosa', 'xh', 'xhosa', 0)");
-        $this->addSql("DELETE FROM settings_options WHERE variable = 'show_glossary_in_extra_tools'");
 
         $this->addSql("ALTER TABLE c_student_publication MODIFY COLUMN date_of_qualification DATETIME NULL DEFAULT NULL");
         $this->addSql("ALTER TABLE c_student_publication MODIFY COLUMN sent_date DATETIME NULL DEFAULT NULL");
@@ -583,7 +582,6 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("UPDATE c_student_publication_assignment SET ends_on = NULL WHERE ends_on = '0000-00-00 00:00:00'");
 
         $this->addSql("UPDATE settings_current SET type = 'checkbox' WHERE variable = 'registration' AND category = 'User'");
-
         $this->addSql("UPDATE settings_current SET selected_value = 'UTF-8' WHERE variable = 'platform_charset'");
 
         $this->addSql("ALTER TABLE course_rel_user DROP PRIMARY KEY");

+ 8 - 0
app/Migrations/Schema/V111/Version111.php

@@ -399,6 +399,14 @@ class Version111 extends AbstractMigrationChamilo
         $this->addSql('DELETE FROM settings_options WHERE variable = "visio_use_rtmpt"');
         $this->addSql('DELETE FROM course_module WHERE name = "conference"');
         $this->addSql('ALTER TABLE c_student_publication_assignment CHANGE add_to_calendar add_to_calendar INT NOT NULL;');
+
+        // Fixes missing options show_glossary_in_extra_tools
+        $this->addSql("DELETE FROM settings_options WHERE variable = 'show_glossary_in_extra_tools'");
+
+        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'none', 'None')");
+        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'exercise', 'Exercise')");
+        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'lp', 'Learning path')");
+        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'exercise_and_lp', 'ExerciseAndLearningPath')");
     }
 
     /**

+ 127 - 0
app/Resources/public/assets/fullcalendar/CONTRIBUTING.md

@@ -0,0 +1,127 @@
+
+## Reporting Bugs
+
+Each bug report MUST have a [JSFiddle/JSBin] recreation before any work can begin. [further instructions »](http://fullcalendar.io/wiki/Reporting-Bugs/)
+
+
+## Requesting Features
+
+Please search the [Issue Tracker] to see if your feature has already been requested, and if so, subscribe to it. Otherwise, read these [further instructions »](http://fullcalendar.io/wiki/Requesting-Features/)
+
+
+## Contributing Features
+
+The FullCalendar project welcomes [Pull Requests][Using Pull Requests] for new features, but because there are so many feature requests (over 100), and because every new feature requires refinement and maintenance, each PR will be prioritized against the project's other demands and might take a while to make it to an official release.
+
+Furthermore, each new feature should be designed as robustly as possible and be useful beyond the immediate usecase it was initially designed for. Feel free to start a ticket discussing the feature's specs before coding.
+
+
+## Contributing Bugfixes
+
+In the description of your [Pull Request][Using Pull Requests], please include recreation steps for the bug as well as a [JSFiddle/JSBin] demo. Communicating the buggy behavior is a requirement before a merge can happen.
+
+
+## Contributing Locales
+
+Please edit the original files in the `locale/` directory. DO NOT edit anything in the `dist/` directory. The build system will responsible for merging FullCalendar's `locale/` data with the [MomentJS locale data].
+
+
+## Other Ways to Contribute
+
+[Read about other ways to contribute »](http://fullcalendar.io/wiki/Contributing/)
+
+
+## Getting Set Up
+
+You will need [Git][git], [Node][node], and NPM installed. For clarification, please view the [jQuery readme][jq-readme], which requires a similar setup.
+
+Also, you will need the [gulp-cli][gulp-cli] package installed globally (`-g`) on your system:
+
+	npm install -g gulp-cli
+
+Then, clone FullCalendar's git repo:
+
+	git clone git://github.com/fullcalendar/fullcalendar.git
+
+Enter the directory and install FullCalendar's dependencies:
+
+	cd fullcalendar
+	npm install
+
+
+## What to edit
+
+When modifying files, please do not edit the generated or minified files in the `dist/` directory. Please edit the original `src/` files.
+
+
+## Development Workflow
+
+After you make code changes, you'll want to compile the JS/CSS so that it can be previewed from the tests and demos. You can either manually rebuild each time you make a change:
+
+	gulp dev
+
+Or, you can run a script that automatically rebuilds whenever you save a source file:
+
+	gulp watch
+
+When you are finished, run the following command to write the distributable files into the `./dist/` directory:
+
+	gulp dist
+
+If you want to clean up the generated files, run:
+
+	gulp clean
+
+
+## Style Guide
+
+Please follow the [Google JavaScript Style Guide] as closely as possible. With the following exceptions:
+
+```js
+if (true) {
+}
+else { // please put else, else if, and catch on a separate line
+}
+
+// please write one-line array literals with a one-space padding inside
+var a = [ 1, 2, 3 ];
+
+// please write one-line object literals with a one-space padding inside
+var o = { a: 1, b: 2, c: 3 };
+```
+
+Other exceptions:
+
+- please ignore anything about Google Closure Compiler or the `goog` library
+- please do not write JSDoc comments
+
+Notes about whitespace:
+
+- **use *tabs* instead of spaces**
+- separate functions with *2* blank lines
+- separate logical blocks within functions with *1* blank line
+
+Run the command line tool to automatically check your style:
+
+	gulp lint
+
+
+## Before Submitting your Code
+
+If you have edited code (including **tests** and **translations**) and would like to submit a pull request, please make sure you have done the following:
+
+1. Conformed to the style guide (successfully run `gulp lint`)
+
+2. Written automated tests. View the [Automated Test Readme]
+
+
+[JSFiddle/JSBin]: http://fullcalendar.io/wiki/Reporting-Bugs/
+[Issue Tracker]: https://github.com/fullcalendar/fullcalendar/issues
+[Using Pull Requests]: https://help.github.com/articles/using-pull-requests/
+[MomentJS locale data]: https://github.com/moment/moment/tree/develop/locale
+[git]: http://git-scm.com/
+[node]: http://nodejs.org/
+[gulp-cli]: https://github.com/gulpjs/gulp/blob/master/docs/getting-started.md
+[jq-readme]: https://github.com/jquery/jquery/blob/master/README.md#what-you-need-to-build-your-own-jquery
+[Google JavaScript Style Guide]: http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml
+[Automated Test Readme]: https://github.com/fullcalendar/fullcalendar/wiki/Automated-Tests

+ 10 - 0
app/Resources/public/assets/fullcalendar/README.md

@@ -0,0 +1,10 @@
+# FullCalendar [![Build Status](https://travis-ci.org/fullcalendar/fullcalendar.svg?branch=master)](https://travis-ci.org/fullcalendar/fullcalendar)
+
+A full-sized drag & drop event calendar (jQuery plugin).
+
+- [Project website and demos](http://fullcalendar.io/)
+- [Documentation](http://fullcalendar.io/docs/)
+- [Support](http://fullcalendar.io/support/)
+- [Contributing](CONTRIBUTING.md)
+- [Changelog](CHANGELOG.md)
+- [License](LICENSE.txt)

+ 5 - 0
app/Resources/public/assets/fullcalendar/dist/fullcalendar.print.min.css

@@ -0,0 +1,5 @@
+/*!
+ * FullCalendar v3.1.0 Print Stylesheet
+ * Docs & License: http://fullcalendar.io/
+ * (c) 2016 Adam Shaw
+ */.fc-bg,.fc-bgevent-container,.fc-bgevent-skeleton,.fc-business-container,.fc-event .fc-resizer,.fc-helper-container,.fc-helper-skeleton,.fc-highlight-container,.fc-highlight-skeleton{display:none}.fc tbody .fc-row,.fc-time-grid{min-height:0!important}.fc-time-grid .fc-event.fc-not-end:after,.fc-time-grid .fc-event.fc-not-start:before{content:"..."}.fc{max-width:100%!important}.fc-event{background:#fff!important;color:#000!important;page-break-inside:avoid}.fc hr,.fc tbody,.fc td,.fc th,.fc thead,.fc-row{border-color:#ccc!important;background:#fff!important}.fc tbody .fc-row{height:auto!important}.fc tbody .fc-row .fc-content-skeleton{position:static;padding-bottom:0!important}.fc tbody .fc-row .fc-content-skeleton tbody tr:last-child td{padding-bottom:1em}.fc tbody .fc-row .fc-content-skeleton table{height:1em}.fc-more,.fc-more-cell{display:none!important}.fc tr.fc-limited{display:table-row!important}.fc td.fc-limited{display:table-cell!important}.fc-agenda-view .fc-axis,.fc-popover{display:none}.fc-slats,.fc-time-grid hr{display:none!important}.fc button,.fc-button-group,.fc-time-grid .fc-event .fc-time span{display:none}.fc-time-grid .fc-content-skeleton{position:static}.fc-time-grid .fc-content-skeleton table{height:4em}.fc-time-grid .fc-event-container{margin:0!important}.fc-time-grid .fc-event{position:static!important;margin:3px 2px!important}.fc-time-grid .fc-event.fc-not-end{border-bottom-width:1px!important}.fc-time-grid .fc-event.fc-not-start{border-top-width:1px!important}.fc-time-grid .fc-event .fc-time{white-space:normal!important}.fc-time-grid .fc-event .fc-time:after{content:attr(data-full)}.fc-day-grid-container,.fc-scroller,.fc-time-grid-container{overflow:visible!important;height:auto!important}.fc-row{border:0!important;margin:0!important}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 5 - 0
app/Resources/public/assets/fullcalendar/dist/gcal.min.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale-all.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/af.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar-dz.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar-ly.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar-ma.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar-sa.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar-tn.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ar.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/bg.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ca.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/cs.js


+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/da.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("da",{months:"januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december".split("_"),monthsShort:"jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec".split("_"),weekdays:"søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag".split("_"),weekdaysShort:"søn_man_tir_ons_tor_fre_lør".split("_"),weekdaysMin:"sø_ma_ti_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY HH:mm",LLLL:"dddd [d.] D. MMMM YYYY HH:mm"},calendar:{sameDay:"[I dag kl.] LT",nextDay:"[I morgen kl.] LT",nextWeek:"dddd [kl.] LT",lastDay:"[I går kl.] LT",lastWeek:"[sidste] dddd [kl] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s siden",s:"få sekunder",m:"et minut",mm:"%d minutter",h:"en time",hh:"%d timer",d:"en dag",dd:"%d dage",M:"en måned",MM:"%d måneder",y:"et år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});return e}(),e.fullCalendar.datepickerLocale("da","da",{closeText:"Luk",prevText:"<Forrige",nextText:"Næste>",currentText:"Idag",monthNames:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],dayNames:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],dayNamesShort:["Søn","Man","Tir","Ons","Tor","Fre","Lør"],dayNamesMin:["Sø","Ma","Ti","On","To","Fr","Lø"],weekHeader:"Uge",dateFormat:"dd-mm-yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.locale("da",{buttonText:{month:"Måned",week:"Uge",day:"Dag",list:"Agenda"},allDayText:"Hele dagen",eventLimitText:"flere",noEventsMessage:"Ingen arrangementer at vise"})});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/de-at.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/de.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/el.js


+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/en-au.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("en-au",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10,r=1===~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th";return e+r},week:{dow:1,doy:4}});return e}(),e.fullCalendar.datepickerLocale("en-au","en-AU",{closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.locale("en-au")});

+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/en-ca.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("en-ca",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"YYYY-MM-DD",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10,t=1===~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th";return e+t}});return e}(),e.fullCalendar.locale("en-ca")});

+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/en-gb.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("en-gb",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd, D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10,r=1===~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th";return e+r},week:{dow:1,doy:4}});return e}(),e.fullCalendar.datepickerLocale("en-gb","en-GB",{closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.locale("en-gb")});

+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/en-ie.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("en-ie",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD-MM-YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY HH:mm",LLLL:"dddd D MMMM YYYY HH:mm"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10,t=1===~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th";return e+t},week:{dow:1,doy:4}});return e}(),e.fullCalendar.locale("en-ie")});

+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/en-nz.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("en-nz",{months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),longDateFormat:{LT:"h:mm A",LTS:"h:mm:ss A",L:"DD/MM/YYYY",LL:"D MMMM YYYY",LLL:"D MMMM YYYY h:mm A",LLLL:"dddd, D MMMM YYYY h:mm A"},calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[Last] dddd [at] LT",sameElse:"L"},relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},ordinalParse:/\d{1,2}(st|nd|rd|th)/,ordinal:function(e){var a=e%10,r=1===~~(e%100/10)?"th":1===a?"st":2===a?"nd":3===a?"rd":"th";return e+r},week:{dow:1,doy:4}});return e}(),e.fullCalendar.datepickerLocale("en-nz","en-NZ",{closeText:"Done",prevText:"Prev",nextText:"Next",currentText:"Today",monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],dayNamesMin:["Su","Mo","Tu","We","Th","Fr","Sa"],weekHeader:"Wk",dateFormat:"dd/mm/yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.locale("en-nz")});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/es-do.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/es.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/eu.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/fa.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/fi.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/fr-ca.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/fr-ch.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/fr.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/gl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/he.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/hi.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/hr.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/hu.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/id.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/is.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/it.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ja.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/kk.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ko.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/lb.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/lt.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/lv.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/mk.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ms-my.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ms.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/nb.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/nl-be.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/nl.js


+ 1 - 0
app/Resources/public/assets/fullcalendar/dist/locale/nn.js

@@ -0,0 +1 @@
+!function(e){"function"==typeof define&&define.amd?define(["jquery","moment"],e):"object"==typeof exports?module.exports=e(require("jquery"),require("moment")):e(jQuery,moment)}(function(e,a){!function(){var e=a.defineLocale("nn",{months:"januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember".split("_"),monthsShort:"jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des".split("_"),weekdays:"sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag".split("_"),weekdaysShort:"sun_mån_tys_ons_tor_fre_lau".split("_"),weekdaysMin:"su_må_ty_on_to_fr_lø".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"DD.MM.YYYY",LL:"D. MMMM YYYY",LLL:"D. MMMM YYYY [kl.] H:mm",LLLL:"dddd D. MMMM YYYY [kl.] HH:mm"},calendar:{sameDay:"[I dag klokka] LT",nextDay:"[I morgon klokka] LT",nextWeek:"dddd [klokka] LT",lastDay:"[I går klokka] LT",lastWeek:"[Føregåande] dddd [klokka] LT",sameElse:"L"},relativeTime:{future:"om %s",past:"%s sidan",s:"nokre sekund",m:"eit minutt",mm:"%d minutt",h:"ein time",hh:"%d timar",d:"ein dag",dd:"%d dagar",M:"ein månad",MM:"%d månader",y:"eit år",yy:"%d år"},ordinalParse:/\d{1,2}\./,ordinal:"%d.",week:{dow:1,doy:4}});return e}(),e.fullCalendar.datepickerLocale("nn","nn",{closeText:"Lukk",prevText:"«Førre",nextText:"Neste»",currentText:"I dag",monthNames:["januar","februar","mars","april","mai","juni","juli","august","september","oktober","november","desember"],monthNamesShort:["jan","feb","mar","apr","mai","jun","jul","aug","sep","okt","nov","des"],dayNamesShort:["sun","mån","tys","ons","tor","fre","lau"],dayNames:["sundag","måndag","tysdag","onsdag","torsdag","fredag","laurdag"],dayNamesMin:["su","må","ty","on","to","fr","la"],weekHeader:"Veke",dateFormat:"dd.mm.yy",firstDay:1,isRTL:!1,showMonthAfterYear:!1,yearSuffix:""}),e.fullCalendar.locale("nn",{buttonText:{month:"Månad",week:"Veke",day:"Dag",list:"Agenda"},allDayText:"Heile dagen",eventLimitText:"til",noEventsMessage:"Ingen hendelser å vise"})});

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/pl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/pt-br.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/pt.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ro.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/ru.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/sk.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/sl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/sr-cyrl.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/sr.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/sv.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/th.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/tr.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/uk.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/vi.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/zh-cn.js


Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 0 - 0
app/Resources/public/assets/fullcalendar/dist/locale/zh-tw.js


+ 1 - 1
bower.json

@@ -20,7 +20,7 @@
     "modernizr": "2.8.*",
     "jqueryui-timepicker-addon": "1.5.*",
     "image-map-resizer": "0.5.*",
-    "fullcalendar": "2.9.*",
+    "fullcalendar": "3.1.*",
     "simpleWebRTC": "1.15.*",
     "select2": "4.*",
     "MathJax": "2.7.*",

+ 7 - 2
main/admin/index.php

@@ -105,16 +105,21 @@ if (api_is_platform_admin()) {
         $items[] = array('url' => 'ldap_users_list.php', 'label' => get_lang('ImportLDAPUsersIntoPlatform'));
     }
     $items[] = array('url' => 'extra_fields.php?type=user', 'label' => get_lang('ManageUserFields'));
+    $items[] = array('url'=>'usergroups.php', 'label' => get_lang('Classes'));
+} elseif (api_is_session_admin() && api_get_configuration_value('limit_session_admin_role')) {
+    $items = array(
+        array('url' => 'user_list.php', 'label' => get_lang('UserList')),
+        array('url' => 'user_add.php', 'label' => get_lang('AddUsers')),
+    );
 } else {
     $items = array(
         array('url' => 'user_list.php', 'label' => get_lang('UserList')),
         array('url' => 'user_add.php', 'label' => get_lang('AddUsers')),
         array('url' => 'user_import.php', 'label' => get_lang('ImportUserListXMLCSV')),
+        array('url' => 'usergroups.php', 'label' => get_lang('Classes')),
     );
 }
 
-$items[] = array('url' => 'usergroups.php', 'label' => get_lang('Classes'));
-
 $blocks['users']['items'] = $items;
 $blocks['users']['extra'] = null;
 

+ 30 - 12
main/announcements/announcements.php

@@ -67,6 +67,15 @@ $sessionId = api_get_session_id();
 
 if (!empty($group_id)) {
     $group_properties = GroupManager:: get_group_properties($group_id);
+
+    $interbreadcrumb[] = array(
+        "url" => api_get_path(WEB_CODE_PATH)."group/group.php?".api_get_cidreq(),
+        "name" => get_lang('Groups'),
+    );
+    $interbreadcrumb[] = array(
+        "url" => api_get_path(WEB_CODE_PATH)."group/group_space.php?".api_get_cidreq(),
+        "name" => get_lang('GroupSpace').' '.$group_properties['name']
+    );
 }
 
 api_protect_course_group(GroupManager::GROUP_TOOL_ANNOUNCEMENT);
@@ -142,6 +151,12 @@ switch ($action) {
         }
         break;
     case 'view':
+        $interbreadcrumb[] = array(
+            "url" => api_get_path(WEB_CODE_PATH)."announcements/announcements.php?".api_get_cidreq(),
+            "name" => $nameTools,
+        );
+
+        $nameTools = get_lang('View');
         $content = AnnouncementManager::display_announcement($announcement_id);
         break;
     case 'list':
@@ -355,6 +370,13 @@ switch ($action) {
         } else {
             $form_name = get_lang('ModifyAnnouncement');
         }
+
+        $interbreadcrumb[] = array(
+            "url" => api_get_path(WEB_CODE_PATH)."announcements/announcements.php?".api_get_cidreq(),
+            "name" => $nameTools,
+        );
+
+        $nameTools = $form_name;
         $form->addElement('header', $form_name);
         $to = [];
         if (empty($group_id)) {
@@ -505,7 +527,9 @@ switch ($action) {
 
                     /*		MAIL FUNCTION	*/
                     if (isset($_POST['email_ann']) && empty($_POST['onlyThoseMails'])) {
-                        AnnouncementManager::send_email(
+                        AnnouncementManager::sendEmail(
+                            api_get_course_info(),
+                            api_get_session_id(),
                             $id,
                             $sendToUsersInSession,
                             isset($data['send_to_hrm_users'])
@@ -529,6 +553,8 @@ switch ($action) {
 
                     if (empty($group_id)) {
                         $insert_id = AnnouncementManager::add_announcement(
+                            api_get_course_info(),
+                            api_get_session_id(),
                             $data['title'],
                             $data['content'],
                             $data['users'],
@@ -558,7 +584,9 @@ switch ($action) {
 
                     /* MAIL FUNCTION */
                     if (isset($data['email_ann']) && $data['email_ann']) {
-                        AnnouncementManager::send_email(
+                        AnnouncementManager::sendEmail(
+                            api_get_course_info(),
+                            api_get_session_id(),
                             $insert_id,
                             $sendToUsersInSession
                         );
@@ -575,16 +603,6 @@ switch ($action) {
 if (!empty($_GET['remind_inactive'])) {
     $to[] = 'USER:'.intval($_GET['remind_inactive']);
 }
-if (!empty($group_id)) {
-    $interbreadcrumb[] = array(
-        "url" => api_get_path(WEB_CODE_PATH)."group/group.php?".api_get_cidreq(),
-        "name" => get_lang('Groups'),
-    );
-    $interbreadcrumb[] = array(
-        "url" => api_get_path(WEB_CODE_PATH)."group/group_space.php?".api_get_cidreq(),
-        "name" => get_lang('GroupSpace').' '.$group_properties['name']
-    );
-}
 
 if (empty($_GET['origin']) or $_GET['origin'] !== 'learnpath') {
     //we are not in the learning path

+ 1 - 1
main/attendance/index.php

@@ -299,6 +299,6 @@ switch ($action) {
             $attendanceController->getAttendanceBaseInLogin(false, true);
         }
         break;
-    default :
+    default:
         $attendanceController->attendance_list();
 }

+ 1 - 1
main/auth/profile.php

@@ -41,7 +41,7 @@ $htmlHeadXtra[] = api_get_css_asset('cropper/dist/cropper.min.css');
 $htmlHeadXtra[] = api_get_asset('cropper/dist/cropper.min.js');
 $htmlHeadXtra[] = '<script>
 $(document).ready(function() {
-    $("id_generate_api_key").on("click", function (e) {
+    $("#id_generate_api_key").on("click", function (e) {
         e.preventDefault();
 
         $.ajax({

+ 12 - 17
main/calendar/agenda.php

@@ -31,7 +31,6 @@ if (empty($action)) {
 
 $group_id = api_get_group_id();
 $groupInfo = GroupManager::get_group_properties($group_id);
-
 $eventId = isset($_REQUEST['id']) ? $_REQUEST['id'] : null;
 $type = $event_type = isset($_GET['type']) ? $_GET['type'] : null;
 
@@ -82,37 +81,29 @@ $nameTools = get_lang('Agenda');
 
 Event::event_access_tool(TOOL_CALENDAR_EVENT);
 
-// permission stuff - also used by loading from global in agenda.inc.php
-$is_allowed_to_edit = api_is_allowed_to_edit(false, true) || (api_get_course_setting('allow_user_edit_agenda') && !api_is_anonymous());
-$agenda = new Agenda();
-$agenda->type = $type;
-$actions = $agenda->displayActions('calendar');
-
 if ($type === 'fromjs') {
     $id_list = explode('_', $eventId);
     $eventId = $id_list[1];
     $event_type = $id_list[0];
 }
 
-if (!api_is_allowed_to_edit(null, true) && $event_type === 'course') {
+$agenda = new Agenda($event_type);
+$allowToEdit = $agenda->getIsAllowedToEdit();
+$actions = $agenda->displayActions('calendar');
+
+if (!$allowToEdit && $event_type === 'course') {
     api_not_allowed(true);
 }
+
 if ($event_type === 'course') {
     $agendaUrl = api_get_path(WEB_CODE_PATH).'calendar/agenda_js.php?'.api_get_cidreq().'&type=course';
 } else {
     $agendaUrl = api_get_path(WEB_CODE_PATH).'calendar/agenda_js.php?&type='.$event_type;
 }
 $course_info = api_get_course_info();
-$agenda->type = $event_type;
 
 $content = null;
-if (api_is_allowed_to_edit(false, true) ||
-    (api_get_course_setting('allow_user_edit_agenda') &&
-        !api_is_anonymous() &&
-        api_is_allowed_to_session_edit(false, true)) ||
-    GroupManager::user_has_access(api_get_user_id(), $groupInfo['iid'], GroupManager::GROUP_TOOL_CALENDAR) &&
-    GroupManager::is_tutor_of_group(api_get_user_id(), $groupInfo['iid'])
-) {
+if ($allowToEdit) {
     switch ($action) {
         case 'add':
             $actionName = get_lang('Add');
@@ -158,7 +149,10 @@ if (api_is_allowed_to_edit(false, true) ||
                 }
                 $message = Display::return_message(get_lang('AddSuccess'), 'confirmation');
                 if ($sendEmail) {
-                    $message .= Display::return_message(get_lang('AdditionalMailWasSentToSelectedUsers'), 'confirmation');
+                    $message .= Display::return_message(
+                        get_lang('AdditionalMailWasSentToSelectedUsers'),
+                        'confirmation'
+                    );
                 }
                 Display::addFlash($message);
                 header("Location: $agendaUrl");
@@ -220,6 +214,7 @@ if (api_is_allowed_to_edit(false, true) ||
                 }
 
                 $usersToSend = isset($values['users_to_send']) ? $values['users_to_send'] : '';
+
                 // Editing normal event.
                 $agenda->editEvent(
                     $eventId,

+ 9 - 13
main/calendar/agenda_js.php

@@ -23,7 +23,8 @@ $this_section = SECTION_MYAGENDA;
 $htmlHeadXtra[] = api_get_jquery_libraries_js(array('jquery-ui', 'jquery-ui-i18n'));
 
 $htmlHeadXtra[] = api_get_asset('qtip2/jquery.qtip.min.js');
-$htmlHeadXtra[] = api_get_asset('fullcalendar/dist/fullcalendar.min.js');
+$htmlHeadXtra[] = api_get_asset('fullcalendar/dist/fullcalendar.js');
+$htmlHeadXtra[] = api_get_asset('fullcalendar/dist/locale-all.js');
 $htmlHeadXtra[] = api_get_asset('fullcalendar/dist/gcal.js');
 $htmlHeadXtra[] = api_get_css_asset('fullcalendar/dist/fullcalendar.min.css');
 $htmlHeadXtra[] = api_get_css_asset('qtip2/jquery.qtip.min.css');
@@ -45,8 +46,7 @@ if (isset($_REQUEST['cidReq']) && !empty($_REQUEST['cidReq'])) {
 
 api_protect_course_group(GroupManager::GROUP_TOOL_CALENDAR);
 
-$agenda = new Agenda();
-$agenda->type = $type;
+$agenda = new Agenda($type);
 
 $is_group_tutor = false;
 $session_id = api_get_session_id();
@@ -81,15 +81,11 @@ switch ($type) {
         break;
     case 'course':
         api_protect_course_script(true);
+        $allowToEdit = $agenda->getIsAllowedToEdit();
         $this_section = SECTION_COURSES;
-        if (api_is_allowed_to_edit()) {
+        if ($allowToEdit) {
             $can_add_events = 1;
         }
-        if (!empty($group_id)) {
-            if ($is_group_tutor) {
-                $can_add_events = 1;
-            }
-        }
         break;
     case 'personal':
         if (api_is_anonymous()) {
@@ -114,13 +110,13 @@ switch ($type) {
 }
 
 //Setting translations
-$day_short = api_get_week_days_short();
+/*$day_short = api_get_week_days_short();
 $days = api_get_week_days_long();
 $months = api_get_months_long();
-$months_short = api_get_months_short();
+$months_short = api_get_months_short();*/
 
 //Setting calendar translations
-$tpl->assign('month_names', json_encode($months));
+/*$tpl->assign('month_names', json_encode($months));
 $tpl->assign('month_names_short', json_encode($months_short));
 $tpl->assign('day_names', json_encode($days));
 $tpl->assign('day_names_short', json_encode($day_short));
@@ -132,7 +128,7 @@ $tpl->assign(
         'week' => get_lang('Week'),
         'day' => get_lang('Day')
     ))
-);
+);*/
 
 //see http://docs.jquery.com/UI/Datepicker/$.datepicker.formatDate
 

+ 3 - 5
main/calendar/agenda_list.php

@@ -8,15 +8,13 @@
 require_once __DIR__.'/../inc/global.inc.php';
 
 $interbreadcrumb[] = array(
-    'url' => api_get_path(WEB_CODE_PATH) . "calendar/agenda_js.php",
+    'url' => api_get_path(WEB_CODE_PATH).'calendar/agenda_js.php',
     'name' => get_lang('Agenda')
 );
 
 $currentCourseId = api_get_course_int_id();
-
-$agenda = new Agenda();
 $type = isset($_REQUEST['type']) ? $_REQUEST['type'] : null;
-$agenda->setType($type);
+$agenda = new Agenda($type);
 $events = $agenda->getEvents(
     null,
     null,
@@ -64,7 +62,7 @@ $tpl = new Template(get_lang('Events'));
 $tpl->assign('agenda_events', $events);
 $tpl->assign('url', $url);
 $tpl->assign('show_action', in_array($type, ['course', 'session']));
-$tpl->assign('actions', $actions);
+$tpl->assign('agenda_actions', $actions);
 $tpl->assign('is_allowed_to_edit', api_is_allowed_to_edit());
 
 if (api_is_allowed_to_edit()) {

+ 1 - 2
main/calendar/ical_export.php

@@ -31,8 +31,7 @@ $id = explode('_', $_GET['id']);
 $type = $id[0];
 $id = $id[1];
 
-$agenda = new Agenda();
-$agenda->type = $type; //course,admin or personal
+$agenda = new Agenda($type);
 if (isset($_GET['course_id'])) {
     $course_info = api_get_course_info_by_id($_GET['course_id']);
     if (!empty($course_info)) {

+ 1 - 1
main/course_home/activity.php

@@ -78,7 +78,7 @@ if ($session_id === 0 && api_is_course_admin() && api_is_allowed_to_edit(null, t
     $my_list = CourseHome::get_tools_category(TOOL_INTERACTION);
     $list2 = CourseHome::get_tools_category(TOOL_COURSE_PLUGIN);
 
-    $my_list = array_merge($my_list,$list2);
+    $my_list = array_merge($my_list, $list2);
     $items =  CourseHome::show_tools_category($my_list);
     $content .= return_block(get_lang('Interaction'), $items, 'course-tools-interaction');
 

+ 4 - 6
main/course_home/course_home.php

@@ -112,6 +112,7 @@ $(document).ready(function() {
 
 // The section for the tabs
 $this_section = SECTION_COURSES;
+
 $user_id = api_get_user_id();
 $course_code = api_get_course_id();
 $courseId = api_get_course_int_id();
@@ -139,12 +140,9 @@ Session::erase('_gid');
 $isSpecialCourse = CourseManager::isSpecialCourse($courseId);
 
 if ($isSpecialCourse) {
-    if (isset($_GET['autoreg'])) {
-        $autoRegistration = Security::remove_XSS($_GET['autoreg']);
-        if ($autoRegistration == 1) {
-            if (CourseManager::subscribe_user($user_id, $course_code, STUDENT)) {
-                Session::write('is_allowed_in_course', true);
-            }
+    if (isset($_GET['autoreg']) && $_GET['autoreg'] == 1) {
+        if (CourseManager::subscribe_user($user_id, $course_code, STUDENT)) {
+            Session::write('is_allowed_in_course', true);
         }
     }
 }

+ 4 - 5
main/course_info/infocours.php

@@ -118,17 +118,16 @@ $form->addHtml('
         <div class="panel-body">
 ');
 
-$image_html = '';
-
+$image = '';
 // Display course picture
 $course_path = api_get_path(SYS_COURSE_PATH).$currentCourseRepository;   // course path
-
 if (file_exists($course_path.'/course-pic85x85.png')) {
     $course_web_path = api_get_path(WEB_COURSE_PATH).$currentCourseRepository;   // course web path
     $course_medium_image = $course_web_path.'/course-pic85x85.png?'.rand(1, 1000); // redimensioned image 85x85
-    $image_html =  '<div class="row"><label class="col-md-2 control-label">'.get_lang('Image').'</label> <div class="col-md-8"><img src="'.$course_medium_image.'" /></div></div>';
+    $image =  '<div class="row"><label class="col-md-2 control-label">'.get_lang('Image').'</label> 
+                    <div class="col-md-8"><img src="'.$course_medium_image.'" /></div></div>';
 }
-$form->addElement('html', $image_html);
+$form->addHtml($image);
 
 $form->addText('title', get_lang('Title'), true);
 $form->applyFilter('title', 'html_filter');

+ 5 - 17
main/course_info/tools.php

@@ -43,7 +43,10 @@ switch ($action) {
             api_not_allowed(true);
         }
 
-        $interbreadcrumb[] = array('url' => api_get_self().'?'.api_get_cidreq(), 'name' => get_lang('CustomizeIcons'));
+        $interbreadcrumb[] = array(
+            'url' => api_get_self().'?'.api_get_cidreq(),
+            'name' => get_lang('CustomizeIcons'),
+        );
         $toolName = Security::remove_XSS(stripslashes($tool['name']));
 
         $currentUrl = api_get_self().'?action=edit_icon&id=' . $id.'&'.api_get_cidreq();
@@ -88,9 +91,7 @@ switch ($action) {
             $form->addCheckBox('delete_icon', null, get_lang('DeletePicture'));
         }
         $form->addHtml('</div>');
-
         $form->setDefaults($tool);
-
         $content = $form->returnForm();
 
         if ($form->validate()) {
@@ -123,7 +124,7 @@ switch ($action) {
             $iconsTools .= '<div class="items-tools">';
 
             if (!empty($tool['custom_icon'])) {
-                $image = getCustomWebIconPath().$tool['custom_icon'];
+                $image = CourseHome::getCustomWebIconPath().$tool['custom_icon'];
                 $icon = Display::img($image, $toolIconName);
             } else {
                 $image = (substr($tool['image'], 0, strpos($tool['image'], '.'))).'.png';
@@ -154,20 +155,7 @@ switch ($action) {
         break;
 }
 
-/**
- * @return string
- */
-function getCustomWebIconPath()
-{
-    // Check if directory exists or create it if it doesn't
-    $dir = api_get_path(WEB_COURSE_PATH).api_get_course_path().'/upload/course_home_icons/';
-
-    return $dir;
-}
 $tpl = new Template($toolName);
-
 $tpl->assign('content', $content);
 $template = $tpl->get_template('layout/layout_1_col.tpl');
 $tpl->display($template);
-
-

+ 1 - 3
main/coursecopy/recycle_course.php

@@ -18,7 +18,7 @@ api_protect_course_script(true);
 
 // Check access rights (only teachers are allowed here)
 if (!api_is_allowed_to_edit()) {
-	api_not_allowed(true);
+    api_not_allowed(true);
 }
 
 // Section for the tabs
@@ -63,9 +63,7 @@ if (Security::check_token('post') && (
     }
     $cr = new CourseRecycler($course);
     $cr->recycle($recycle_type);
-
     Display::display_confirmation_message(get_lang('RecycleFinished'));
-
 } elseif (Security::check_token('post') && (
         isset($_POST['recycle_option']) &&
         $_POST['recycle_option'] == 'select_items'

+ 249 - 47
main/cron/import_csv.php

@@ -202,6 +202,7 @@ class ImportCsv
                         } else {
                             $this->$method($file, true);
                         }
+                        $this->logger->addInfo("--Finish reading file--");
                     }
                 }
             }
@@ -237,6 +238,7 @@ class ImportCsv
                             $teacherBackup,
                             $groupBackup
                         );
+                        $this->logger->addInfo("--Finish reading file--");
                     }
                 }
             }
@@ -373,7 +375,7 @@ class ImportCsv
      */
     private function importTeachersStatic($file)
     {
-        $this->importTeachers($file, false);
+        $this->importTeachers($file, true);
     }
 
     /**
@@ -525,7 +527,7 @@ class ImportCsv
      */
     private function importStudentsStatic($file)
     {
-        $this->importStudents($file, false);
+        $this->importStudents($file, true);
     }
 
     /**
@@ -751,7 +753,7 @@ class ImportCsv
      */
     private function importCoursesStatic($file, $moveFile, &$teacherBackup = array(), &$groupBackup = array())
     {
-        $this->importCourses($file, false, $teacherBackup, $groupBackup);
+        $this->importCourses($file, true, $teacherBackup, $groupBackup);
     }
 
     /**
@@ -769,6 +771,7 @@ class ImportCsv
             $this->logger->addInfo(count($data)." records found.");
             $eventsToCreate = array();
             $errorFound = false;
+
             foreach ($data as $row) {
                 $sessionId = null;
                 $externalSessionId = null;
@@ -827,7 +830,6 @@ class ImportCsv
 
                 if (empty($teacherId)) {
                     $errorFound = true;
-
                     $this->logger->addInfo(
                         "No teacher found in course code : '$courseCode' and session: '$sessionId'"
                     );
@@ -854,11 +856,6 @@ class ImportCsv
                     $errorFound = true;
                 }
 
-                // If old events do nothing.
-                /*if (api_strtotime($startDate) < time()) {
-                    continue;
-                }*/
-
                 if ($errorFound == false) {
                     $eventsToCreate[] = array(
                         'start' => $startDate,
@@ -883,12 +880,6 @@ class ImportCsv
                 return 0;
             }
 
-            $this->logger->addInfo(
-                "Ready to insert events"
-            );
-
-            $agenda = new Agenda();
-
             $extraFieldValue = new ExtraFieldValue('calendar_event');
             $extraFieldName = $this->extraFieldIdNameList['calendar_event'];
             $externalEventId = null;
@@ -906,6 +897,23 @@ class ImportCsv
                 return 0;
             }
 
+            $this->logger->addInfo(
+                "Ready to insert events"
+            );
+
+            $batchSize = $this->batchSize;
+            $counter = 1;
+            $em = Database::getManager();
+            $eventStartDateList = [];
+            $eventEndDateList = [];
+            $report = [
+                'mail_sent' => 0,
+                'mail_not_sent_announcement_exists' => 0,
+                'mail_not_sent_because_date' => 0,
+            ];
+
+            $eventsToCreateFinal = [];
+
             foreach ($eventsToCreate as $event) {
                 $update = false;
                 $item = null;
@@ -932,21 +940,49 @@ class ImportCsv
                     );
 
                     if (!empty($item)) {
-                        $this->logger->addInfo(
-                            "Event #$externalEventId was already added. Updating ..."
-                        );
                         $update = true;
-                        //continue;
-                    } else {
-                        $this->logger->addInfo(
-                            "Event #$externalEventId was not added. Preparing to be created ..."
-                        );
                     }
                 }
 
                 $courseInfo = api_get_course_info_by_id($event['course_id']);
+                $event['course_info']  = $courseInfo;
+                $event['update']  = $update;
+                $event['item']  = $item;
+                $event['external_event_id']  = $externalEventId;
+
+                if (isset($eventStartDateList[$courseInfo['real_id']]) &&
+                    isset($eventStartDateList[$courseInfo['real_id']][$event['session_id']])
+                ) {
+                    $currentItemDate = api_strtotime($event['start']);
+                    $firstDate = $eventStartDateList[$courseInfo['real_id']][$event['session_id']];
+                    if ($currentItemDate < api_strtotime($firstDate)) {
+                        $eventStartDateList[$courseInfo['real_id']][$event['session_id']] = $event['start'];
+                        $eventEndDateList[$courseInfo['real_id']][$event['session_id']] = $event['end'];
+                    }
+                } else {
+                    // First time
+                    $eventStartDateList[$courseInfo['real_id']][$event['session_id']] = $event['start'];
+                    $eventEndDateList[$courseInfo['real_id']][$event['session_id']] = $event['end'];
+                }
+                $eventsToCreateFinal[] = $event;
+            }
+
+            $eventAlreadySent = [];
+            foreach ($eventsToCreateFinal as $event) {
+                $courseInfo = $event['course_info'];
+                $item = $event['item'];
+                $update = $event['update'];
+                $externalEventId = $event['external_event_id'];
+
+                $info = 'Course: '.$courseInfo['real_id'].' ('.$courseInfo['code'].') - Session: '.$event['session_id'];
+
+                $agenda = new Agenda(
+                    'course',
+                    $event['sender_id'],
+                    $courseInfo['real_id'],
+                    $event['session_id']
+                );
                 $agenda->set_course($courseInfo);
-                $agenda->setType('course');
                 $agenda->setSessionId($event['session_id']);
                 $agenda->setSenderId($event['sender_id']);
                 $agenda->setIsAllowedToEdit(true);
@@ -957,21 +993,145 @@ class ImportCsv
                 // To use the event comment you need
                 // ALTER TABLE c_calendar_event ADD COLUMN comment TEXT;
                 // add in configuration.php allow_agenda_event_comment = true
-
                 if (empty($courseInfo)) {
                     $this->logger->addInfo(
-                        "No course found for added: #".$event['course_id']." Skipping ..."
+                        "No course found for event #$externalEventId Course #".$event['course_id']." Skipping ..."
                     );
                     continue;
                 }
 
                 if (empty($event['sender_id'])) {
                     $this->logger->addInfo(
-                        "No sender found: #".$event['sender_id']." Skipping ..."
+                        "No sender found for event #$externalEventId Send #".$event['sender_id']." Skipping ..."
                     );
                     continue;
                 }
 
+                // Taking first element of course-session event
+                $alreadyAdded = false;
+                $firstDate = $eventStartDateList[$courseInfo['real_id']][$event['session_id']];
+                $firstEndDate = $eventEndDateList[$courseInfo['real_id']][$event['session_id']];
+
+                if (isset($eventAlreadySent[$courseInfo['real_id']]) &&
+                    isset($eventAlreadySent[$courseInfo['real_id']][$event['session_id']])
+                ) {
+                    $alreadyAdded = true;
+                } else {
+                    $eventAlreadySent[$courseInfo['real_id']][$event['session_id']] = true;
+                }
+
+                // Working days (Mon-Fri)see BT#12156#note-16
+                $days = 5;
+                $startDatePlusDays = api_strtotime("$days weekdays");
+
+                $this->logger->addInfo(
+                    "startDatePlusDays: ".api_get_utc_datetime($startDatePlusDays).' - First date: '.$firstDate
+                );
+
+                // Send
+                if ($startDatePlusDays > api_strtotime($firstDate)) {
+                    $sendMail = true;
+                } else {
+                    $sendMail = false;
+                }
+
+                // Send announcement to users
+                if ($sendMail && $alreadyAdded == false) {
+                    $start = $firstDate;
+                    $end = $firstEndDate;
+
+                    if (!empty($end) &&
+                        api_format_date($start, DATE_FORMAT_LONG) == api_format_date($end, DATE_FORMAT_LONG)
+                    ) {
+                        $date = api_format_date($start, DATE_FORMAT_LONG).' ('.
+                            api_format_date($start, TIME_NO_SEC_FORMAT).' '.
+                            api_format_date($end, TIME_NO_SEC_FORMAT).')';
+                    } else {
+                        $date = api_format_date($start,DATE_TIME_FORMAT_LONG_24H).' - '.
+                                api_format_date($end, DATE_TIME_FORMAT_LONG_24H);
+                    }
+
+                    $sessionName = '';
+                    if (!empty($event['session_id'])) {
+                        $sessionName = ' ('.api_get_session_name($event['session_id']).')';
+                    }
+
+                    $courseTitle = $courseInfo['title'].$sessionName;
+
+                    $emailBody = get_lang('Dear').' ((user_firstname)) <br />'.
+                        sprintf(
+                        get_lang('YouHaveBeenSubscribedToCourseXTheStartDateXAndCommentX'),
+                        $courseTitle,
+                        $date,
+                        $event['comment']
+                    );
+
+                    $subject = sprintf(
+                        get_lang('AgendaAvailableInCourseX'),
+                        $courseInfo['title']
+                    );
+
+                    $coaches = SessionManager::getCoachesByCourseSession(
+                        $courseInfo['real_id'],
+                        $event['session_id']
+                    );
+
+                    // Search if an announcement exists:
+                    $announcementsWithTitleList = AnnouncementManager::getAnnouncementsByTitle(
+                        $subject,
+                        $courseInfo['real_id'],
+                        $event['session_id'],
+                        1
+                    );
+
+                    if (count($announcementsWithTitleList) == 0) {
+                        $this->logger->addInfo(
+                            "Mail to be sent because start date: ".$event['start']." and no announcement found."
+                        );
+                        $announcementId = AnnouncementManager::add_announcement(
+                            $courseInfo,
+                            $event['session_id'],
+                            $subject,
+                            $emailBody,
+                            [
+                                'everyone',
+                                'users' => $coaches
+                            ],
+                            [],
+                            null,
+                            null,
+                            false,
+                            $this->defaultAdminId
+                        );
+
+                        if ($announcementId) {
+                            $this->logger->addInfo(
+                                "Announcement added: ".(int)($announcementId)." in $info"
+                            );
+                            $this->logger->addInfo(
+                                "<<--SENDING MAIL-->>"
+                            );
+
+                            $report['mail_sent']++;
+                            AnnouncementManager::sendEmail(
+                                $courseInfo,
+                                $event['session_id'],
+                                $announcementId,
+                                false
+                            );
+                        }
+                    } else {
+                        $report['mail_not_sent_announcement_exists']++;
+                        $this->logger->addInfo(
+                            "Mail NOT sent. An announcement seems to be already saved in $info"
+                        );
+                    }
+                } else {
+                    if ($sendMail == false) {
+                        $report['mail_not_sent_because_date']++;
+                    }
+                }
+
                 $content = '';
                 if ($update && isset($item['item_id'])) {
                     //the event already exists, just update
@@ -986,16 +1146,18 @@ class ImportCsv
                         array(), //$attachmentArray = array(),
                         [], //$attachmentCommentList
                         $eventComment,
-                        $color
+                        $color,
+                        false,
+                        false
                     );
 
                     if ($eventResult !== false) {
                         $this->logger->addInfo(
-                            "Event updated: #".$item['item_id']
+                            "Event updated #".$item['item_id']." External cal Id: (".$externalEventId.") $info"
                         );
                     } else {
                         $this->logger->addInfo(
-                            "Error while updating event."
+                            "Error while updating event with external id: $externalEventId"
                         );
                     }
                 } else {
@@ -1025,15 +1187,28 @@ class ImportCsv
                             )
                         );
                         $this->logger->addInfo(
-                            "Event added: #$eventId"
+                            "Event added: #$eventId External cal id: (".$externalEventId.") $info"
                         );
                     } else {
                         $this->logger->addInfo(
-                            "Error while creating event."
+                            "Error while creating event external id: $externalEventId"
                         );
                     }
                 }
+
+                if (($counter % $batchSize) === 0) {
+                    $em->flush();
+                    $em->clear(); // Detaches all objects from Doctrine!
+                }
+                $counter++;
+            }
+
+            $em->clear(); // Detaches all objects from Doctrine!
+            $this->logger->addInfo('------Summary------');
+            foreach ($report as $title => $count) {
+                $this->logger->addInfo("$title: $count");
             }
+            $this->logger->addInfo('------End Summary------');
         }
 
         if ($moveFile) {
@@ -1179,7 +1354,7 @@ class ImportCsv
      * Parse filename: encora_subsessionsextid-static_31082016.csv
      * @param string $file
      */
-    private function importSubscribeUserToCourseSessionExtStatic($file)
+    private function importSubscribeUserToCourseSessionExtStatic($file, $moveFile = true)
     {
         $data = Import::csv_reader($file);
         if (!empty($data)) {
@@ -1236,7 +1411,6 @@ class ImportCsv
                         break;
                     case 'drh':
                         $removeAllSessionsFromUser = true;
-
                         if (in_array($userId, $userIdList)) {
                             $removeAllSessionsFromUser = false;
                         } else {
@@ -1259,12 +1433,17 @@ class ImportCsv
 
             }
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
-     * @param string $file
+     * @param $file
+     * @param bool $moveFile
      */
-    private function importUnsubSessionsExtIdStatic($file)
+    private function importUnsubSessionsExtIdStatic($file, $moveFile = true)
     {
         $data = Import::csv_reader($file);
 
@@ -1317,13 +1496,16 @@ class ImportCsv
             }
         }
 
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
      *
      * @param string $file
      */
-    private function importSessionsExtIdStatic($file)
+    private function importSessionsExtIdStatic($file, $moveFile = true)
     {
         $data = Import::csv_reader($file);
 
@@ -1386,13 +1568,18 @@ class ImportCsv
                 );
             }
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
      * Updates the session synchronize with the csv file.
+     * @param bool $moveFile
      * @param string $file
      */
-    private function importSessionsStatic($file)
+    private function importSessionsStatic($file, $moveFile = true)
     {
         $content = file($file);
         $sessions = array();
@@ -1414,10 +1601,8 @@ class ImportCsv
                         $tag_name
                     );
                 }
-                if (!in_array('SessionName', $tag_names) || !in_array(
-                        'DateStart',
-                        $tag_names
-                    ) || !in_array('DateEnd', $tag_names)
+                if (!in_array('SessionName', $tag_names) ||
+                    !in_array('DateStart',$tag_names) || !in_array('DateEnd', $tag_names)
                 ) {
                     $error_message = get_lang('NoNeededData');
                     break;
@@ -1650,6 +1835,10 @@ class ImportCsv
         } else {
             $this->logger->addInfo($error_message);
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
@@ -1703,8 +1892,9 @@ class ImportCsv
 
     /**
      * @param string $file
+     * @param bool $moveFile
      */
-    private function importSubscribeStatic($file)
+    private function importSubscribeStatic($file, $moveFile = true)
     {
         $data = Import::csv_reader($file);
 
@@ -1760,6 +1950,10 @@ class ImportCsv
                 );
             }
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
@@ -1773,7 +1967,6 @@ class ImportCsv
         if (!empty($data)) {
             $this->logger->addInfo(count($data) . " records found.");
             foreach ($data as $row) {
-
                 $chamiloUserName = $row['UserName'];
                 $chamiloCourseCode = $row['CourseCode'];
                 $status = $row['Status'];
@@ -1808,10 +2001,17 @@ class ImportCsv
                 );
             }
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**
      * @param string $file
+     * @param bool $moveFile
+     * @param array $teacherBackup
+     * @param array $groupBackup
      */
     private function importUnsubscribeStatic(
         $file,
@@ -1858,9 +2058,7 @@ class ImportCsv
                 $userCourseData = Database::fetch_array($result, 'ASSOC');
                 $teacherBackup[$userId][$courseInfo['code']] = $userCourseData;
 
-                $sql = "SELECT * FROM ".Database::get_course_table(
-                        TABLE_GROUP_USER
-                    )."
+                $sql = "SELECT * FROM ".Database::get_course_table(TABLE_GROUP_USER)."
                         WHERE
                             user_id = ".$userId." AND
                             c_id = '".$courseInfo['real_id']."'
@@ -1893,6 +2091,10 @@ class ImportCsv
                 );
             }
         }
+
+        if ($moveFile) {
+            $this->moveFile($file);
+        }
     }
 
     /**

+ 2 - 1
main/document/downloadfolder.inc.php

@@ -237,7 +237,8 @@ if (api_is_allowed_to_edit()) {
 
     // 2nd: Get all folders that are invisible in the given path
     $sql = "SELECT path, docs.session_id, docs.id, props.to_group_id, docs.c_id
-            FROM $doc_table AS docs INNER JOIN $prop_table AS props
+            FROM $doc_table AS docs 
+            INNER JOIN $prop_table AS props
             ON
                 docs.id = props.ref AND
                 docs.c_id = props.c_id

+ 1 - 1
main/exercise/TestCategory.php

@@ -954,8 +954,8 @@ class TestCategory
 
             $return .= '</table>';
             $return .= get_lang('ZeroMeansNoQuestionWillBeSelectedMinusOneMeansThatAllQuestionsWillBeSelected');
-            return $return;
         }
+        return $return;
     }
 
     /**

+ 8 - 1
main/exercise/admin.php

@@ -393,7 +393,14 @@ if ($inATest) {
     }
 
     echo '</div>';
-    echo '<div class="alert alert-info">'.sprintf(get_lang('XQuestionsWithTotalScoreY'), $objExercise->selectNbrQuestions(), $maxScoreAllQuestions).'</div>';
+    echo '<div class="alert alert-info">'.
+        sprintf(get_lang('XQuestionsWithTotalScoreY'), $objExercise->selectNbrQuestions(), $maxScoreAllQuestions);
+    if ($objExercise->random > 0) {
+        echo '<br />' .
+            sprintf(get_lang('OnlyXQuestionsPickedRandomly'), $objExercise->random);
+    }
+    echo '</div>';
+
 } else if (isset($_GET['newQuestion'])) {
     // we are in create a new question from question pool not in a test
     echo '<div class="actions">';

+ 31 - 14
main/exercise/exercise.class.php

@@ -201,7 +201,7 @@ class Exercise
 
             // Checking if question_order is correctly set
             if ($parseQuestionList) {
-                $this->setQuestionList();
+                $this->setQuestionList(true);
             }
 
             //overload questions list with recorded questions list
@@ -581,7 +581,7 @@ class Exercise
 
             $sql = "SELECT q.iid
                     FROM $TBL_EXERCICE_QUESTION e INNER JOIN $TBL_QUESTIONS  q
-                        ON (e.question_id = q.iid AND e.c_id = ".$this->course_id." )
+                        ON (e.question_id = q.id AND e.c_id = ".$this->course_id." )
 					WHERE e.exercice_id	= '".Database::escape_string($this->id)."'
 					";
 
@@ -1082,11 +1082,12 @@ class Exercise
 
     /**
      * returns the array with the question ID list
-     *
+     * @param   bool    $from_db    Whether the results should be fetched in the database or just from memory
+     * @param   bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
      * @author Olivier Brouckaert
      * @return array - question ID list
      */
-    public function selectQuestionList($from_db = false)
+    public function selectQuestionList($from_db = false, $adminView = false)
     {
         if ($from_db && !empty($this->id)) {
             $nbQuestions = $this->getQuestionCount();
@@ -1101,7 +1102,7 @@ class Exercise
                     if ($this->random == 0 || $nbQuestions < 2) {
                         $questionList = $this->getQuestionOrderedList();
                     } else {
-                        $questionList = $this->selectRandomList();
+                        $questionList = $this->selectRandomList($adminView);
                     }
                     break;
                 default:
@@ -1153,10 +1154,11 @@ class Exercise
      *
      * @author Olivier Brouckaert
      * @author Hubert Borderiou 15 nov 2011
+     * @param    bool    $adminView  Whether we should return all questions (admin view) or just a list limited by the max number of random questions
      * @return array - if the exercise is not set to take questions randomly, returns the question list
      *					 without randomizing, otherwise, returns the list with questions selected randomly
      */
-    public function selectRandomList()
+    public function selectRandomList($adminView = false)
     {
         /*$nbQuestions	= $this->selectNbrQuestions();
         $temp_list		= $this->questionList;
@@ -1191,18 +1193,18 @@ class Exercise
 
         $random = isset($this->random) && !empty($this->random) ? $this->random : 0;
 
-        $randomLimit = "LIMIT $random";
+        $randomLimit = "ORDER BY RAND() LIMIT $random";
         // Random all questions so no limit
-        if ($random == -1) {
-            $randomLimit = null;
+        if ($random == -1 or $adminView === true) {
+            // If viewing it as admin for edition, don't show it randomly, use title + id
+            $randomLimit = 'ORDER BY e.question_order';
         }
 
         $sql = "SELECT e.question_id
                 FROM $TBL_EXERCISE_QUESTION e 
                 INNER JOIN $TBL_QUESTIONS q
-                ON (e.question_id= q.iid AND e.c_id = q.c_id)
+                ON (e.question_id= q.id AND e.c_id = q.c_id)
                 WHERE e.c_id = {$this->course_id} AND e.exercice_id	= '".Database::escape_string($this->id)."'
-                ORDER BY RAND()
                 $randomLimit ";
         $result = Database::query($sql);
         $questionList = array();
@@ -2055,7 +2057,7 @@ class Exercise
                     $option,
                     array(
                         'id' => 'questionSelection',
-                        'onclick' => 'checkQuestionSelection()'
+                        'onchange' => 'checkQuestionSelection()'
                     )
                 );
 
@@ -2100,6 +2102,9 @@ class Exercise
                 // Category selection.
                 $cat = new TestCategory();
                 $cat_form = $cat->returnCategoryForm($this);
+                if (empty($cat_form)) {
+                    $cat_form = '<span class="label label-warning">' . get_lang('NoCategoriesDefined') . '</span>';
+                }
                 $form->addElement('label', null, $cat_form);
                 $form->addElement('html', '</div>');
 
@@ -6062,11 +6067,12 @@ class Exercise
 
     /**
      * Sets the question list when the exercise->read() is executed
+     * @param   bool    $adminView  Whether to view the set the list of *all* questions or just the normal student view
      */
-    public function setQuestionList()
+    public function setQuestionList($adminView = false)
     {
         // Getting question list.
-        $questionList = $this->selectQuestionList(true);
+        $questionList = $this->selectQuestionList(true, $adminView);
 
         $this->setMediaList($questionList);
 
@@ -8479,6 +8485,17 @@ class Exercise
                         case FILL_IN_BLANKS:
                             $isCorrect = FillBlanks::isCorrect($answer['answer']);
                             break;
+                        case MATCHING:
+                            //no break
+                        case DRAGGABLE:
+                            //no break
+                        case MATCHING_DRAGGABLE:
+                            $isCorrect = Matching::isCorrect(
+                                $answer['position'],
+                                $answer['answer'],
+                                $answer['question_id']
+                            );
+                            break;
                         default:
                             $isCorrect = $objAnswer->isCorrectByAutoId($answer['answer']);
                     }

+ 5 - 5
main/exercise/exercise_admin.php

@@ -86,22 +86,22 @@ $htmlHeadXtra[] = '<script>
     }
 
     function checkQuestionSelection() {
-        var selection = $("#questionSelection option:selected").val()
+        var selection = $("#questionSelection option:selected").val();
         switch (selection) {
-            case "\'.EX_Q_SELECTION_ORDERED.\'":
+            case "'.EX_Q_SELECTION_ORDERED.'":
                 disabledHideRandom();
                 $("#hidden_matrix").hide();
                 break;
-            case "\'.EX_Q_SELECTION_RANDOM.\'":
+            case "'.EX_Q_SELECTION_RANDOM.'":
                 $("#hidden_random").show();
                 $("#hidden_matrix").hide();
                 break;
-            case "\'.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.\'":
+            case "'.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.'":
                 disabledHideRandom();
                 $("#hidden_matrix").show();
                 break;
             case "per_categories":
-                $("#questionSelection option:eq(\'.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.\')").prop("selected", true);
+                $("#questionSelection option:eq('.EX_Q_SELECTION_CATEGORIES_ORDERED_QUESTIONS_ORDERED.')").prop("selected", true);
                 disabledHideRandom();
                 $("#hidden_matrix").show();
                 break;

+ 81 - 51
main/exercise/exercise_result.class.php

@@ -9,7 +9,7 @@
 */
 class ExerciseResult
 {
-	private $results = array();
+    private $results = [];
     public $includeAllUsers = false;
     public $onlyBestAttempts = false;
 
@@ -83,9 +83,12 @@ class ExerciseResult
                         te.orig_lp_id as orig_lp_id,
                         tlm.name as lp_name
                 FROM $TBL_EXERCISES  AS ce
-                INNER JOIN $TBL_TRACK_EXERCISES AS te ON (te.exe_exo_id = ce.id)
-                INNER JOIN $TBL_USER  AS user ON (user.user_id = exe_user_id)
-                LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm ON tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id
+                INNER JOIN $TBL_TRACK_EXERCISES AS te 
+                ON (te.exe_exo_id = ce.id)
+                INNER JOIN $TBL_USER AS user 
+                ON (user.user_id = exe_user_id)
+                LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm 
+                ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
                 WHERE
                     ce.c_id = $course_id AND
                     te.status != 'incomplete' AND
@@ -110,9 +113,12 @@ class ExerciseResult
                     te.orig_lp_id as orig_lp_id,
                     tlm.name as lp_name
                     FROM $TBL_EXERCISES  AS ce
-                    INNER JOIN $TBL_TRACK_EXERCISES AS te ON (te.exe_exo_id = ce.id)
-                    INNER JOIN  $TBL_USER  AS user ON (user.user_id = exe_user_id)
-                    LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm ON tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id
+                    INNER JOIN $TBL_TRACK_EXERCISES AS te 
+                    ON (te.exe_exo_id = ce.id)
+                    INNER JOIN $TBL_USER AS user 
+                    ON (user.user_id = exe_user_id)
+                    LEFT JOIN $TBL_TABLE_LP_MAIN AS tlm 
+                    ON (tlm.id = te.orig_lp_id AND tlm.c_id = ce.c_id)
                     WHERE
                         ce.c_id = $course_id AND
                         te.status != 'incomplete' AND
@@ -192,16 +198,16 @@ class ExerciseResult
 
                 $return[$i] = array();
                 if (empty($user_id)) {
-                    $return[$i]['official_code']   = $result['official_code'];
+                    $return[$i]['official_code'] = $result['official_code'];
                     if (api_is_western_name_order()) {
-                        $return[$i]['first_name']   = $results[$i]['userpart1'];
-                        $return[$i]['last_name']    = $results[$i]['userpart2'];
+                        $return[$i]['first_name'] = $results[$i]['userpart1'];
+                        $return[$i]['last_name'] = $results[$i]['userpart2'];
                     } else {
-                        $return[$i]['first_name']   = $results[$i]['userpart2'];
-                        $return[$i]['last_name']    = $results[$i]['userpart1'];
+                        $return[$i]['first_name'] = $results[$i]['userpart2'];
+                        $return[$i]['last_name'] = $results[$i]['userpart1'];
                     }
-                    $return[$i]['user_id']      = $results[$i]['excruid'];
-                    $return[$i]['email']        = $results[$i]['exemail'];
+                    $return[$i]['user_id'] = $results[$i]['excruid'];
+                    $return[$i]['email'] = $results[$i]['exemail'];
                 }
                 $return[$i]['title'] = $result['extitle'];
                 $return[$i]['start_date'] = api_get_local_time($result['exstart']);
@@ -235,17 +241,17 @@ class ExerciseResult
                         $isWestern = api_is_western_name_order();
 
                         if (empty($user_id)) {
-                            $return[$i]['official_code']   = $student['official_code'];
+                            $return[$i]['official_code'] = $student['official_code'];
                             if ($isWestern) {
-                                $return[$i]['first_name']   = $student['firstname'];
-                                $return[$i]['last_name']    = $student['lastname'];
+                                $return[$i]['first_name'] = $student['firstname'];
+                                $return[$i]['last_name'] = $student['lastname'];
                             } else {
-                                $return[$i]['first_name']   = $student['lastname'];
-                                $return[$i]['last_name']    = $student['firstname'];
+                                $return[$i]['first_name'] = $student['lastname'];
+                                $return[$i]['last_name'] = $student['firstname'];
                             }
 
-                            $return[$i]['user_id']      = $student['user_id'];
-                            $return[$i]['email']        = $student['email'];
+                            $return[$i]['user_id'] = $student['user_id'];
+                            $return[$i]['email'] = $student['email'];
                         }
                         $return[$i]['title'] = null;
                         $return[$i]['start_date'] = null;
@@ -271,9 +277,14 @@ class ExerciseResult
 
 	/**
 	 * Exports the complete report as a CSV file
-	 * @param	string		Document path inside the document tool
-	 * @param	integer		Optional user ID
-	 * @param	boolean		Whether to include user fields or not
+     *
+     * @param    string $document_path Document path inside the document tool
+     * @param    integer $user_id Optional user ID
+     * @param    boolean $export_user_fields Whether to include user fields or not
+     * @param    int $export_filter
+     * @param    int $exercise_id
+     * @param    string $hotpotato_name
+     *
 	 * @return	boolean		False on error
 	 */
     public function exportCompleteReportCSV(
@@ -286,24 +297,27 @@ class ExerciseResult
     ) {
         global $charset;
         $this->getExercisesReporting($document_path, $user_id, $export_filter, $exercise_id, $hotpotato_name);
-
-        $filename = 'exercise_results_'.date('YmdGis').'.csv';
-        if(!empty($user_id)) {
-            $filename = 'exercise_results_user_'.$user_id.'_'.date('YmdGis').'.csv';
+        $now = api_get_local_time();
+        $filename = 'exercise_results_'.$now.'.csv';
+        if (!empty($user_id)) {
+            $filename = 'exercise_results_user_'.$user_id.'_'.$now.'.csv';
         }
+
+        $filename = api_replace_dangerous_char($filename);
+
         $data = '';
         if (api_is_western_name_order()) {
-            if(!empty($this->results[0]['first_name'])) {
+            if (!empty($this->results[0]['first_name'])) {
                 $data .= get_lang('FirstName').';';
             }
-            if(!empty($this->results[0]['last_name'])) {
+            if (!empty($this->results[0]['last_name'])) {
                 $data .= get_lang('LastName').';';
             }
         } else {
-            if(!empty($this->results[0]['last_name'])) {
+            if (!empty($this->results[0]['last_name'])) {
                 $data .= get_lang('LastName').';';
             }
-            if(!empty($this->results[0]['first_name'])) {
+            if (!empty($this->results[0]['first_name'])) {
                 $data .= get_lang('FirstName').';';
             }
         }
@@ -317,10 +331,18 @@ class ExerciseResult
 
         if ($export_user_fields) {
             //show user fields section with a big th colspan that spans over all fields
-            $extra_user_fields = UserManager::get_extra_fields(0,1000,5,'ASC',false, 1);
-            $num = count($extra_user_fields);
-            foreach($extra_user_fields as $field) {
-                $data .= '"'.str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($field[3]), ENT_QUOTES, $charset)).'";';
+            $extra_user_fields = UserManager::get_extra_fields(
+                0,
+                1000,
+                5,
+                'ASC',
+                false,
+                1
+            );
+            if (!empty($extra_user_fields)) {
+                foreach ($extra_user_fields as $field) {
+                    $data .= '"'.str_replace("\r\n", '  ', api_html_entity_decode(strip_tags($field[3]), ENT_QUOTES, $charset)).'";';
+                }
             }
         }
 
@@ -337,7 +359,6 @@ class ExerciseResult
 
         //results
         foreach ($this->results as $row) {
-
             if (api_is_western_name_order()) {
                 $data .= str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
                 $data .= str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($row['last_name']), ENT_QUOTES, $charset)).';';
@@ -346,30 +367,40 @@ class ExerciseResult
                 $data .= str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($row['first_name']), ENT_QUOTES, $charset)).';';
             }
 
-            if ($officialCodeInList) {
+            // Official code
+            if ($officialCodeInList === 'true') {
                 $data .= $row['official_code'].';';
             }
 
+            // Email
             $data .= str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($row['email']), ENT_QUOTES, $charset)).';';
             $data .= str_replace("\r\n",'  ',implode(", ", GroupManager :: get_user_group_name($row['user_id']))).';';
 
             if ($export_user_fields) {
                 //show user fields data, if any, for this user
-                $user_fields_values = UserManager::get_extra_user_data($row['user_id'],false,false, false, true);
-                foreach($user_fields_values as $value) {
-                    $data .= '"'.str_replace('"','""',api_html_entity_decode(strip_tags($value), ENT_QUOTES, $charset)).'";';
+                $user_fields_values = UserManager::get_extra_user_data(
+                    $row['user_id'],
+                    false,
+                    false,
+                    false,
+                    true
+                );
+                if (!empty($user_fields_values)) {
+                    foreach($user_fields_values as $value) {
+                        $data .= '"'.str_replace('"','""',api_html_entity_decode(strip_tags($value), ENT_QUOTES, $charset)).'";';
+                    }
                 }
             }
 
-            $data .= str_replace("\r\n",'  ',api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset)).';';
-            $data .= str_replace("\r\n",'  ',$row['start_date']).';';
-            $data .= str_replace("\r\n",'  ',$row['end_date']).';';
-            $data .= str_replace("\r\n",'  ',$row['duration']).';';
-            $data .= str_replace("\r\n",'  ',$row['result']).';';
-            $data .= str_replace("\r\n",'  ',$row['max']).';';
-            $data .= str_replace("\r\n",'  ',$row['status']).';';
-            $data .= str_replace("\r\n",'  ',$row['lp_name']).';';
-            $data .= str_replace("\r\n",'  ',$row['is_user_subscribed']).';';
+            $data .= str_replace("\r\n",'  ', api_html_entity_decode(strip_tags($row['title']), ENT_QUOTES, $charset)).';';
+            $data .= str_replace("\r\n", '  ', $row['start_date']).';';
+            $data .= str_replace("\r\n", '  ', $row['end_date']).';';
+            $data .= str_replace("\r\n", '  ', $row['duration']).';';
+            $data .= str_replace("\r\n", '  ', $row['result']).';';
+            $data .= str_replace("\r\n", '  ', $row['max']).';';
+            $data .= str_replace("\r\n", '  ', $row['status']).';';
+            $data .= str_replace("\r\n", '  ', $row['lp_name']).';';
+            $data .= str_replace("\r\n", '  ', $row['is_user_subscribed']).';';
             $data .= "\n";
         }
 
@@ -502,7 +533,6 @@ class ExerciseResult
 
         foreach ($this->results as $row) {
             $column = 0;
-
             if ($with_column_user) {
                 if (api_is_western_name_order()) {
                     $worksheet->setCellValueByColumnAndRow(

+ 25 - 0
main/exercise/matching.class.php

@@ -275,4 +275,29 @@ class Matching extends Question
 
         return $header;
     }
+
+    /**
+     * Check if a answer is correct
+     * @param int $position
+     * @param int $answer
+     * @return bool
+     */
+    public static function isCorrect($position, $answer, $questionId)
+    {
+        $em = Database::getManager();
+
+        $count = $em
+            ->createQuery('
+                SELECT COUNT(a) From ChamiloCourseBundle:CQuizAnswer a
+                WHERE a.iid = :position AND a.correct = :answer AND a.questionId = :question
+            ')
+            ->setParameters([
+                'position' => $position,
+                'answer' => $answer,
+                'question' => $questionId
+            ])
+            ->getSingleScalarResult();
+
+        return $count ? true : false;
+    }
 }

+ 1 - 1
main/exercise/question_list_admin.inc.php

@@ -194,7 +194,7 @@ if (!$inATest) {
         // Show all questions no matter the category settings.
         $tempCategoryOrder = isset($objExercise->specialCategoryOrders) ? $objExercise->specialCategoryOrders : false;
         $objExercise->specialCategoryOrders = false;
-        $questionList = $objExercise->selectQuestionList(true);
+        $questionList = $objExercise->selectQuestionList(true, true);
         $objExercise->specialCategoryOrders = $tempCategoryOrder;
 
         // Style for columns

+ 15 - 24
main/forum/editpost.php

@@ -125,11 +125,6 @@ $htmlHeadXtra[] = <<<JS
     </script>
 JS;
 
-if ($origin == 'learnpath') {
-    Display::display_reduced_header();
-} else {
-    Display::display_header();
-}
 /* Is the user allowed here? */
 
 // The user is not allowed here if
@@ -139,15 +134,13 @@ if ($origin == 'learnpath') {
 // 4. if editing of replies is not allowed
 // The only exception is the course manager
 // I have split this is several pieces for clarity.
-//if (!api_is_allowed_to_edit() AND (($current_forum_category['visibility'] == 0 OR $current_forum['visibility'] == 0) OR ($current_forum_category['locked'] <> 0 OR $current_forum['locked'] <> 0 OR $current_thread['locked'] <> 0))) {
 if (!api_is_allowed_to_edit(null, true) &&
-    (($current_forum_category && $current_forum_category['visibility'] == 0) ||
-        $current_forum['visibility'] == 0)
+    (
+        ($current_forum_category && $current_forum_category['visibility'] == 0) ||
+        $current_forum['visibility'] == 0
+    )
 ) {
-    $forum_allow = forum_not_allowed_here();
-    if ($forum_allow === false) {
-        exit;
-    }
+    api_not_allowed(true);
 }
 
 if (!api_is_allowed_to_edit(null, true) &&
@@ -157,28 +150,26 @@ if (!api_is_allowed_to_edit(null, true) &&
         $current_thread['locked'] <> 0
     )
 ) {
-    $forum_allow = forum_not_allowed_here();
-    if ($forum_allow === false) {
-        exit;
-    }
+    api_not_allowed(true);
 }
 
 if (!$_user['user_id'] && $current_forum['allow_anonymous'] == 0) {
-    $forum_allow = forum_not_allowed_here();
-    if ($forum_allow === false) {
-        exit;
-    }
+    api_not_allowed(true);
 }
+
 $group_id = api_get_group_id();
 
 if (!api_is_allowed_to_edit(null, true) &&
     $current_forum['allow_edit'] == 0 &&
     !GroupManager::is_tutor_of_group(api_get_user_id(), $group_properties['iid'])
 ) {
-    $forum_allow = forum_not_allowed_here();
-    if ($forum_allow === false) {
-        exit;
-    }
+    api_not_allowed(true);
+}
+
+if ($origin == 'learnpath') {
+    Display::display_reduced_header();
+} else {
+    Display::display_header();
 }
 
 // Action links

+ 99 - 14
main/forum/forumfunction.inc.php

@@ -25,6 +25,7 @@ use Chamilo\CourseBundle\Entity\CForumThread;
  * @todo several functions have to be moved to the itemmanager library
  * @todo displaying icons => display library
  * @todo complete the missing phpdoc the correct order should be
+ * @todo convert into a class
  */
 
 get_notifications_of_user();
@@ -1929,8 +1930,14 @@ function getThreadInfo($threadId, $cId)
  *
  * @return array containing all the information about the posts of a given thread
  */
-function getPosts($forumInfo, $threadId, $orderDirection = 'ASC', $recursive = false, $postId = null, $depth = -1)
-{
+function getPosts(
+    $forumInfo,
+    $threadId,
+    $orderDirection = 'ASC',
+    $recursive = false,
+    $postId = null,
+    $depth = -1
+) {
     $em = Database::getManager();
 
     if (api_is_allowed_to_edit(false, true)) {
@@ -1955,15 +1962,11 @@ function getPosts($forumInfo, $threadId, $orderDirection = 'ASC', $recursive = f
             $filterModerated = false;
         }
     } else {
-        if (GroupManager::is_tutor_of_group(api_get_user_id(), $groupInfo['iid']) || api_is_allowed_to_edit()) {
+        if (GroupManager::is_tutor_of_group(api_get_user_id(), $groupInfo['iid']) || api_is_allowed_to_edit(false, true)) {
             $filterModerated = false;
         }
     }
 
-    if ($filterModerated && $forumInfo['moderated'] == 1) {
-        $criteria->andWhere(Criteria::expr()->eq('status', 1));
-    }
-
     if ($recursive) {
         $criteria->andWhere(Criteria::expr()->eq('postParentId', $postId));
     }
@@ -1973,8 +1976,20 @@ function getPosts($forumInfo, $threadId, $orderDirection = 'ASC', $recursive = f
         ->addCriteria($criteria)
         ->addOrderBy('p.postId', $orderDirection);
 
-    $posts = $qb->getQuery()->getResult();
 
+    if ($filterModerated && $forumInfo['moderated'] == 1) {
+        if (!api_is_allowed_to_edit(false, true)) {
+            $userId = api_get_user_id();
+            $qb->andWhere(
+                "p.status = 1 OR 
+                    (p.status = ".CForumPost::STATUS_WAITING_MODERATION." AND p.posterId = $userId) OR
+                    (p.status IS NULL AND p.posterId = $userId) 
+                    "
+            );
+        }
+    }
+
+    $posts = $qb->getQuery()->getResult();
     $depth++;
 
     $list = [];
@@ -2016,7 +2031,6 @@ function getPosts($forumInfo, $threadId, $orderDirection = 'ASC', $recursive = f
         if (!$recursive) {
             continue;
         }
-
         $list = array_merge(
             $list,
             getPosts(
@@ -3937,6 +3951,8 @@ function updateThreadInfo($thread_id, $last_post_id, $post_date)
  * This function is called when the user is not allowed in this forum/thread/...
  * @return bool display message of "not allowed"
  *
+ * @deprecated use api_not_allowed()
+ *
  * @author Patrick Cool <patrick.cool@UGent.be>, Ghent University
  * @version february 2006, dokeos 1.8
  */
@@ -6053,24 +6069,93 @@ function getForumCategoryByTitle($title, $courseId, $sessionId = 0)
     return $resultData;
 }
 
-function getPostStatus($current_forum, $row)
+/**
+ * @param array $current_forum
+ * @param array $row
+ *
+ * @return string
+ */
+function getPostStatus($current_forum, $row, $addWrapper = true)
 {
     $statusIcon = '';
     if ($current_forum['moderated']) {
-        $statusIcon = '<br /><br />';
+        if ($addWrapper) {
+            $statusIcon = '<br /><br /><span id="status_post_'.$row['iid'].'">';
+        }
         $row['status'] = empty($row['status']) ? 2 : $row['status'];
+
+        $addUrl = false;
+        if (api_is_allowed_to_edit(false, true)) {
+            $addUrl = true;
+        }
+
+        $label = '';
+        $icon = '';
+        $buttonType = '';
         switch ($row['status']) {
             case CForumPost::STATUS_VALIDATED:
-                $statusIcon .= Display::label(get_lang('Validated'), 'success');
+                $label = get_lang('Validated');
+                $icon = 'check-circle';
+                $buttonType = 'success';
                 break;
             case CForumPost::STATUS_WAITING_MODERATION:
-                $statusIcon .= Display::label(get_lang('WaitingModeration'), 'warning');
+                $label = get_lang('WaitingModeration');
+                $icon = 'warning';
+                $buttonType = 'warning';
                 break;
             case CForumPost::STATUS_REJECTED:
-                $statusIcon .= Display::label(get_lang('Rejected'), 'danger');
+                $label = get_lang('Rejected');
+                $icon = 'minus-circle';
+                $buttonType = 'danger';
                 break;
         }
+
+        if ($addUrl) {
+            $statusIcon .= Display::toolbarButton(
+                $label.'&nbsp;',
+                'javascript:void(0)',
+                $icon,
+                $buttonType,
+                ['class' => 'change_post_status']
+            );
+        } else {
+            $statusIcon .= Display::label(
+                Display::returnFontAwesomeIcon($icon).$label,
+                $buttonType
+            );
+        }
+
+        if ($addWrapper) {
+            $statusIcon .= '</span>';
+        }
     }
 
     return $statusIcon;
 }
+
+/**
+ * @param array $forumInfo
+ * @param int $threadId
+ * @param int $status
+ * @return mixed
+ */
+function getCountPostsWithStatus($status, $forumInfo, $threadId = null)
+{
+    $em = Database::getManager();
+    $criteria = Criteria::create();
+    $criteria
+        ->where(Criteria::expr()->eq('status', $status))
+        ->andWhere(Criteria::expr()->eq('cId', $forumInfo['c_id']))
+        ->andWhere(Criteria::expr()->eq('visible', 1))
+    ;
+
+    if (!empty($threadId)) {
+        $criteria->andWhere(Criteria::expr()->eq('threadId', $threadId));
+    }
+
+    $qb = $em->getRepository('ChamiloCourseBundle:CForumPost')->createQueryBuilder('p');
+    $qb->select('count(p.iid)')
+        ->addCriteria($criteria);
+
+    return $qb->getQuery()->getSingleScalarResult();
+}

+ 2 - 5
main/forum/iframe_thread.php

@@ -51,11 +51,8 @@ $current_forum_category = get_forumcategory_information(
 
 // if the user is not a course administrator and the forum is hidden
 // then the user is not allowed here.
-if (!api_is_allowed_to_edit(false, true) AND ($current_forum['visibility'] == 0 OR $current_thread['visibility'] == 0)) {
-    $forum_allow = forum_not_allowed_here();
-    if ($forum_allow === false) {
-        exit;
-    }
+if (!api_is_allowed_to_edit(false, true) && ($current_forum['visibility'] == 0 || $current_thread['visibility'] == 0)) {
+    api_not_allowed(false);
 }
 
 $course_id = api_get_course_int_id();

+ 15 - 0
main/forum/index.php

@@ -2,6 +2,7 @@
 /* For licensing terms, see /license.txt */
 
 use ChamiloSession as Session;
+use Chamilo\CourseBundle\Entity\CForumPost;
 
 /**
  * These files are a complete rework of the forum. The database structure is
@@ -509,6 +510,20 @@ if (is_array($forumCategories)) {
                                 'class'=>'description'
                             ]
                         );
+
+                        if ($forum['moderated'] == 1 && api_is_allowed_to_edit(false, true)) {
+                            $waitingCount = getCountPostsWithStatus(
+                                CForumPost::STATUS_WAITING_MODERATION,
+                                $forum
+                            );
+                            if (!empty($waitingCount)) {
+                                $html .= Display::label(
+                                    get_lang('PostsPendingModeration'). ': '.$waitingCount,
+                                    'warning'
+                                );
+                            }
+                        }
+
                         $html .= '</div>';
                         $html .= '</div>';
 

+ 18 - 4
main/forum/viewforum.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CourseBundle\Entity\CForumPost;
+
 /**
  * These files are a complete rework of the forum. The database structure is
  * based on phpBB but all the code is rewritten. A lot of new functionalities
@@ -460,11 +462,8 @@ if (is_array($threads)) {
                 $newPost = '';
             }
 
-            if ($row['thread_sticky'] == 1) {
-                //$sticky = Display::return_icon('exclamation.gif');
-            }
-
             $name = api_get_person_name($row['firstname'], $row['lastname']);
+
             $linkPostForum = '<a href="viewthread.php?' . api_get_cidreq() . '&forum=' . Security::remove_XSS($my_forum)
                 . "&origin=$origin&thread={$row['thread_id']}&search="
                 . Security::remove_XSS(urlencode($my_search)) . '">'
@@ -514,6 +513,21 @@ if (is_array($threads)) {
             );
             $html .= '<p>'. get_lang('By') .' ' .$authorName.'</p>';
             $html .= '<p>' . api_convert_and_format_date($row['insert_date']) . '</p>';
+
+            if ($current_forum['moderated'] == 1 && api_is_allowed_to_edit(false, true)) {
+                $waitingCount = getCountPostsWithStatus(
+                    CForumPost::STATUS_WAITING_MODERATION,
+                    $current_forum,
+                    $row['thread_id']
+                );
+                if (!empty($waitingCount)) {
+                    $html .= Display::label(
+                        get_lang('PostsPendingModeration'). ': '.$waitingCount,
+                        'warning'
+                    );
+                }
+            }
+
             $html .= '</div>';
             $html .= '</div>';
 

+ 17 - 1
main/forum/viewforumcategory.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CourseBundle\Entity\CForumPost;
+
 /**
  * These files are a complete rework of the forum. The database structure is
  * based on phpBB but all the code is rewritten. A lot of new functionalities
@@ -268,7 +270,7 @@ if ($action_forums != 'add') {
             // SHOULD WE SHOW THIS PARTICULAR FORUM
             // you are teacher => show forum
 
-            if (api_is_allowed_to_edit(false,true)) {
+            if (api_is_allowed_to_edit(false, true)) {
                 //echo 'teacher';
                 $show_forum = true;
             } else {
@@ -390,6 +392,20 @@ if ($action_forums != 'add') {
                         'class' => 'description'
                     )
                 );
+
+                if ($forum['moderated'] == 1 && api_is_allowed_to_edit(false, true)) {
+                    $waitingCount = getCountPostsWithStatus(
+                        CForumPost::STATUS_WAITING_MODERATION,
+                        $forum
+                    );
+                    if (!empty($waitingCount)) {
+                        $html .= Display::label(
+                            get_lang('PostsPendingModeration'). ': '.$waitingCount,
+                            'warning'
+                        );
+                    }
+                }
+
                 $html .= '</div>';
                 $html .= '</div>';
                 $html .= '<div class="col-md-6">';

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels