Переглянути джерело

Merge branch '1.11.x' into add-captcha-password-recovery

Yannick Warnier 7 роки тому
батько
коміт
a74c52b50f
88 змінених файлів з 6001 додано та 4394 видалено
  1. 4 4
      app/Resources/public/assets/i18next/.bower.json
  2. 27 0
      app/Resources/public/assets/i18next/CHANGELOG.md
  3. 1 1
      app/Resources/public/assets/i18next/LICENSE
  4. 57 28
      app/Resources/public/assets/i18next/i18next.js
  5. 0 0
      app/Resources/public/assets/i18next/i18next.min.js
  6. 4 4
      app/Resources/public/assets/imagesloaded/.bower.json
  7. 5 5
      app/Resources/public/assets/imagesloaded/README.md
  8. 0 5
      app/Resources/public/assets/imagesloaded/gulpfile.js
  9. 2 2
      app/Resources/public/assets/imagesloaded/imagesloaded.js
  10. 3 3
      app/Resources/public/assets/imagesloaded/imagesloaded.pkgd.js
  11. 1 1
      app/Resources/public/assets/imagesloaded/imagesloaded.pkgd.min.js
  12. 5 5
      app/Resources/public/assets/simpleWebRTC/.bower.json
  13. 6 0
      app/Resources/public/assets/simpleWebRTC/.gitignore
  14. 1 1
      app/Resources/public/assets/simpleWebRTC/.jshintignore
  15. 34 0
      app/Resources/public/assets/simpleWebRTC/.travis.yml
  16. 213 6
      app/Resources/public/assets/simpleWebRTC/README.md
  17. 4 4
      app/Resources/public/assets/simpleWebRTC/build.js
  18. 2 3
      app/Resources/public/assets/simpleWebRTC/index.html
  19. 0 0
      app/Resources/public/assets/simpleWebRTC/latest-v2.js
  20. 0 0
      app/Resources/public/assets/simpleWebRTC/latest.js
  21. 30 14
      app/Resources/public/assets/simpleWebRTC/package.json
  22. 302 0
      app/Resources/public/assets/simpleWebRTC/peer.js
  23. 3100 2760
      app/Resources/public/assets/simpleWebRTC/simplewebrtc.bundle.js
  24. 34 28
      app/Resources/public/assets/simpleWebRTC/simplewebrtc.js
  25. 1 1
      app/Resources/public/assets/simpleWebRTC/socketioconnection.js
  26. 26 0
      app/Resources/public/assets/simpleWebRTC/test/run-selenium
  27. 7 0
      app/Resources/public/assets/simpleWebRTC/test/selenium/index.js
  28. 54 0
      app/Resources/public/assets/simpleWebRTC/test/selenium/p2p.js
  29. 33 0
      app/Resources/public/assets/simpleWebRTC/test/selenium/selenium-lib.js
  30. 101 0
      app/Resources/public/assets/simpleWebRTC/test/selenium/three.js
  31. 157 0
      app/Resources/public/assets/simpleWebRTC/webrtc.js
  32. 12 4
      app/Resources/public/css/base.css
  33. 3 0
      app/Resources/public/css/editor.css
  34. 1 1
      bower.json
  35. 123 153
      main/admin/ldap_users_list.php
  36. 1 1
      main/announcements/announcements.php
  37. 1 1
      main/auth/catalog_layout.php
  38. 13 2
      main/auth/courses.php
  39. 97 88
      main/auth/courses_categories.php
  40. 19 7
      main/auth/courses_controller.php
  41. 41 35
      main/auth/courses_list.php
  42. 1 1
      main/auth/gotocourse.php
  43. 38 7
      main/auth/inscription.php
  44. 13 7
      main/auth/lostPassword.php
  45. 1 1
      main/auth/profile.php
  46. 6 1
      main/auth/reset.php
  47. 5 1
      main/auth/set_temp_password.php
  48. 17 3
      main/auth/unsubscribe_account.php
  49. 16 4
      main/coursecopy/copy_course_session.php
  50. 0 2
      main/cron/import_csv.php
  51. 14 6
      main/inc/ajax/user_manager.ajax.php
  52. 2 0
      main/inc/lib/CoursesAndSessionsCatalog.class.php
  53. 20 8
      main/inc/lib/attendance.lib.php
  54. 2 2
      main/inc/lib/banner.lib.php
  55. 181 181
      main/inc/lib/course.lib.php
  56. 1 1
      main/inc/lib/course_category.lib.php
  57. 22 2
      main/inc/lib/course_home.lib.php
  58. 1 1
      main/inc/lib/extra_field_value.lib.php
  59. BIN
      main/inc/lib/javascript/chat/img/webrtc_chrome.png
  60. BIN
      main/inc/lib/javascript/chat/img/webrtc_firefox.png
  61. 4 1
      main/inc/lib/javascript/chat/video.php
  62. 37 26
      main/inc/lib/myspace.lib.php
  63. 10 3
      main/inc/lib/pdf.lib.php
  64. 5 4
      main/inc/lib/usermanager.lib.php
  65. 33 6
      main/mySpace/myStudents.php
  66. 245 248
      main/session/session_export.php
  67. 3 3
      main/template/default/auth/session_catalog.tpl
  68. 9 3
      main/template/default/chat/video.tpl
  69. 23 11
      main/webservices/access_url.php
  70. 8 1
      plugin/buycourses/src/service_process_confirm.php
  71. 18 7
      plugin/buycourses/src/service_success.php
  72. 0 5
      plugin/buycourses/src/services_edit.php
  73. 11 5
      plugin/buycourses/src/success.php
  74. 7 7
      plugin/dashboard/block_course/block_course.class.php
  75. 1 5
      plugin/dashboard/block_daily/block_daily.class.php
  76. 122 110
      plugin/dashboard/block_evaluation_graph/block_evaluation_graph.class.php
  77. 60 60
      plugin/dashboard/block_global_info/block_global_info.class.php
  78. 102 110
      plugin/dashboard/block_session/block_session.class.php
  79. 183 175
      plugin/dashboard/block_student/block_student.class.php
  80. 99 96
      plugin/dashboard/block_student_graph/block_student_graph.class.php
  81. 0 3
      plugin/dashboard/block_teacher/block_teacher.class.php
  82. 79 78
      plugin/dashboard/block_teacher_graph/block_teacher_graph.class.php
  83. 2 6
      plugin/studentfollowup/Entity/CarePost.php
  84. 15 1
      plugin/studentfollowup/StudentFollowUpPlugin.php
  85. 23 2
      plugin/studentfollowup/post.php
  86. 1 1
      plugin/studentfollowup/posts.php
  87. 35 19
      plugin/studentfollowup/view/post.html.twig
  88. 1 3
      plugin/studentfollowup/view/posts.html.twig

+ 4 - 4
app/Resources/public/assets/i18next/.bower.json

@@ -18,12 +18,12 @@
     "karma.conf.js"
   ],
   "homepage": "https://github.com/i18next/i18next",
-  "version": "7.1.3",
-  "_release": "7.1.3",
+  "version": "8.2.1",
+  "_release": "8.2.1",
   "_resolution": {
     "type": "version",
-    "tag": "v7.1.3",
-    "commit": "eb036b4b3db9c2dd2f0dc079ec4f07e01da23590"
+    "tag": "v8.2.1",
+    "commit": "030a200df42689ebdc0f334a0fc6f3deb22e6e82"
   },
   "_source": "https://github.com/i18next/i18next.git",
   "_target": ">=2.4.0",

+ 27 - 0
app/Resources/public/assets/i18next/CHANGELOG.md

@@ -1,3 +1,30 @@
+### 8.2.1
+- fixes issue in async lng detection
+
+### 8.2.0
+- supports now async language detection - when language detector has member async = true
+
+### 8.1.0
+- option to disable nesting by calling `i18next.t('key', { nest: false })` [PR920](https://github.com/i18next/i18next/pull/920)
+- fixes passing lng from options to formatter function if available
+
+### 8.0.0
+- nonExplicitWhitelist flag now not only gets considered on a fallback lng but also on user language. Eg. userlng 'de-AT' and whitelist ['de'] will now let de-AT pass as whitelisted if nonExplicitWhitelist is set true.
+
+### 7.2.3
+- rebuild seems we had a mistake in pushing latest build
+
+### 7.2.2
+- fixes issue in accepting string as a result of "nested" lookup [PR909](https://github.com/i18next/i18next/pull/909)
+
+### 7.2.1
+- fixes iterating over object's own properties [#904](https://github.com/i18next/i18next/pull/904)
+
+### 7.2.0
+- new init option simplifyPluralSuffix - setting it to false will treat all plurals using suffix numbers even for locals only having singular and plural
+- even if no lng set or detected at least load the fallback languages
+- delay init call on createInstance if not set initImmediate to false [#879](https://github.com/i18next/i18next/issues/879)
+
 ### 7.1.3
 - fixes issue in returnObject tree called with options including ns: [react-i18next #240](https://github.com/i18next/react-i18next/issues/240)
 

+ 1 - 1
app/Resources/public/assets/i18next/LICENSE

@@ -1,6 +1,6 @@
 The MIT License (MIT)
 
-Copyright (c) 2015 i18next
+Copyright (c) 2017 i18next
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 57 - 28
app/Resources/public/assets/i18next/i18next.js

@@ -279,16 +279,20 @@ function getLastOfPath(object, path, Empty) {
     return key && key.indexOf('###') > -1 ? key.replace(/###/g, '.') : key;
   }
 
+  function canNotTraverseDeeper() {
+    return !object || typeof object === 'string';
+  }
+
   var stack = typeof path !== 'string' ? [].concat(path) : path.split('.');
   while (stack.length > 1) {
-    if (!object) return {};
+    if (canNotTraverseDeeper()) return {};
 
     var key = cleanKey(stack.shift());
     if (!object[key] && Empty) object[key] = new Empty();
     object = object[key];
   }
 
-  if (!object) return {};
+  if (canNotTraverseDeeper()) return {};
   return {
     obj: object,
     k: cleanKey(stack.shift())
@@ -752,7 +756,9 @@ var Translator = function (_EventEmitter) {
         var copy$$1 = resType === '[object Array]' ? [] : {}; // apply child translation on a copy
 
         for (var m in res) {
-          copy$$1[m] = this.translate('' + key + keySeparator + m, _extends({}, options, { joinArrays: false, ns: namespaces }));
+          if (res.hasOwnProperty(m)) {
+            copy$$1[m] = this.translate('' + key + keySeparator + m, _extends({}, options, { joinArrays: false, ns: namespaces }));
+          }
         }
         res = copy$$1;
       }
@@ -827,10 +833,10 @@ var Translator = function (_EventEmitter) {
     // interpolate
     var data = options.replace && typeof options.replace !== 'string' ? options.replace : options;
     if (this.options.interpolation.defaultVariables) data = _extends({}, this.options.interpolation.defaultVariables, data);
-    res = this.interpolator.interpolate(res, data, this.language);
+    res = this.interpolator.interpolate(res, data, options.lng || this.language);
 
     // nesting
-    res = this.interpolator.nest(res, function () {
+    if (options.nest !== false) res = this.interpolator.nest(res, function () {
       for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
         args[_key] = arguments[_key];
       }
@@ -984,11 +990,11 @@ var LanguageUtil = function () {
     }
   };
 
-  LanguageUtil.prototype.isWhitelisted = function isWhitelisted(code, exactMatch) {
-    if (this.options.load === 'languageOnly' || this.options.nonExplicitWhitelist && !exactMatch) {
+  LanguageUtil.prototype.isWhitelisted = function isWhitelisted(code) {
+    if (this.options.load === 'languageOnly' || this.options.nonExplicitWhitelist) {
       code = this.getLanguagePartFromCode(code);
     }
-    return !this.whitelist || !this.whitelist.length || this.whitelist.indexOf(code) > -1 ? true : false;
+    return !this.whitelist || !this.whitelist.length || this.whitelist.indexOf(code) > -1;
   };
 
   LanguageUtil.prototype.getFallbackCodes = function getFallbackCodes(fallbacks, code) {
@@ -1014,10 +1020,8 @@ var LanguageUtil = function () {
 
     var codes = [];
     var addCode = function addCode(code) {
-      var exactMatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
-
       if (!code) return;
-      if (_this.isWhitelisted(code, exactMatch)) {
+      if (_this.isWhitelisted(code)) {
         codes.push(code);
       } else {
         _this.logger.warn('rejecting non-whitelisted language code: ' + code);
@@ -1025,8 +1029,8 @@ var LanguageUtil = function () {
     };
 
     if (typeof code === 'string' && code.indexOf('-') > -1) {
-      if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code), true);
-      if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code), true);
+      if (this.options.load !== 'languageOnly') addCode(this.formatLanguageCode(code));
+      if (this.options.load !== 'languageOnly' && this.options.load !== 'currentOnly') addCode(this.getScriptPartFromCode(code));
       if (this.options.load !== 'currentOnly') addCode(this.getLanguagePartFromCode(code));
     } else if (typeof code === 'string') {
       addCode(this.formatLanguageCode(code));
@@ -1166,7 +1170,7 @@ var PluralResolver = function () {
       var suffix = rule.numbers[idx];
 
       // special treatment for lngs only having singular and plural
-      if (rule.numbers.length === 2 && rule.numbers[0] === 1) {
+      if (this.options.simplifyPluralSuffix && rule.numbers.length === 2 && rule.numbers[0] === 1) {
         if (suffix === 2) {
           suffix = 'plural';
         } else if (suffix === 1) {
@@ -1697,6 +1701,7 @@ function get$1() {
     load: 'all', // | currentOnly | languageOnly
     preload: false, // array with preload languages
 
+    simplifyPluralSuffix: true,
     keySeparator: '.',
     nsSeparator: ':',
     pluralSeparator: '_',
@@ -1770,7 +1775,15 @@ var I18n = function (_EventEmitter) {
     _this.logger = baseLogger;
     _this.modules = { external: [] };
 
-    if (callback && !_this.isInitialized && !options.isClone) _this.init(options, callback);
+    if (callback && !_this.isInitialized && !options.isClone) {
+      var _ret;
+
+      // https://github.com/i18next/i18next/issues/879
+      if (!_this.options.initImmediate) return _ret = _this.init(options, callback), possibleConstructorReturn(_this, _ret);
+      setTimeout(function () {
+        _this.init(options, callback);
+      }, 0);
+    }
     return _this;
   }
 
@@ -1816,7 +1829,7 @@ var I18n = function (_EventEmitter) {
         s.cacheConnector.save();
       });
       s.languageUtils = lu;
-      s.pluralResolver = new PluralResolver(lu, { prepend: this.options.pluralSeparator, compatibilityJSON: this.options.compatibilityJSON });
+      s.pluralResolver = new PluralResolver(lu, { prepend: this.options.pluralSeparator, compatibilityJSON: this.options.compatibilityJSON, simplifyPluralSuffix: this.options.simplifyPluralSuffix });
       s.interpolator = new Interpolator(this.options);
 
       s.backendConnector = new Connector(createClassOnDemand(this.modules.backend), s.resourceStore, s, this.options);
@@ -1911,11 +1924,19 @@ var I18n = function (_EventEmitter) {
         });
       };
 
-      append(this.language);
+      if (!this.language) {
+        // at least load fallbacks in this case
+        var fallbacks = this.services.languageUtils.getFallbackCodes(this.options.fallbackLng);
+        fallbacks.forEach(function (l) {
+          return append(l);
+        });
+      } else {
+        append(this.language);
+      }
 
       if (this.options.preload) {
         this.options.preload.forEach(function (l) {
-          append(l);
+          return append(l);
         });
       }
 
@@ -1979,20 +2000,28 @@ var I18n = function (_EventEmitter) {
       });
     };
 
-    if (!lng && this.services.languageDetector) lng = this.services.languageDetector.detect();
+    var setLng = function setLng(l) {
+      if (l) {
+        _this4.language = l;
+        _this4.languages = _this4.services.languageUtils.toResolveHierarchy(l);
 
-    if (lng) {
-      this.language = lng;
-      this.languages = this.services.languageUtils.toResolveHierarchy(lng);
+        _this4.translator.changeLanguage(l);
 
-      this.translator.changeLanguage(lng);
+        if (_this4.services.languageDetector) _this4.services.languageDetector.cacheUserLanguage(l);
+      }
 
-      if (this.services.languageDetector) this.services.languageDetector.cacheUserLanguage(lng);
-    }
+      _this4.loadResources(function (err) {
+        done(err);
+      });
+    };
 
-    this.loadResources(function (err) {
-      done(err);
-    });
+    if (!lng && this.services.languageDetector && !this.services.languageDetector.async) {
+      setLng(this.services.languageDetector.detect());
+    } else if (!lng && this.services.languageDetector && this.services.languageDetector.async) {
+      this.services.languageDetector.detect(setLng);
+    } else {
+      setLng(lng);
+    }
   };
 
   I18n.prototype.getFixedT = function getFixedT(lng, ns) {

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
app/Resources/public/assets/i18next/i18next.min.js


+ 4 - 4
app/Resources/public/assets/imagesloaded/.bower.json

@@ -33,12 +33,12 @@
     "images"
   ],
   "license": "MIT",
-  "version": "4.1.1",
-  "_release": "4.1.1",
+  "version": "4.1.2",
+  "_release": "4.1.2",
   "_resolution": {
     "type": "version",
-    "tag": "v4.1.1",
-    "commit": "2646b88563dbc19ef44da453be92d9be3bdd78d6"
+    "tag": "v4.1.2",
+    "commit": "60ce45d675567970993a52cb11763ba9b49aa5d0"
   },
   "_source": "https://github.com/desandro/imagesloaded.git",
   "_target": ">=3.0.0",

+ 5 - 5
app/Resources/public/assets/imagesloaded/README.md

@@ -10,15 +10,15 @@ Detect when images have been loaded.
 
 ### Download
 
-+ [imagesloaded.pkgd.min.js](https://npmcdn.com/imagesloaded@4/imagesloaded.pkgd.min.js) minified
-+ [imagesloaded.pkgd.js](https://npmcdn.com/imagesloaded@4/imagesloaded.pkgd.js) un-minified
++ [imagesloaded.pkgd.min.js](https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js) minified
++ [imagesloaded.pkgd.js](https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.js) un-minified
 
 ### CDN
 
 ``` html
-<script src="https://npmcdn.com/imagesloaded@4.1/imagesloaded.pkgd.min.js"></script>
+<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js"></script>
 <!-- or -->
-<script src="https://npmcdn.com/imagesloaded@4.1/imagesloaded.pkgd.js"></script>
+<script src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.js"></script>
 ```
 
 ### Package managers
@@ -219,7 +219,7 @@ _Image_ - The `img` element
 
 ### LoadingImage.isLoaded
 
-_Boolean_ - `true` when the image has succesfully loaded
+_Boolean_ - `true` when the image has successfully loaded
 
 ### imagesLoaded.images
 

+ 0 - 5
app/Resources/public/assets/imagesloaded/gulpfile.js

@@ -157,11 +157,6 @@ gulp.task( 'version', function() {
   gulp.src( [ 'bower.json', 'package.json' ] )
     .pipe( replace( /"version": "\d+\.\d+\.\d+"/, '"version": "' + version + '"' ) )
     .pipe( gulp.dest('.') );
-  // replace CDN links in README
-  var minorVersion = version.match( /^\d+\.\d+/ )[0];
-  gulp.src('README.md')
-    .pipe( replace( /imagesloaded@\d+\.\d+/g, 'imagesloaded@' + minorVersion ))
-    .pipe( gulp.dest('.') );
 });
 
 // ----- default ----- //

+ 2 - 2
app/Resources/public/assets/imagesloaded/imagesloaded.js

@@ -1,5 +1,5 @@
 /*!
- * imagesLoaded v4.1.1
+ * imagesLoaded v4.1.2
  * JavaScript is all like "You images are done yet or what?"
  * MIT License
  */
@@ -30,7 +30,7 @@
     );
   }
 
-})( window,
+})( typeof window !== 'undefined' ? window : this,
 
 // --------------------------  factory -------------------------- //
 

+ 3 - 3
app/Resources/public/assets/imagesloaded/imagesloaded.pkgd.js

@@ -1,5 +1,5 @@
 /*!
- * imagesLoaded PACKAGED v4.1.1
+ * imagesLoaded PACKAGED v4.1.2
  * JavaScript is all like "You images are done yet or what?"
  * MIT License
  */
@@ -115,7 +115,7 @@ return EvEmitter;
 }));
 
 /*!
- * imagesLoaded v4.1.1
+ * imagesLoaded v4.1.2
  * JavaScript is all like "You images are done yet or what?"
  * MIT License
  */
@@ -146,7 +146,7 @@ return EvEmitter;
     );
   }
 
-})( window,
+})( typeof window !== 'undefined' ? window : this,
 
 // --------------------------  factory -------------------------- //
 

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
app/Resources/public/assets/imagesloaded/imagesloaded.pkgd.min.js


+ 5 - 5
app/Resources/public/assets/simpleWebRTC/.bower.json

@@ -1,14 +1,14 @@
 {
   "name": "simpleWebRTC",
   "homepage": "https://github.com/HenrikJoreteg/SimpleWebRTC",
-  "version": "1.15.4",
-  "_release": "1.15.4",
+  "version": "2.2.3",
+  "_release": "2.2.3",
   "_resolution": {
     "type": "version",
-    "tag": "v1.15.4",
-    "commit": "6f84fbcd42bd6ab1af8134ef0c495e8a6c3175f7"
+    "tag": "2.2.3",
+    "commit": "0eeba55fd186a1ee45e06665e5be7e30aab4b5dd"
   },
   "_source": "https://github.com/HenrikJoreteg/SimpleWebRTC.git",
-  "_target": "1.15.*",
+  "_target": "2.2.*",
   "_originalSource": "simpleWebRTC"
 }

+ 6 - 0
app/Resources/public/assets/simpleWebRTC/.gitignore

@@ -1,3 +1,9 @@
 node_modules
 .DS_Store
 npm-debug.log
+
+browsers
+firefox*.bz2
+
+# JetBrains IDEs
+.idea

+ 1 - 1
app/Resources/public/assets/simpleWebRTC/.jshintignore

@@ -1,4 +1,4 @@
 node_modules
 *.bundle.js
 socket.io.js
-latest.js
+latest-v2.js

+ 34 - 0
app/Resources/public/assets/simpleWebRTC/.travis.yml

@@ -0,0 +1,34 @@
+sudo: false
+language: node_js
+node_js:
+  - 6
+addons:
+  apt:
+    sources:
+      - ubuntu-toolchain-r-test
+    packages:
+      - g++-4.8
+cache:
+  directories:
+    - node_modules
+
+env:
+  global:
+    - CXX=g++-4.8
+  matrix:
+    - BROWSER=chrome  BVER=stable
+
+before_script:
+  - ./node_modules/travis-multirunner/setup.sh
+  - export DISPLAY=:99.0
+  - sh -e /etc/init.d/xvfb start
+
+script:
+  - npm run test-travis
+
+after_failure:
+  - for file in *.log; do echo $file; echo "======================"; cat $file; done || true
+
+notifications:
+  email:
+  - 

+ 213 - 6
app/Resources/public/assets/simpleWebRTC/README.md

@@ -3,7 +3,7 @@
 [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/HenrikJoreteg/SimpleWebRTC?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 
-Want to see it in action? Check out the demo: https://talky.io/
+Want to see it in action? Check out the demo: https://simplewebrtc.com/demo.html
 
 
 ## It's so easy:
@@ -14,7 +14,7 @@ Want to see it in action? Check out the demo: https://talky.io/
 <!DOCTYPE html>
 <html>
     <head>
-        <script src="http://simplewebrtc.com/latest.js"></script> 
+        <script src="https://simplewebrtc.com/latest-v2.js"></script>
         <style>
             #remoteVideos video {
                 height: 150px;
@@ -58,17 +58,30 @@ webrtc.on('readyToCall', function () {
 ### Available options
 
 
-`peerConnectionConfig` - Set this to specify your own STUN and TURN servers. By default, SimpleWebRTC uses Google's public STUN server (`stun.l.google.com:19302`), which is intended for public use according to: https://twitter.com/HenrikJoreteg/status/354105684591251456
+`peerConnectionConfig` - Set this to specify your own STUN and TURN servers. By
+default, SimpleWebRTC uses Google's public STUN server
+(`stun.l.google.com:19302`), which is intended for public use according to:
+https://twitter.com/HenrikJoreteg/status/354105684591251456
 
-Note that you will most likely also need to run your own TURN servers. See http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ for a basic tutorial. 
+Note that you will most likely also need to run your own TURN servers. See
+http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ for a basic
+tutorial.
+
+## Filetransfer
+Sending files between individual participants is supported. See
+http://simplewebrtc.com/filetransfer.html for a demo.
+
+Note that this is not file sharing between a group which requires a completely
+different approach.
 
 ## It's not always that simple...
 
-Sometimes you need to do more advanced stuff. See http://simplewebrtc.com/notsosimple.html for some examples.
+Sometimes you need to do more advanced stuff. See
+http://simplewebrtc.com/notsosimple.html for some examples.
 
 ## Got questions?
 
-Join the SimpleWebRTC discussion list: 
+Join the SimpleWebRTC discussion list:
 
 http://lists.andyet.com/mailman/listinfo/simplewebrtc
 
@@ -77,3 +90,197 @@ or the Gitter channel:
 https://gitter.im/HenrikJoreteg/SimpleWebRTC
 
 ## API
+
+### Constructor
+
+`new SimpleWebRTC(options)`
+
+- `object options` - options object provided to constructor consisting of:
+  - `string url` - *required* url for signaling server. Defaults to signaling
+  server URL which can be used for development. You must use your own signaling
+  server for production.
+  - `object socketio` - *optional* object to be passed as options to the signaling
+  server connection.
+  - `Connection connection` - *optional* connection object for signaling. See
+  `Connection` below. Defaults to a new SocketIoConnection
+  - `bool debug` - *optional* flag to set the instance to debug mode
+  - `[string|DomElement] localVideoEl` - ID or Element to contain the local video
+  element
+  - `[string|DomElement] remoteVideosEl` - ID or Element to contain the
+  remote video elements
+  - `bool autoRequestMedia` - *optional(=false)* option to automatically request
+  user media. Use `true` to request automatically, or `false` to request media
+  later with `startLocalVideo`
+  - `bool enableDataChannels` *optional(=true)* option to enable/disable data
+  channels (used for volume levels or direct messaging)
+  - `bool autoRemoveVideos` - *optional(=true)* option to automatically remove
+  video elements when streams are stopped.
+  - `bool adjustPeerVolume` - *optional(=false)* option to reduce peer volume
+  when the local participant is speaking
+  - `number peerVolumeWhenSpeaking` - *optional(=.0.25)* value used in
+  conjunction with `adjustPeerVolume`. Uses values between 0 and 1.
+  - `object media` - media options to be passed to `getUserMedia`. Defaults to
+  `{ video: true, audio: true }`. Valid configurations described
+  [on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
+  with official spec
+  [at w3c](http://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia).
+  - `object receiveMedia` - *optional* RTCPeerConnection options. Defaults to
+  `{ offerToReceiveAudio: 1, offerToReceiveVideo: 1 }`.
+  - `object localVideo` - *optional* options for attaching the local video
+  stream to the page. Defaults to
+  ```javascript
+  {
+      autoplay: true, // automatically play the video stream on the page
+      mirror: true, // flip the local video to mirror mode (for UX)
+      muted: true // mute local video stream to prevent echo
+  }
+  ```
+  - `object logger` - *optional* alternate logger for the instance; any object
+  that implements `log`, `warn`, and `error` methods.
+
+### Fields
+
+`capabilities` - the
+[`webrtcSupport`](https://github.com/HenrikJoreteg/webrtcsupport) object that
+describes browser capabilities, for convenience
+
+`config` - the configuration options extended from options passed to the
+constructor
+
+`connection` - the socket (or alternate) signaling connection
+
+`webrtc` - the underlying WebRTC session manager
+
+### Events
+
+To set up event listeners, use the SimpleWebRTC instance created with the
+constructor. Example:
+
+```javascript
+var webrtc = new SimpleWebRTC(options);
+webrtc.on('connectionReady', function (sessionId) {
+    // ...
+})
+```
+
+`'connectionReady', sessionId` - emitted when the signaling connection emits the
+`connect` event, with the unique id for the session.
+
+`'createdPeer', peer` - emitted three times:
+
+- when joining a room with existing peers, once for each peer
+- when a new peer joins a joined room
+- when sharing screen, once for each peer
+
+- `peer` - the object representing the peer and underlying peer connection
+
+`'stunservers', [...args]` - emitted when the signaling connection emits the
+same event
+
+`'turnservers', [...args]` - emitted when the signaling connection emits the
+same event
+
+`'localScreenAdded', el` - emitted after triggering the start of screen sharing
+
+- `el` the element that contains the local screen stream
+
+`'leftRoom', roomName` - emitted after successfully leaving the current room,
+ending all peers, and stopping the local screen stream
+
+`'videoAdded', videoEl, peer` - emitted when a peer stream is added
+
+- `videoEl` - the video element associated with the stream that was added
+- `peer` - the peer associated with the stream that was added
+
+`'videoRemoved', videoEl, peer` - emitted when a peer stream is removed
+
+- `videoEl` - the video element associated with the stream that was removed
+- `peer` - the peer associated with the stream that was removed
+
+### Methods
+
+`createRoom(name, callback)` - emits the `create` event on the connection with
+`name` and (if provided) invokes `callback` on response
+
+`joinRoom(name, callback)` - joins the conference in room `name`. Callback is
+invoked with `callback(err, roomDescription)` where `roomDescription` is yielded
+by the connection on the `join` event. See [signalmaster](https://github.com/andyet/signalmaster) for more details.
+
+`startLocalVideo()` - starts the local media with the `media` options provided
+in the config passed to the constructor
+
+`testReadiness()` - tests that the connection is ready and that (if media is
+enabled) streams have started
+
+`mute()` - mutes the local audio stream for all peers (pauses sending audio)
+
+`unmute()` - unmutes local audio stream for all peers (resumes sending audio)
+
+`pauseVideo()` - pauses sending video to peers
+
+`resumeVideo()` - resumes sending video to all peers
+
+`pause()` - pauses sending audio and video to all peers
+
+`resume()` - resumes sending audio and video to all peers
+
+`sendToAll(messageType, payload)` - broadcasts a message to all peers in the
+room via the signaling channel (websocket)
+
+- `string messageType` - the key for the type of message being sent
+- `object payload` - an arbitrary value or object to send to peers
+
+`sendDirectlyToAll(channelLabel, messageType, payload)` - broadcasts a message
+to all peers in the room via a dataChannel
+
+- `string channelLabel` - the label for the dataChannel to send on
+- `string messageType` - the key for the type of message being sent
+- `object payload` - an arbitrary value or object to send to peers
+
+`getPeers(sessionId, type)` - returns all peers by `sessionId` and/or `type`
+
+`shareScreen(callback)` - initiates screen capture request to browser, then
+adds the stream to the conference
+
+`getLocalScreen()` - returns the local screen stream
+
+`stopScreenShare()` - stops the screen share stream and removes it from the room
+
+`stopLocalVideo()` - stops all local media streams
+
+`setVolumeForAll(volume)` - used to set the volume level for all peers
+
+- `volume` - the volume level, between 0 and 1
+
+`leaveRoom()` - leaves the currently joined room and stops local screen share
+
+`disconnect()` - calls `disconnect` on the signaling connection and deletes it
+
+`handlePeerStreamAdded(peer)` - used internally to attach media stream to the
+DOM and perform other setup
+
+`handlePeerStreamRemoved(peer)` - used internally to remove the video container
+from the DOM and emit `videoRemoved`
+
+`getDomId(peer)` - used internally to get the DOM id associated with a peer
+
+`getEl(idOrEl)` - helper used internally to get an element where `idOrEl` is
+either an element, or an id of an element
+
+`getLocalVideoContainer()` - used internally to get the container that will hold
+the local video element
+
+`getRemoteVideoContainer()` - used internally to get the container that holds
+the remote video elements
+
+### Connection
+
+By default, SimpleWebRTC uses a [socket.io](http://socket.io/) connection to
+communicate with the signaling server. However, you can provide an alternate
+connection object to use. All that your alternate connection need provide are
+four methods:
+
+- `on(ev, fn)` - A method to invoke `fn` when event `ev` is triggered
+- `emit()` - A method to send/emit arbitrary arguments on the connection
+- `getSessionId()` - A method to get a unique session Id for the connection
+- `disconnect()` - A method to disconnect the connection

+ 4 - 4
app/Resources/public/assets/simpleWebRTC/build.js

@@ -1,13 +1,13 @@
-var bundle = require('browserify')(),
+var bundle = require('browserify')({standalone: 'SimpleWebRTC'}),
     fs = require('fs'),
     request = require('request'),
     uglify = require('uglify-js');
 
 bundle.add('./simplewebrtc');
-bundle.bundle({standalone: 'SimpleWebRTC'}, function (err, source) {
+bundle.bundle(function (err, source) {
     if (err) console.error(err);
     fs.writeFileSync('simplewebrtc.bundle.js', source);
-    fs.writeFile('latest.js', uglify.minify(source, {fromString: true}).code, function (err) {
-        if (err) throw err;
+    fs.writeFile('latest-v2.js', uglify.minify(source.toString('utf8'), {fromString: true}).code, function (err) {
+      if (err) throw err;
     });
 });

+ 2 - 3
app/Resources/public/assets/simpleWebRTC/index.html

@@ -36,7 +36,7 @@
             <div id="localVolume" class="volume_bar"></div>
         </div>
         <div id="remotes"></div>
-        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
+        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
         <script src="simplewebrtc.bundle.js"></script>
         <script>
             // grab the room from the URL
@@ -51,8 +51,7 @@
                 // immediately ask for camera access
                 autoRequestMedia: true,
                 debug: false,
-                detectSpeakingEvents: true,
-                autoAdjustMic: false
+                detectSpeakingEvents: true
             });
 
             // when it's ready, join if we got a room from the URL

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
app/Resources/public/assets/simpleWebRTC/latest-v2.js


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
app/Resources/public/assets/simpleWebRTC/latest.js


+ 30 - 14
app/Resources/public/assets/simpleWebRTC/package.json

@@ -1,28 +1,44 @@
 {
   "name": "simplewebrtc",
-  "version": "1.15.4",
+  "version": "2.2.3",
   "repository": {
     "type": "git",
-    "url": "git@github.com:henrikjoreteg/SimpleWebRTC.git"
+    "url": "https://github.com/andyet/SimpleWebRTC.git"
   },
   "main": "./simplewebrtc.js",
   "description": "World's easiest webrtc",
   "dependencies": {
-    "webrtcsupport": "^1.0.0",
-    "wildemitter": "1.x",
-    "socket.io-client": "0.9.16",
-    "attachmediastream": "1.0.1",
-    "webrtc": "1.14.x",
+    "filetransfer": "^2.0.4",
+    "localmedia": "^2.2.1",
+    "rtcpeerconnection": "^5.1.1",
+    "webrtcsupport": "^2.2.0",
+    "wildemitter": "^1.2.0",
+    "socket.io-client": "1.3.7",
+    "attachmediastream": "^1.4.1",
     "mockconsole": "0.0.1"
   },
   "devDependencies": {
-    "express": "3.x.x",
-    "browserify": "2.22.0",
-    "precommit-hook": "0.3.x",
-    "request": "",
-    "uglify-js": "2.3.6"
+    "browserify": "^13.1.0",
+    "chromedriver": "^2.16.0",
+    "express": "^4.14.1",
+    "geckodriver": "^1.3.0",
+    "precommit-hook": "^3.0.0",
+    "request": "^2.72.0",
+    "selenium-webdriver": "^3.0.1",
+    "tape": "^4.0.0",
+    "testling": "^1.7.1",
+    "travis-multirunner": "^3.0.0",
+    "uglify-js": "^2.7.3"
   },
+  "license": "MIT",
   "scripts": {
-    "updateLatest": "./scripts/updateLatest.sh"
-  }
+    "test-travis": "test/run-selenium",
+    "updateLatest": "./scripts/updateLatest.sh",
+    "lint": "jshint .",
+    "validate": "npm ls"
+  },
+  "pre-commit": [
+    "lint",
+    "validate"
+  ]
 }

+ 302 - 0
app/Resources/public/assets/simpleWebRTC/peer.js

@@ -0,0 +1,302 @@
+var util = require('util');
+var webrtcSupport = require('webrtcsupport');
+var PeerConnection = require('rtcpeerconnection');
+var WildEmitter = require('wildemitter');
+var FileTransfer = require('filetransfer');
+
+// the inband-v1 protocol is sending metadata inband in a serialized JSON object
+// followed by the actual data. Receiver closes the datachannel upon completion
+var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1';
+
+function isAllTracksEnded(stream) {
+    var isAllTracksEnded = true;
+    stream.getTracks().forEach(function (t) {
+        isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
+    });
+    return isAllTracksEnded;
+}
+
+function Peer(options) {
+    var self = this;
+
+    // call emitter constructor
+    WildEmitter.call(this);
+
+    this.id = options.id;
+    this.parent = options.parent;
+    this.type = options.type || 'video';
+    this.oneway = options.oneway || false;
+    this.sharemyscreen = options.sharemyscreen || false;
+    this.browserPrefix = options.prefix;
+    this.stream = options.stream;
+    this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels;
+    this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia;
+    this.channels = {};
+    this.sid = options.sid || Date.now().toString();
+    // Create an RTCPeerConnection via the polyfill
+    this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints);
+    this.pc.on('ice', this.onIceCandidate.bind(this));
+    this.pc.on('endOfCandidates', function (event) {
+        self.send('endOfCandidates', event);
+    });
+    this.pc.on('offer', function (offer) {
+        if (self.parent.config.nick) offer.nick = self.parent.config.nick;
+        self.send('offer', offer);
+    });
+    this.pc.on('answer', function (answer) {
+        if (self.parent.config.nick) answer.nick = self.parent.config.nick;
+        self.send('answer', answer);
+    });
+    this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this));
+    this.pc.on('addChannel', this.handleDataChannelAdded.bind(this));
+    this.pc.on('removeStream', this.handleStreamRemoved.bind(this));
+    // Just fire negotiation needed events for now
+    // When browser re-negotiation handling seems to work
+    // we can use this as the trigger for starting the offer/answer process
+    // automatically. We'll just leave it be for now while this stabalizes.
+    this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded'));
+    this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange'));
+    this.pc.on('iceConnectionStateChange', function () {
+        switch (self.pc.iceConnectionState) {
+        case 'failed':
+            // currently, in chrome only the initiator goes to failed
+            // so we need to signal this to the peer
+            if (self.pc.pc.peerconnection.localDescription.type === 'offer') {
+                self.parent.emit('iceFailed', self);
+                self.send('connectivityError');
+            }
+            break;
+        }
+    });
+    this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange'));
+    this.logger = this.parent.logger;
+
+    // handle screensharing/broadcast mode
+    if (options.type === 'screen') {
+        if (this.parent.localScreen && this.sharemyscreen) {
+            this.logger.log('adding local screen stream to peer connection');
+            this.pc.addStream(this.parent.localScreen);
+            this.broadcaster = options.broadcaster;
+        }
+    } else {
+        this.parent.localStreams.forEach(function (stream) {
+            self.pc.addStream(stream);
+        });
+    }
+
+    this.on('channelOpen', function (channel) {
+        if (channel.protocol === INBAND_FILETRANSFER_V1) {
+            channel.onmessage = function (event) {
+                var metadata = JSON.parse(event.data);
+                var receiver = new FileTransfer.Receiver();
+                receiver.receive(metadata, channel);
+                self.emit('fileTransfer', metadata, receiver);
+                receiver.on('receivedFile', function (file, metadata) {
+                    receiver.channel.close();
+                });
+            };
+        }
+    });
+
+    // proxy events to parent
+    this.on('*', function () {
+        self.parent.emit.apply(self.parent, arguments);
+    });
+}
+
+util.inherits(Peer, WildEmitter);
+
+Peer.prototype.handleMessage = function (message) {
+    var self = this;
+
+    this.logger.log('getting', message.type, message);
+
+    if (message.prefix) this.browserPrefix = message.prefix;
+
+    if (message.type === 'offer') {
+        if (!this.nick) this.nick = message.payload.nick;
+        delete message.payload.nick;
+        this.pc.handleOffer(message.payload, function (err) {
+            if (err) {
+                return;
+            }
+            // auto-accept
+            self.pc.answer(function (err, sessionDescription) {
+                //self.send('answer', sessionDescription);
+            });
+        });
+    } else if (message.type === 'answer') {
+        if (!this.nick) this.nick = message.payload.nick;
+        delete message.payload.nick;
+        this.pc.handleAnswer(message.payload);
+    } else if (message.type === 'candidate') {
+        this.pc.processIce(message.payload);
+    } else if (message.type === 'connectivityError') {
+        this.parent.emit('connectivityError', self);
+    } else if (message.type === 'mute') {
+        this.parent.emit('mute', {id: message.from, name: message.payload.name});
+    } else if (message.type === 'unmute') {
+        this.parent.emit('unmute', {id: message.from, name: message.payload.name});
+    } else if (message.type === 'endOfCandidates') {
+        // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the
+        // shim this will only be called in Edge.
+        var mLines = this.pc.pc.peerconnection.transceivers || [];
+        mLines.forEach(function (mLine) {
+            if (mLine.iceTransport) {
+                mLine.iceTransport.addRemoteCandidate({});
+            }
+        });
+    }
+};
+
+// send via signalling channel
+Peer.prototype.send = function (messageType, payload) {
+    var message = {
+        to: this.id,
+        sid: this.sid,
+        broadcaster: this.broadcaster,
+        roomType: this.type,
+        type: messageType,
+        payload: payload,
+        prefix: webrtcSupport.prefix
+    };
+    this.logger.log('sending', messageType, message);
+    this.parent.emit('message', message);
+};
+
+// send via data channel
+// returns true when message was sent and false if channel is not open
+Peer.prototype.sendDirectly = function (channel, messageType, payload) {
+    var message = {
+        type: messageType,
+        payload: payload
+    };
+    this.logger.log('sending via datachannel', channel, messageType, message);
+    var dc = this.getDataChannel(channel);
+    if (dc.readyState != 'open') return false;
+    dc.send(JSON.stringify(message));
+    return true;
+};
+
+// Internal method registering handlers for a data channel and emitting events on the peer
+Peer.prototype._observeDataChannel = function (channel) {
+    var self = this;
+    channel.onclose = this.emit.bind(this, 'channelClose', channel);
+    channel.onerror = this.emit.bind(this, 'channelError', channel);
+    channel.onmessage = function (event) {
+        self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
+    };
+    channel.onopen = this.emit.bind(this, 'channelOpen', channel);
+};
+
+// Fetch or create a data channel by the given name
+Peer.prototype.getDataChannel = function (name, opts) {
+    if (!webrtcSupport.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported'));
+    var channel = this.channels[name];
+    opts || (opts = {});
+    if (channel) return channel;
+    // if we don't have one by this label, create it
+    channel = this.channels[name] = this.pc.createDataChannel(name, opts);
+    this._observeDataChannel(channel);
+    return channel;
+};
+
+Peer.prototype.onIceCandidate = function (candidate) {
+    if (this.closed) return;
+    if (candidate) {
+        var pcConfig = this.parent.config.peerConnectionConfig;
+        if (webrtcSupport.prefix === 'moz' && pcConfig && pcConfig.iceTransports &&
+                candidate.candidate && candidate.candidate.candidate &&
+                candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) {
+            this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports);
+        } else {
+            this.send('candidate', candidate);
+        }
+    } else {
+        this.logger.log("End of candidates.");
+    }
+};
+
+Peer.prototype.start = function () {
+    var self = this;
+
+    // well, the webrtc api requires that we either
+    // a) create a datachannel a priori
+    // b) do a renegotiation later to add the SCTP m-line
+    // Let's do (a) first...
+    if (this.enableDataChannels) {
+        this.getDataChannel('simplewebrtc');
+    }
+
+    this.pc.offer(this.receiveMedia, function (err, sessionDescription) {
+        //self.send('offer', sessionDescription);
+    });
+};
+
+Peer.prototype.icerestart = function () {
+    var constraints = this.receiveMedia;
+    constraints.mandatory.IceRestart = true;
+    this.pc.offer(constraints, function (err, success) { });
+};
+
+Peer.prototype.end = function () {
+    if (this.closed) return;
+    this.pc.close();
+    this.handleStreamRemoved();
+};
+
+Peer.prototype.handleRemoteStreamAdded = function (event) {
+    var self = this;
+    if (this.stream) {
+        this.logger.warn('Already have a remote stream');
+    } else {
+        this.stream = event.stream;
+
+        this.stream.getTracks().forEach(function (track) {
+            track.addEventListener('ended', function () {
+                if (isAllTracksEnded(self.stream)) {
+                    self.end();
+                }
+            });
+        });
+
+        this.parent.emit('peerStreamAdded', this);
+    }
+};
+
+Peer.prototype.handleStreamRemoved = function () {
+    var peerIndex = this.parent.peers.indexOf(this);
+    if (peerIndex > -1) {
+        this.parent.peers.splice(peerIndex, 1);
+        this.closed = true;
+        this.parent.emit('peerStreamRemoved', this);
+    }
+};
+
+Peer.prototype.handleDataChannelAdded = function (channel) {
+    this.channels[channel.label] = channel;
+    this._observeDataChannel(channel);
+};
+
+Peer.prototype.sendFile = function (file) {
+    var sender = new FileTransfer.Sender();
+    var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), {
+        protocol: INBAND_FILETRANSFER_V1
+    });
+    // override onopen
+    dc.onopen = function () {
+        dc.send(JSON.stringify({
+            size: file.size,
+            name: file.name
+        }));
+        sender.send(file, dc);
+    };
+    // override onclose
+    dc.onclose = function () {
+        console.log('sender received transfer');
+        sender.emit('complete');
+    };
+    return sender;
+};
+
+module.exports = Peer;

Різницю між файлами не показано, бо вона завелика
+ 3100 - 2760
app/Resources/public/assets/simpleWebRTC/simplewebrtc.bundle.js


+ 34 - 28
app/Resources/public/assets/simpleWebRTC/simplewebrtc.js

@@ -1,4 +1,4 @@
-var WebRTC = require('webrtc');
+var WebRTC = require('./webrtc');
 var WildEmitter = require('wildemitter');
 var webrtcSupport = require('webrtcsupport');
 var attachMediaStream = require('attachmediastream');
@@ -9,7 +9,7 @@ function SimpleWebRTC(opts) {
     var self = this;
     var options = opts || {};
     var config = this.config = {
-            url: 'https://signaling.simplewebrtc.com:443/',
+            url: 'https://sandbox.simplewebrtc.com:443/',
             socketio: {/* 'force new connection':true*/},
             connection: null,
             debug: false,
@@ -18,17 +18,15 @@ function SimpleWebRTC(opts) {
             enableDataChannels: true,
             autoRequestMedia: false,
             autoRemoveVideos: true,
-            adjustPeerVolume: true,
+            adjustPeerVolume: false,
             peerVolumeWhenSpeaking: 0.25,
             media: {
                 video: true,
                 audio: true
             },
-            receiveMedia: { // FIXME: remove old chrome <= 37 constraints format
-                mandatory: {
-                    OfferToReceiveAudio: true,
-                    OfferToReceiveVideo: true
-                }
+            receiveMedia: {
+                offerToReceiveAudio: 1,
+                offerToReceiveVideo: 1
             },
             localVideo: {
                 autoplay: true,
@@ -57,7 +55,9 @@ function SimpleWebRTC(opts) {
 
     // set our config from options
     for (item in options) {
-        this.config[item] = options[item];
+        if (options.hasOwnProperty(item)) {
+            this.config[item] = options[item];
+        }
     }
 
     // attach detected support for convenience
@@ -128,7 +128,7 @@ function SimpleWebRTC(opts) {
     this.webrtc = new WebRTC(opts);
 
     // attach a few methods from underlying lib to simple.
-    ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll'].forEach(function (method) {
+    ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers'].forEach(function (method) {
         self[method] = self.webrtc[method].bind(self.webrtc);
     });
 
@@ -218,10 +218,8 @@ function SimpleWebRTC(opts) {
                     sharemyscreen: true,
                     enableDataChannels: false,
                     receiveMedia: {
-                        mandatory: {
-                            OfferToReceiveAudio: false,
-                            OfferToReceiveVideo: false
-                        }
+                        offerToReceiveAudio: 0,
+                        offerToReceiveVideo: 0
                     },
                     broadcaster: self.connection.getSessionid(),
                 });
@@ -231,7 +229,9 @@ function SimpleWebRTC(opts) {
         });
     });
     this.webrtc.on('localScreenStopped', function (stream) {
-        self.stopScreenShare();
+        if (self.getLocalScreen()) {
+            self.stopScreenShare();
+        }
         /*
         self.connection.emit('unshareScreen');
         self.webrtc.peers.forEach(function (peer) {
@@ -261,9 +261,9 @@ SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
 SimpleWebRTC.prototype.leaveRoom = function () {
     if (this.roomName) {
         this.connection.emit('leave');
-        this.webrtc.peers.forEach(function (peer) {
-            peer.end();
-        });
+        while (this.webrtc.peers.length) {
+            this.webrtc.peers[0].end();
+        }
         if (this.getLocalScreen()) {
             this.stopScreenShare();
         }
@@ -328,6 +328,7 @@ SimpleWebRTC.prototype.joinRoom = function (name, cb) {
     var self = this;
     this.roomName = name;
     this.connection.emit('join', name, function (err, roomDescription) {
+        console.log('join CB', err, roomDescription);
         if (err) {
             self.emit('error', err);
         } else {
@@ -344,10 +345,8 @@ SimpleWebRTC.prototype.joinRoom = function (name, cb) {
                             type: type,
                             enableDataChannels: self.config.enableDataChannels && type !== 'screen',
                             receiveMedia: {
-                                mandatory: {
-                                    OfferToReceiveAudio: type !== 'screen' && self.config.receiveMedia.mandatory.OfferToReceiveAudio,
-                                    OfferToReceiveVideo: self.config.receiveMedia.mandatory.OfferToReceiveVideo
-                                }
+                                offerToReceiveAudio: type !== 'screen' && self.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
+                                offerToReceiveVideo: self.config.receiveMedia.offerToReceiveVideo
                             }
                         });
                         self.emit('createdPeer', peer);
@@ -419,7 +418,6 @@ SimpleWebRTC.prototype.stopScreenShare = function () {
     this.connection.emit('unshareScreen');
     var videoEl = document.getElementById('localScreen');
     var container = this.getRemoteVideoContainer();
-    var stream = this.getLocalScreen();
 
     if (this.config.autoRemoveVideos && container && videoEl) {
         container.removeChild(videoEl);
@@ -427,24 +425,32 @@ SimpleWebRTC.prototype.stopScreenShare = function () {
 
     // a hack to emit the event the removes the video
     // element that we want
-    if (videoEl) this.emit('videoRemoved', videoEl);
-    if (stream) stream.stop();
+    if (videoEl) {
+        this.emit('videoRemoved', videoEl);
+    }
+    if (this.getLocalScreen()) {
+        this.webrtc.stopScreenShare();
+    }
     this.webrtc.peers.forEach(function (peer) {
         if (peer.broadcaster) {
             peer.end();
         }
     });
-    //delete this.webrtc.localScreen;
 };
 
 SimpleWebRTC.prototype.testReadiness = function () {
     var self = this;
-    if (this.webrtc.localStream && this.sessionReady) {
-        self.emit('readyToCall', self.connection.getSessionid());
+    if (this.sessionReady) {
+        if (!this.config.media.video && !this.config.media.audio) {
+            self.emit('readyToCall', self.connection.getSessionid());
+        } else if (this.webrtc.localStreams.length > 0) {
+            self.emit('readyToCall', self.connection.getSessionid());
+        }
     }
 };
 
 SimpleWebRTC.prototype.createRoom = function (name, cb) {
+    this.roomName = name;
     if (arguments.length === 2) {
         this.connection.emit('create', name, cb);
     } else {

+ 1 - 1
app/Resources/public/assets/simpleWebRTC/socketioconnection.js

@@ -13,7 +13,7 @@ SocketIoConnection.prototype.emit = function () {
 };
 
 SocketIoConnection.prototype.getSessionid = function () {
-    return this.connection.socket.sessionid;
+    return this.connection.id;
 };
 
 SocketIoConnection.prototype.disconnect = function () {

+ 26 - 0
app/Resources/public/assets/simpleWebRTC/test/run-selenium

@@ -0,0 +1,26 @@
+#!/bin/sh
+#
+# Installs Chrome or Firefox if missing and sets default browser to
+# chrome stable.
+#
+echo -e "\nPreparing Selenium webdriver tests."
+
+BINDIR=./browsers/bin
+export BROWSER=chrome
+export BVER=stable
+BROWSERBIN=$BINDIR/$BROWSER-$BVER
+if [ ! -x $BROWSERBIN ]; then
+  echo "Installing Chrome"
+  ./node_modules/travis-multirunner/setup.sh
+fi
+export BROWSER=firefox
+export BVER=stable
+BROWSERBIN=$BINDIR/$BROWSER-$BVER
+if [ ! -x $BROWSERBIN ]; then
+  echo "Installing Firefox"
+  ./node_modules/travis-multirunner/setup.sh
+fi
+echo "Starting browser."
+PATH=$PATH:./node_modules/.bin
+
+node test/selenium/index.js

+ 7 - 0
app/Resources/public/assets/simpleWebRTC/test/selenium/index.js

@@ -0,0 +1,7 @@
+// Test 1-1 selenium.
+require('./p2p');
+
+// Test three-party mesh.
+// apparently too much for travis :-(
+// TODO: investigate sauce labs
+//require('./three');

+ 54 - 0
app/Resources/public/assets/simpleWebRTC/test/selenium/p2p.js

@@ -0,0 +1,54 @@
+'use strict';
+var test = require('tape');
+
+// https://code.google.com/p/selenium/wiki/WebDriverJs
+var seleniumHelpers = require('./selenium-lib');
+var webdriver = require('selenium-webdriver');
+
+function doJoin(driver, room) {
+    return driver.get('file://' + process.cwd() + '/index.html?' + room);
+}
+
+function testP2P(browserA, browserB, t) {
+    var room = 'testing_' + Math.floor(Math.random() * 100000);
+
+    var userA = seleniumHelpers.buildDriver(browserA);
+    doJoin(userA, room);
+
+    var userB = seleniumHelpers.buildDriver(browserB);
+    doJoin(userB, room);
+
+    userA.wait(function () {
+        return userA.executeScript(function () {
+            return window.webrtc.getPeers().length === 1 && window.webrtc.getPeers()[0].pc.iceConnectionState === 'connected';
+        });
+    }, 30 * 1000)
+    .then(function () {
+        t.pass('P2P connected');
+        userA.quit();
+        userB.quit().then(function () {
+            t.end();
+        });
+    })
+    .then(null, function (err) {
+        t.fail(err);
+        userA.quit();
+        userB.quit();
+    });
+}
+
+test('P2P, Chrome-Chrome', function (t) {
+    testP2P('chrome', 'chrome', t);
+});
+
+test('P2P, Firefox-Firefox', function (t) {
+    testP2P('firefox', 'firefox', t);
+});
+
+test('P2P, Chrome-Firefox', function (t) {
+    testP2P('chrome', 'firefox', t);
+});
+
+test('P2P, Firefox-Chrome', function (t) {
+    testP2P('firefox', 'chrome', t);
+});

+ 33 - 0
app/Resources/public/assets/simpleWebRTC/test/selenium/selenium-lib.js

@@ -0,0 +1,33 @@
+// https://code.google.com/p/selenium/wiki/WebDriverJs
+var webdriver = require('selenium-webdriver');
+var chrome = require('selenium-webdriver/chrome');
+var firefox = require('selenium-webdriver/firefox');
+
+function buildDriver(browser) {
+    // Firefox options.
+    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
+    var profile = new firefox.Profile();
+    profile.setPreference('media.navigator.streams.fake', true);
+    var firefoxOptions = new firefox.Options()
+        .setBinary('browsers/bin/firefox-stable')
+        .setProfile(profile);
+
+    // Chrome options.
+    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_chrome_class_Options.html#addArguments
+    var chromeOptions = new chrome.Options()
+        .setChromeBinaryPath('browsers/bin/chrome-stable')
+        .addArguments('allow-file-access-from-files')
+        .addArguments('use-fake-device-for-media-stream')
+        .addArguments('use-fake-ui-for-media-stream');
+        // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
+
+    return new webdriver.Builder()
+        .forBrowser(browser || process.env.BROWSER || 'firefox')
+        .setFirefoxOptions(firefoxOptions)
+        .setChromeOptions(chromeOptions)
+        .build();
+}
+
+module.exports = {
+    buildDriver: buildDriver
+};

+ 101 - 0
app/Resources/public/assets/simpleWebRTC/test/selenium/three.js

@@ -0,0 +1,101 @@
+'use strict';
+var test = require('tape');
+
+// https://code.google.com/p/selenium/wiki/WebDriverJs
+var webdriver = require('selenium-webdriver');
+var chrome = require('selenium-webdriver/chrome');
+var firefox = require('selenium-webdriver/firefox');
+
+function buildDriver(browser) {
+    // Firefox options.
+    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
+    var profile = new firefox.Profile();
+    profile.setPreference('media.navigator.streams.fake', true);
+    var firefoxOptions = new firefox.Options()
+        .setProfile(profile);
+
+    // Chrome options.
+    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_chrome_class_Options.html#addArguments
+    var chromeOptions = new chrome.Options()
+        /*
+        .addArguments('enable-logging=1')
+        .addArguments('v=1')
+        .addArguments('vmodule=*libjingle/source/talk/*=4')
+        .addArguments('user-data-dir=/some/where')
+        */
+        .addArguments('allow-file-access-from-files')
+        .addArguments('use-fake-device-for-media-stream')
+        .addArguments('use-fake-ui-for-media-stream');
+        // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
+
+    return new webdriver.Builder()
+      .forBrowser(browser || process.env.BROWSER || 'firefox')
+      .setFirefoxOptions(firefoxOptions)
+      .setChromeOptions(chromeOptions)
+      .build();
+}
+
+function doJoin(driver, room) {
+    return driver.get('file://' + process.cwd() + '/index.html?' + room);
+}
+
+function test3(browserA, browserB, browserC, t) {
+    var room = 'testing_' + Math.floor(Math.random() * 100000);
+
+    var userA = buildDriver(browserA);
+    doJoin(userA, room);
+
+    var userB = buildDriver(browserB);
+    doJoin(userB, room);
+
+    var userC = buildDriver(browserC);
+    doJoin(userC, room);
+    userA.wait(function () {
+        return userA.executeScript('return (function() {' +
+            'var connected = 0;' +
+            'webrtc.getPeers().forEach(function (peer) {' +
+            '  if (peer.pc.iceConnectionState === \'connected\' || peer.pc.iceConnectionState === \'completed\') connected++;' +
+            '});' +
+            'return connected === 2;' +
+            '})()');
+    }, 15 * 1000)
+    .then(function () {
+        //return userA.sleep(2000);
+    })
+    .then(function () {
+        t.pass('Mesh connected');
+        userA.quit();
+        userB.quit();
+        userC.quit().then(function () {
+            t.end();
+        });
+    })
+    .then(null, function (err) {
+        t.fail('Mesh failed');
+        userA.quit();
+        userB.quit();
+        userC.quit().then(function () {
+            t.end();
+        });
+    });
+}
+
+test('Mesh, Chrome-Chrome-Chrome', function (t) {
+    test3('chrome', 'chrome', 'chrome', t);
+});
+
+test('Mesh, Chrome-Firefox-Firefox', function (t) {
+    test3('chrome', 'firefox', 'firefox', t);
+});
+
+test('Mesh, Firefox-Firefox-Chrome', function (t) {
+    test3('firefox', 'firefox', 'chrome', t);
+});
+
+test('Mesh, Chrome-Chrome-Firefox', function (t) {
+    test3('chrome', 'chrome', 'chrome', t);
+});
+
+test('Mesh, Firefox-Firefox-Firefox', function (t) {
+    test3('firefox', 'firefox', 'firefox', t);
+});

+ 157 - 0
app/Resources/public/assets/simpleWebRTC/webrtc.js

@@ -0,0 +1,157 @@
+var util = require('util');
+var webrtcSupport = require('webrtcsupport');
+var mockconsole = require('mockconsole');
+var localMedia = require('localmedia');
+var Peer = require('./peer');
+
+
+function WebRTC(opts) {
+    var self = this;
+    var options = opts || {};
+    var config = this.config = {
+            debug: false,
+            // makes the entire PC config overridable
+            peerConnectionConfig: {
+                iceServers: [{'urls': 'stun:stun.l.google.com:19302'}]
+            },
+            peerConnectionConstraints: {
+                optional: []
+            },
+            receiveMedia: {
+                offerToReceiveAudio: 1,
+                offerToReceiveVideo: 1
+            },
+            enableDataChannels: true
+        };
+    var item;
+
+    // We also allow a 'logger' option. It can be any object that implements
+    // log, warn, and error methods.
+    // We log nothing by default, following "the rule of silence":
+    // http://www.linfo.org/rule_of_silence.html
+    this.logger = function () {
+        // we assume that if you're in debug mode and you didn't
+        // pass in a logger, you actually want to log as much as
+        // possible.
+        if (opts.debug) {
+            return opts.logger || console;
+        } else {
+        // or we'll use your logger which should have its own logic
+        // for output. Or we'll return the no-op.
+            return opts.logger || mockconsole;
+        }
+    }();
+
+    // set options
+    for (item in options) {
+        if (options.hasOwnProperty(item)) {
+            this.config[item] = options[item];
+        }
+    }
+
+    // check for support
+    if (!webrtcSupport.support) {
+        this.logger.error('Your browser doesn\'t seem to support WebRTC');
+    }
+
+    // where we'll store our peer connections
+    this.peers = [];
+
+    // call localMedia constructor
+    localMedia.call(this, this.config);
+
+    this.on('speaking', function () {
+        if (!self.hardMuted) {
+            // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
+            self.peers.forEach(function (peer) {
+                if (peer.enableDataChannels) {
+                    var dc = peer.getDataChannel('hark');
+                    if (dc.readyState != 'open') return;
+                    dc.send(JSON.stringify({type: 'speaking'}));
+                }
+            });
+        }
+    });
+    this.on('stoppedSpeaking', function () {
+        if (!self.hardMuted) {
+            // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
+            self.peers.forEach(function (peer) {
+                if (peer.enableDataChannels) {
+                    var dc = peer.getDataChannel('hark');
+                    if (dc.readyState != 'open') return;
+                    dc.send(JSON.stringify({type: 'stoppedSpeaking'}));
+                }
+            });
+        }
+    });
+    this.on('volumeChange', function (volume, treshold) {
+        if (!self.hardMuted) {
+            // FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
+            self.peers.forEach(function (peer) {
+                if (peer.enableDataChannels) {
+                    var dc = peer.getDataChannel('hark');
+                    if (dc.readyState != 'open') return;
+                    dc.send(JSON.stringify({type: 'volume', volume: volume }));
+                }
+            });
+        }
+    });
+
+    // log events in debug mode
+    if (this.config.debug) {
+        this.on('*', function (event, val1, val2) {
+            var logger;
+            // if you didn't pass in a logger and you explicitly turning on debug
+            // we're just going to assume you're wanting log output with console
+            if (self.config.logger === mockconsole) {
+                logger = console;
+            } else {
+                logger = self.logger;
+            }
+            logger.log('event:', event, val1, val2);
+        });
+    }
+}
+
+util.inherits(WebRTC, localMedia);
+
+WebRTC.prototype.createPeer = function (opts) {
+    var peer;
+    opts.parent = this;
+    peer = new Peer(opts);
+    this.peers.push(peer);
+    return peer;
+};
+
+// removes peers
+WebRTC.prototype.removePeers = function (id, type) {
+    this.getPeers(id, type).forEach(function (peer) {
+        peer.end();
+    });
+};
+
+// fetches all Peer objects by session id and/or type
+WebRTC.prototype.getPeers = function (sessionId, type) {
+    return this.peers.filter(function (peer) {
+        return (!sessionId || peer.id === sessionId) && (!type || peer.type === type);
+    });
+};
+
+// sends message to all
+WebRTC.prototype.sendToAll = function (message, payload) {
+    this.peers.forEach(function (peer) {
+        peer.send(message, payload);
+    });
+};
+
+// sends message to all using a datachannel
+// only sends to anyone who has an open datachannel
+WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) {
+    this.peers.forEach(function (peer) {
+        if (peer.enableDataChannels) {
+            peer.sendDirectly(channel, message, payload);
+        }
+    });
+};
+
+module.exports = WebRTC;

+ 12 - 4
app/Resources/public/css/base.css

@@ -4803,12 +4803,18 @@ div#chat-remote-video video {
 .grid-courses .items.my-courses .block-title{
     margin: 10px;
 }
-.grid-courses .items.items-courses{
-    min-height: 425px;
+
+.grid-courses .items.items-courses {
+    padding-bottom: 10px;
     width: 310px;
     box-shadow: 2px 4px 15px 0 rgba(46,61,73,.1);
     transition: box-shadow .3s ease , border .3s ease;
 }
+
+.grid-courses .items.items-sessions {
+    min-height: 355px;
+}
+
 .grid-courses .items.items-courses:hover,
 .grid-courses .items.my-courses:hover,
 .grid-courses .items.items-hotcourse:hover{
@@ -4882,7 +4888,8 @@ div#chat-remote-video video {
 	margin: 0;
 	padding: 0;
 }
-.grid-courses .items .image{
+.grid-courses .items .image {
+    height: 174px;
     position: relative;
 }
 .grid-courses .items .image .pin{
@@ -4966,7 +4973,8 @@ div#chat-remote-video video {
     color: #fff;
 }
 /*---- */
-.grid-courses .items .block-author{
+.grid-courses .items .block-author {
+    min-height: 35px;
     display: flex;
     margin-top: 5px;
     margin-left: 5px;

+ 3 - 0
app/Resources/public/css/editor.css

@@ -172,6 +172,9 @@ blockquote p {
   border: 1px solid transparent;
   border-radius: 4px;
 }
+.alert-dismissible {
+    padding-right: 35px;
+}
 .alert h4 {
   margin-top: 0;
   color: inherit;

+ 1 - 1
bower.json

@@ -21,7 +21,7 @@
     "jqueryui-timepicker-addon": "1.5.*",
     "image-map-resizer": "0.5.*",
     "fullcalendar": "3.1.0",
-    "simpleWebRTC": "1.15.*",
+    "simpleWebRTC": "2.2.*",
     "select2": "4.*",
     "MathJax": "2.5.*",
     "webcamjs": "1.0.*",

+ 123 - 153
main/admin/ldap_users_list.php

@@ -1,11 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 /**
- *	@author Mustapha Alouani
- *	@package chamilo.admin
- */
-/**
- * Init
+ * @author Mustapha Alouani
+ * @package chamilo.admin
  */
 $cidReset = true;
 require_once __DIR__.'/../inc/global.inc.php';
@@ -20,13 +17,13 @@ $login_as_user_id = @$_GET["user_id"] ?: null;
 // Login as ...
 if ($action == "login_as" && !empty ($login_as_user_id))
 {
-	login_user($login_as_user_id);
+    login_user($login_as_user_id);
 }
 
 //if we already have a session id and a user...
 /*
 if (($_GET['action']=="add_user") && ($_GET['id_session'] == strval(intval($_GET['id_session']))) && $_GET['id_session']>0 ){
-	header('Location: ldap_import_students_to_session.php?id_session='.$_GET['id_session'].'&ldap_user='.$_GET['id']);
+    header('Location: ldap_import_students_to_session.php?id_session='.$_GET['id_session'].'&ldap_user='.$_GET['id']);
 }
 */
 
@@ -37,151 +34,126 @@ $tool_name = get_lang('SearchLDAPUsers');
 
 if (isset ($_GET['action']))
 {
-	$check = Security::check_token('get');
-	if ($check)
-	{
-		switch ($_GET['action'])
-		{
-			case 'show_message' :
-				Display::addFlash(Display::return_message($_GET['message'], 'normal'));
+    $check = Security::check_token('get');
+    if ($check) {
+        switch ($_GET['action']) {
+            case 'show_message':
+                Display::addFlash(Display::return_message($_GET['message'], 'normal'));
                 Display::display_header($tool_name);
-				break;
-			case 'delete_user' :
-				if ($user_id != $_user['user_id'] && UserManager::delete_user($_GET['user_id']))
-				{
-					Display::addFlash(Display::return_message(get_lang('UserDeleted'), 'normal'));
-				}
-				else
-				{
-					Display::addFlash(Display::return_message(get_lang('CannotDeleteUser'), 'error'));
-				}
+                break;
+            case 'delete_user':
+                if ($user_id != $_user['user_id'] && UserManager::delete_user($_GET['user_id'])) {
+                    Display::addFlash(Display::return_message(get_lang('UserDeleted'), 'normal'));
+                } else {
+                    Display::addFlash(Display::return_message(get_lang('CannotDeleteUser'), 'error'));
+                }
                 Display::display_header($tool_name);
-				break;
-			case 'lock' :
-				$message = lock_unlock_user('lock', $_GET['user_id']);
-				Display::addFlash(Display::return_message($message, 'normal'));
+                break;
+            case 'lock':
+                $message = lock_unlock_user('lock', $_GET['user_id']);
+                Display::addFlash(Display::return_message($message, 'normal'));
                 Display::display_header($tool_name);
-				break;
-			case 'unlock':
-				$message = lock_unlock_user('unlock', $_GET['user_id']);
-				Display::addFlash(Display::return_message($message, 'normal'));
+                break;
+            case 'unlock':
+                $message = lock_unlock_user('unlock', $_GET['user_id']);
+                Display::addFlash(Display::return_message($message, 'normal'));
                 Display::display_header($tool_name);
-				break;
+                break;
             case 'add_user':
-				$id = $_GET['id'];
-				$UserList = array();
-				$userid_match_login = array();
-				foreach ($id as $user_id) {
-					$tmp = ldap_add_user($user_id);
-					$UserList[] = $tmp;
-					$userid_match_login[$tmp] = $user_id;
-				}
-				if (isset($_GET['id_session']) && ($_GET['id_session'] == strval(intval($_GET['id_session']))) && ($_GET['id_session'] > 0)) {
-					ldap_add_user_to_session($UserList, $_GET['id_session']);
-					header('Location: resume_session.php?id_session='.intval($_GET['id_session']));
-				} else {
-					Display::display_header($tool_name);
-					if (count($userid_match_login) > 0)
-					{
-						$message = get_lang('LDAPUsersAddedOrUpdated').':<br />';
-						foreach ($userid_match_login as $user_id => $login)
-						{
-							$message .= '- '.$login.'<br />';
-						}
-					}
-					else
-					{
-						$message = get_lang('NoUserAdded');
-					}
-					Display::addFlash(Display::return_message($message, 'normal', false));
+                $id = $_GET['id'];
+                $UserList = array();
+                $userid_match_login = array();
+                foreach ($id as $user_id) {
+                    $tmp = ldap_add_user($user_id);
+                    $UserList[] = $tmp;
+                    $userid_match_login[$tmp] = $user_id;
+                }
+                if (isset($_GET['id_session']) && ($_GET['id_session'] == strval(intval($_GET['id_session']))) && ($_GET['id_session'] > 0)) {
+                    ldap_add_user_to_session($UserList, $_GET['id_session']);
+                    header('Location: resume_session.php?id_session='.intval($_GET['id_session']));
+                } else {
                     Display::display_header($tool_name);
-				}
-				break;
-			default:
-				Display::display_header($tool_name);
-		}
-		Security::clear_token();
-	}
-	else
-	{
-		Display::display_header($tool_name);
-	}
-}
-else
-{
-	Display::display_header($tool_name);
+                    if (count($userid_match_login) > 0) {
+                        $message = get_lang('LDAPUsersAddedOrUpdated').':<br />';
+                        foreach ($userid_match_login as $user_id => $login) {
+                            $message .= '- '.$login.'<br />';
+                        }
+                    } else {
+                        $message = get_lang('NoUserAdded');
+                    }
+                    Display::addFlash(Display::return_message($message, 'normal', false));
+                    Display::display_header($tool_name);
+                }
+                break;
+            default:
+                Display::display_header($tool_name);
+        }
+        Security::clear_token();
+    } else {
+        Display::display_header($tool_name);
+    }
+} else {
+    Display::display_header($tool_name);
 }
-if (isset ($_POST['action']))
-{
-	$check = Security::check_token('get');
-	if ($check)
-	{
-		switch ($_POST['action'])
-		{
-			case 'delete' :
-				$number_of_selected_users = count($_POST['id']);
-				$number_of_deleted_users = 0;
-				foreach ($_POST['id'] as $index => $user_id)
-				{
-					if ($user_id != $_user['user_id'])
-					{
-						if (UserManager::delete_user($user_id))
-						{
-							$number_of_deleted_users++;
-						}
-					}
-				}
-				if ($number_of_selected_users == $number_of_deleted_users)
-				{
-					echo Display::return_message(get_lang('SelectedUsersDeleted'), 'normal');
-				}
-				else
-				{
-					echo Display::return_message(get_lang('SomeUsersNotDeleted'), 'error');
-				}
-				break;
-			case 'add_user' :
-				$number_of_selected_users = count($_POST['id']);
-				$number_of_added_users = 0;
-				$UserList = array();
-				foreach ($_POST['id'] as $index => $user_id)
-				{
-					if ($user_id != $_user['user_id'])
-					{
-						$UserList[] = ldap_add_user($user_id);
-					}
-				}
-				if (isset($_GET['id_session']) && (trim($_GET['id_session']) != ""))
-					addUserToSession($UserList, $_GET['id_session']);
-				if (count($UserList) > 0)
-				{
-					echo Display::return_message(count($UserList)." ".get_lang('LDAPUsersAdded'));
-				}
-				else
-				{
-					echo Display::return_message(get_lang('NoUserAdded'));
-				}
-				break;
 
-		}
-		Security::clear_token();
-	}
+if (isset($_POST['action'])) {
+    $check = Security::check_token('get');
+    if ($check) {
+        switch ($_POST['action']) {
+            case 'delete':
+                $number_of_selected_users = count($_POST['id']);
+                $number_of_deleted_users = 0;
+                foreach ($_POST['id'] as $index => $user_id) {
+                    if ($user_id != $_user['user_id']) {
+                        if (UserManager::delete_user($user_id)) {
+                            $number_of_deleted_users++;
+                        }
+                    }
+                }
+                if ($number_of_selected_users == $number_of_deleted_users) {
+                    echo Display::return_message(get_lang('SelectedUsersDeleted'), 'normal');
+                } else {
+                    echo Display::return_message(get_lang('SomeUsersNotDeleted'), 'error');
+                }
+                break;
+            case 'add_user':
+                $number_of_selected_users = count($_POST['id']);
+                $number_of_added_users = 0;
+                $UserList = array();
+                foreach ($_POST['id'] as $index => $user_id) {
+                    if ($user_id != $_user['user_id']) {
+                        $UserList[] = ldap_add_user($user_id);
+                    }
+                }
+                if (isset($_GET['id_session']) && (trim($_GET['id_session']) != "")) {
+                    addUserToSession($UserList, $_GET['id_session']);
+                }
+                if (count($UserList) > 0) {
+                    echo Display::return_message(
+                        count($UserList)." ".get_lang('LDAPUsersAdded')
+                    );
+                } else {
+                    echo Display::return_message(get_lang('NoUserAdded'));
+                }
+                break;
+
+        }
+        Security::clear_token();
+    }
 }
 
 $form = new FormValidator('advanced_search', 'get');
 $form->addText('keyword_username', get_lang('LoginName'), false);
-if (api_is_western_name_order())
-{
-	$form->addText('keyword_firstname', get_lang('FirstName'), false);
-	$form->addText('keyword_lastname', get_lang('LastName'), false);
+if (api_is_western_name_order()) {
+    $form->addText('keyword_firstname', get_lang('FirstName'), false);
+    $form->addText('keyword_lastname', get_lang('LastName'), false);
+} else {
+    $form->addText('keyword_lastname', get_lang('LastName'), false);
+    $form->addText('keyword_firstname', get_lang('FirstName'), false);
 }
-else
-{
-	$form->addText('keyword_lastname', get_lang('LastName'), false);
-	$form->addText('keyword_firstname', get_lang('FirstName'), false);
+if (isset($_GET['id_session'])) {
+    $form->addElement('hidden', 'id_session', $_GET['id_session']);
 }
-if (isset($_GET['id_session']))
-	$form->addElement('hidden', 'id_session', $_GET['id_session']);
 
 $type = array();
 $type["all"] = get_lang('All');
@@ -195,28 +167,31 @@ $form->addElement('submit', 'submit', get_lang('Ok'));
 //$defaults['keyword_inactive'] = 1;
 //$form->setDefaults($defaults);
 $form->display();
-
-
-
 $parameters['keyword_username'] = @$_GET['keyword_username'] ?: null;
 $parameters['keyword_firstname'] = @$_GET['keyword_firstname'] ?: null;
 $parameters['keyword_lastname'] = @$_GET['keyword_lastname'] ?: null;
 $parameters['keyword_email'] = @$_GET['keyword_email'] ?: null;
-if (isset($_GET['id_session']))
-	$parameters['id_session'] = $_GET['id_session'];
+if (isset($_GET['id_session'])) {
+    $parameters['id_session'] = $_GET['id_session'];
+}
 // Create a sortable table with user-data
 
 $parameters['sec_token'] = Security::get_token();
-$table = new SortableTable('users', 'ldap_get_number_of_users', 'ldap_get_user_data', (api_is_western_name_order() xor api_sort_by_first_name()) ? 3 : 2);
+$table = new SortableTable(
+    'users',
+    'ldap_get_number_of_users',
+    'ldap_get_user_data',
+    (api_is_western_name_order() xor api_sort_by_first_name()) ? 3 : 2
+);
 $table->set_additional_parameters($parameters);
 $table->set_header(0, '', false);
 $table->set_header(1, get_lang('LoginName'));
 if (api_is_western_name_order()) {
-	$table->set_header(2, get_lang('FirstName'));
-	$table->set_header(3, get_lang('LastName'));
+    $table->set_header(2, get_lang('FirstName'));
+    $table->set_header(3, get_lang('LastName'));
 } else {
-	$table->set_header(2, get_lang('LastName'));
-	$table->set_header(3, get_lang('FirstName'));
+    $table->set_header(2, get_lang('LastName'));
+    $table->set_header(3, get_lang('FirstName'));
 }
 $table->set_header(4, get_lang('Email'));
 $table->set_header(5, get_lang('Actions'));
@@ -226,9 +201,4 @@ $table->set_column_filter(5, 'modify_filter');
 $table->set_form_actions(array('add_user' => get_lang('AddLDAPUsers')));
 $table->display();
 
-/*
-==============================================================================
-		FOOTER
-==============================================================================
-*/
 Display::display_footer();

+ 1 - 1
main/announcements/announcements.php

@@ -485,7 +485,7 @@ switch ($action) {
 
         $defaults['email_ann'] = true;
 
-        $form->addElement('text', 'title', get_lang('EmailTitle'),  array( "onkeypress" => "return event.keyCode != 13;") ); //do not submit on enter
+        $form->addElement('text', 'title', get_lang('EmailTitle'), array("onkeypress" => "return event.keyCode != 13;")); //do not submit on enter
         $form->addRule('title', get_lang('ThisFieldIsRequired'), 'required');
         $form->addElement('hidden', 'id');
         $htmlTags = "<b>".get_lang('Tags')."</b><br /><br />";

+ 1 - 1
main/auth/catalog_layout.php

@@ -7,7 +7,7 @@
  */
 
 if (api_get_setting('course_catalog_published') !== 'true') {
-    // Acces rights: anonymous users can't do anything usefull here.
+    // Access rights: anonymous users can't do anything usefull here.
     api_block_anonymous_users();
 }
 

+ 13 - 2
main/auth/courses.php

@@ -144,7 +144,14 @@ if (isset($_POST['create_course_category']) &&
 // search courses
 if (isset($_REQUEST['search_course'])) {
     if ($ctok == $_REQUEST['sec_token']) {
-        $courses_controller->search_courses($_REQUEST['search_term'], null, null, null, $limit, true);
+        $courses_controller->search_courses(
+            $_REQUEST['search_term'],
+            null,
+            null,
+            null,
+            $limit,
+            true
+        );
     }
 }
 
@@ -163,7 +170,11 @@ if (isset($_REQUEST['subscribe_course'])) {
 if (isset($_GET['unsubscribe'])) {
     $search_term = isset($_GET['search_term']) ? $_GET['search_term'] : null;
     if ($ctok == $_GET['sec_token']) {
-        $courses_controller->unsubscribe_user_from_course($_GET['unsubscribe'], $search_term, $categoryCode);
+        $courses_controller->unsubscribe_user_from_course(
+            $_GET['unsubscribe'],
+            $search_term,
+            $categoryCode
+        );
     }
 }
 

+ 97 - 88
main/auth/courses_categories.php

@@ -16,7 +16,7 @@ if (isset($_REQUEST['action']) && Security::remove_XSS($_REQUEST['action']) !==
 $showCourses = CoursesAndSessionsCatalog::showCourses();
 $showSessions = CoursesAndSessionsCatalog::showSessions();
 $pageCurrent = isset($pageCurrent) ? $pageCurrent : isset($_GET['pageCurrent']) ? intval($_GET['pageCurrent']) : 1;
-$pageLength = isset($pageLength) ? $pageLength : isset($_GET['pageLength']) ? intval($_GET['pageLength']) : 12;
+$pageLength = isset($pageLength) ? $pageLength : isset($_GET['pageLength']) ? intval($_GET['pageLength']) : CoursesAndSessionsCatalog::PAGE_LENGTH;
 $pageTotal = intval(ceil(intval($countCoursesInCategory) / $pageLength));
 $cataloguePagination = $pageTotal > 1 ? CourseCategory::getCatalogPagination($pageCurrent, $pageLength, $pageTotal) : '';
 $search_term = isset($search_term) ? $search_term : null;
@@ -62,73 +62,71 @@ $code = isset($code) ? $code : null;
     });
 </script>
 <div class="row">
-	<div class="col-md-12">
-		<h2 class="title-courses"><?php echo get_lang('CourseManagement'); ?></h2>
-		<div class="search-courses">
-			<div class="row">
-		    <div class="col-md-6">
-		        <?php
-		        if ($showCourses) {
-		            if (!isset($_GET['hidden_links']) || intval($_GET['hidden_links']) != 1) { ?>
-		            <form method="post" action="<?php echo CourseCategory::getCourseCategoryUrl(1, $pageLength, 'ALL', 0, 'subscribe'); ?>">
-		                <input type="hidden" name="sec_token" value="<?php echo $stok; ?>">
-		                <input type="hidden" name="search_course" value="1" />
-		                <label><?php echo get_lang('Search'); ?></label>
-		                <div class="input-group">
-		                    <input class="form-control" type="text" name="search_term" value="<?php echo (empty($_POST['search_term']) ? '' : api_htmlentities(Security::remove_XSS($_POST['search_term']))); ?>" />
-		                    <div class="input-group-btn">
-		                        <button class="btn btn-default" type="submit">
-		                            <em class="fa fa-search"></em> <?php echo get_lang('Search'); ?>
-		                        </button>
-		                    </div>
-		                </div>
-		            </form>
-		        <?php } ?>
-		    </div>
-		    <div class="col-md-6">
-		        <?php
-		        $webAction = api_get_path(WEB_CODE_PATH).'auth/courses.php';
-		        $action = (!empty($_REQUEST['action']) ? Security::remove_XSS($_REQUEST['action']) : 'display_courses');
-		        $pageLength = (!empty($_REQUEST['pageLength']) ? intval($_REQUEST['pageLength']) : 10);
-		        $pageCurrent = (!empty($_REQUEST['pageCurrent']) ? intval($_REQUEST['pageCurrent']) : 1);
-		        $form = '<form action="'.$webAction.'" method="GET" >';
-		        $form .= '<input type="hidden" name="action" value="'.$action.'">';
-		        $form .= '<input type="hidden" name="pageCurrent" value="'.$pageCurrent.'">';
-		        $form .= '<input type="hidden" name="pageLength" value="'.$pageLength.'">';
-		        $form .= '<div class="form-group">';
-				$form .= '<label>'.get_lang('CourseCategories').'</label>';
-		        $form .= '<select name="category_code" onchange="submit();" class="selectpicker show-tick form-control">';
-		        $codeType = isset($_REQUEST['category_code']) ? Security::remove_XSS($_REQUEST['category_code']) : '';
-		        foreach ($browse_course_categories[0] as $category) {
-		            $categoryCode = $category['code'];
-		            $countCourse = $category['count_courses'];
-		            $form .= '<option '.($categoryCode == $codeType ? 'selected="selected" ' : '').' value="'.$category['code'].'">'.$category['name'].' ( '.$countCourse.' ) </option>';
-		            if (!empty($browse_course_categories[$categoryCode])) {
-		                foreach ($browse_course_categories[$categoryCode] as $subCategory) {
-		                    $subCategoryCode = $subCategory['code'];
-		                    $form .= '<option '.($subCategoryCode == $codeType ? 'selected="selected" ' : '').' value="'.$subCategory['code'].'"> ---'.$subCategory['name'].' ( '.$subCategory['count_courses'].' ) </option>';
-		                }
-		            }
-		        }
-		        $form .= '</select>';
-		        $form .= '</form>';
-		        echo $form;
-		    ?>
-		    </div>
-		    </div>
-		</div>
-
-	</div>
-	<?php  if ($showSessions) { ?>
-	    <div class="return-catalog">
-	        <a class="btn btn-default btn-lg btn-block" href="<?php echo CourseCategory::getCourseCategoryUrl(1, $pageLength, null, 0, 'display_sessions'); ?>">
-	            <em class="fa fa-arrow-right"></em> <?php echo get_lang('SessionList'); ?>
-	        </a>
-	    </div>
-	<?php } ?>
+    <div class="col-md-12">
+        <h2 class="title-courses"><?php echo get_lang('CourseManagement'); ?></h2>
+        <div class="search-courses">
+            <div class="row">
+            <div class="col-md-6">
+                <?php
+                if ($showCourses) {
+                    if (!isset($_GET['hidden_links']) || intval($_GET['hidden_links']) != 1) { ?>
+                    <form method="post" action="<?php echo CourseCategory::getCourseCategoryUrl(1, $pageLength, 'ALL', 0, 'subscribe'); ?>">
+                        <input type="hidden" name="sec_token" value="<?php echo $stok; ?>">
+                        <input type="hidden" name="search_course" value="1" />
+                        <label><?php echo get_lang('Search'); ?></label>
+                        <div class="input-group">
+                            <input class="form-control" type="text" name="search_term" value="<?php echo (empty($_POST['search_term']) ? '' : api_htmlentities(Security::remove_XSS($_POST['search_term']))); ?>" />
+                            <div class="input-group-btn">
+                                <button class="btn btn-default" type="submit">
+                                    <em class="fa fa-search"></em> <?php echo get_lang('Search'); ?>
+                                </button>
+                            </div>
+                        </div>
+                    </form>
+                <?php } ?>
+            </div>
+            <div class="col-md-6">
+                <?php
+                $webAction = api_get_path(WEB_CODE_PATH).'auth/courses.php';
+                $action = (!empty($_REQUEST['action']) ? Security::remove_XSS($_REQUEST['action']) : 'display_courses');
+                $pageLength = (!empty($_REQUEST['pageLength']) ? intval($_REQUEST['pageLength']) : CoursesAndSessionsCatalog::PAGE_LENGTH);
+                $pageCurrent = (!empty($_REQUEST['pageCurrent']) ? intval($_REQUEST['pageCurrent']) : 1);
+                $form = '<form action="'.$webAction.'" method="GET" >';
+                $form .= '<input type="hidden" name="action" value="'.$action.'">';
+                $form .= '<input type="hidden" name="pageCurrent" value="'.$pageCurrent.'">';
+                $form .= '<input type="hidden" name="pageLength" value="'.$pageLength.'">';
+                $form .= '<div class="form-group">';
+                $form .= '<label>'.get_lang('CourseCategories').'</label>';
+                $form .= '<select name="category_code" onchange="submit();" class="selectpicker show-tick form-control">';
+                $codeType = isset($_REQUEST['category_code']) ? Security::remove_XSS($_REQUEST['category_code']) : '';
+                foreach ($browse_course_categories[0] as $category) {
+                    $categoryCode = $category['code'];
+                    $countCourse = $category['count_courses'];
+                    $form .= '<option '.($categoryCode == $codeType ? 'selected="selected" ' : '').' value="'.$category['code'].'">'.$category['name'].' ( '.$countCourse.' ) </option>';
+                    if (!empty($browse_course_categories[$categoryCode])) {
+                        foreach ($browse_course_categories[$categoryCode] as $subCategory) {
+                            $subCategoryCode = $subCategory['code'];
+                            $form .= '<option '.($subCategoryCode == $codeType ? 'selected="selected" ' : '').' value="'.$subCategory['code'].'"> ---'.$subCategory['name'].' ( '.$subCategory['count_courses'].' ) </option>';
+                        }
+                    }
+                }
+                $form .= '</select>';
+                $form .= '</form>';
+                echo $form;
+            ?>
+            </div>
+            </div>
+        </div>
+    </div>
+    <?php  if ($showSessions) { ?>
+        <div class="return-catalog">
+            <a class="btn btn-default btn-lg btn-block" href="<?php echo CourseCategory::getCourseCategoryUrl(1, $pageLength, null, 0, 'display_sessions'); ?>">
+                <em class="fa fa-arrow-right"></em> <?php echo get_lang('SessionList'); ?>
+            </a>
+        </div>
+    <?php } ?>
 </div>
-
- <?php  } ?>
+<?php  } ?>
 <div class="grid-courses">
 <div class="row">
 <?php
@@ -161,14 +159,14 @@ if ($showCourses && $action != 'display_sessions') {
                 continue;
             }
 
-            $userRegisterdInCourse = CourseManager::is_user_subscribed_in_course($user_id, $course['code']);
-            $userRegisterdInCourseAsTeacher = CourseManager::is_course_teacher($user_id, $course['code']);
-            $userRegisterd = ($userRegisterdInCourse && $userRegisterdInCourseAsTeacher);
+            $userRegisteredInCourse = CourseManager::is_user_subscribed_in_course($user_id, $course['code']);
+            $userRegisteredInCourseAsTeacher = CourseManager::is_course_teacher($user_id, $course['code']);
+            $userRegistered = $userRegisteredInCourse && $userRegisteredInCourseAsTeacher;
 
-            $course_public = ($course['visibility'] == COURSE_VISIBILITY_OPEN_WORLD);
-            $course_open = ($course['visibility'] == COURSE_VISIBILITY_OPEN_PLATFORM);
-            $course_private = ($course['visibility'] == COURSE_VISIBILITY_REGISTERED);
-            $course_closed = ($course['visibility'] == COURSE_VISIBILITY_CLOSED);
+            $course_public = $course['visibility'] == COURSE_VISIBILITY_OPEN_WORLD;
+            $course_open = $course['visibility'] == COURSE_VISIBILITY_OPEN_PLATFORM;
+            $course_private = $course['visibility'] == COURSE_VISIBILITY_REGISTERED;
+            $course_closed = $course['visibility'] == COURSE_VISIBILITY_CLOSED;
 
             $course_subscribe_allowed = ($course['subscribe'] == 1);
             $course_unsubscribe_allowed = ($course['unsubscribe'] == 1);
@@ -185,7 +183,7 @@ if ($showCourses && $action != 'display_sessions') {
             }
 
             // display thumbnail
-            $html .= returnThumbnail($course, $userRegisterd);
+            $html .= returnThumbnail($course, $userRegistered);
 
             $separator = '<div class="separator">&nbsp;</div>';
             $subscribeButton = return_register_button($course, $stok, $code, $search_term);
@@ -213,7 +211,7 @@ if ($showCourses && $action != 'display_sessions') {
 
             // display course title and button bloc
             $html .= '<div class="description">';
-            $html .= return_title($course, $userRegisterdInCourse);
+            $html .= return_title($course, $userRegisteredInCourse);
             $html .= return_teacher($course);
 
             // display button line
@@ -224,20 +222,18 @@ if ($showCourses && $action != 'display_sessions') {
             $html .= '<div class="right">';
             $html .= '<div class="btn-group">';
             // if user registered as student
-            if ($userRegisterdInCourse) {
+            if ($userRegisteredInCourse) {
                 $html .= return_already_registered_label('student');
                 if (!$course_closed) {
                     if ($course_unsubscribe_allowed) {
                         $html .= return_unregister_button($course, $stok, $search_term, $code);
                     }
                 }
-            } elseif ($userRegisterdInCourseAsTeacher) {
+            } elseif ($userRegisteredInCourseAsTeacher) {
                 // if user registered as teacher
                 if ($course_unsubscribe_allowed) {
                     $html .= return_unregister_button($course, $stok, $search_term, $code);
                 }
-                //$html .= return_already_registered_label('teacher');
-
             } else {
                 // if user not registered in the course
                 if (!$course_closed) {
@@ -247,7 +243,6 @@ if ($showCourses && $action != 'display_sessions') {
                         }
                     }
                 }
-
             }
             $html .= '</div>';
             $html .= '</div>';
@@ -292,11 +287,17 @@ function returnThumbnail($course, $registeredUser)
         $course_medium_image = api_get_path(WEB_COURSE_PATH).$course['directory'].'/course-pic.png'; // redimensioned image 85x85
     } else {
         // without picture
-        $course_medium_image = Display::return_icon('session_default.png', null, null, null, null, true);
+        $course_medium_image = Display::return_icon(
+            'session_default.png',
+            null,
+            null,
+            null,
+            null,
+            true
+        );
     }
 
     $html .= '<div class="image">';
-
     if (!$registeredUser) {
         $html .= '<img class="img-responsive"'
                 .' src="'.$course_medium_image.'" '
@@ -322,16 +323,13 @@ function returnThumbnail($course, $registeredUser)
 
 function return_teacher($course)
 {
-    //Info course
     $courseInfo = api_get_course_info($course['code']);
     $teachers = CourseManager::getTeachersFromCourse($courseInfo['real_id']);
-    //$count = 0;
     $html = null;
     $html .= '<div class="block-author">';
     $length = count($teachers);
     foreach ($teachers as $value) {
         $name = $value['firstname'].' '.$value['lastname'];
-
         if ($length > 2) {
              $html .= '<a href="'.$value['url'].'" class="ajax" data-title="'.$name.'">
                     <img src="'.$value['avatar'].'" alt="'.get_lang('UserPicture').'"/></a>';
@@ -344,6 +342,7 @@ function return_teacher($course)
         }
     }
     $html .= '</div>';
+
     return $html;
 }
 
@@ -416,7 +415,11 @@ function return_goto_button($course)
     $html = Display::url(
         Display::returnFontAwesomeIcon('share'),
         api_get_course_url($course['code']),
-        array('class' => 'btn btn-default btn-sm', 'title' => $title, 'aria-label' => $title)
+        array(
+            'class' => 'btn btn-default btn-sm',
+            'title' => $title,
+            'aria-label' => $title,
+        )
     );
     return $html;
 }
@@ -439,7 +442,12 @@ function return_already_registered_label($in_status)
     $html = Display::tag(
         'span',
         $icon.' '.$title,
-        array('id' => 'register', 'class' => 'label-subscribed text-success', 'title' => $title, 'aria-label' => $title)
+        array(
+            'id' => 'register',
+            'class' => 'label-subscribed text-success',
+            'title' => $title,
+            'aria-label' => $title,
+        )
     );
 
     return $html;
@@ -484,5 +492,6 @@ function return_unregister_button($course, $stok, $search_term, $code)
         .'&unsubscribe='.$course['code'].'&search_term='.$search_term.'&category_code='.$code,
         array('class' => 'btn btn-danger btn-sm', 'title' => $title, 'aria-label' => $title)
     );
+
     return $html;
 }

+ 19 - 7
main/auth/courses_controller.php

@@ -614,7 +614,10 @@ class CoursesController
 
         return Display::div(
             $icon,
-            array('class' => 'btn btn-default btn-sm registered', 'title' => get_lang("AlreadyRegisteredToSession"))
+            array(
+                'class' => 'btn btn-default btn-sm registered',
+                'title' => get_lang("AlreadyRegisteredToSession"),
+            )
         );
     }
 
@@ -689,13 +692,18 @@ class CoursesController
         $searchTag = isset($_POST['search_tag']) ? $_POST['search_tag'] : null;
         $searchDate = isset($_POST['date']) ? $_POST['date'] : date('Y-m-d');
         $hiddenLinks = isset($_GET['hidden_links']) ? intval($_GET['hidden_links']) == 1 : false;
-        $courseUrl = CourseCategory::getCourseCategoryUrl(1, $limit['length'], null, 0, 'subscribe');
+        $courseUrl = CourseCategory::getCourseCategoryUrl(
+            1,
+            $limit['length'],
+            null,
+            0,
+            'subscribe'
+        );
 
         $sessions = $this->model->browseSessionsByTags($searchTag, $limit);
         $sessionsBlocks = $this->getFormattedSessionsBlock($sessions);
 
         $tpl = new Template();
-
         $tpl->assign('show_courses', CoursesAndSessionsCatalog::showCourses());
         $tpl->assign('show_sessions', CoursesAndSessionsCatalog::showSessions());
         $tpl->assign('show_tutor', (api_get_setting('show_session_coach') === 'true' ? true : false));
@@ -720,7 +728,13 @@ class CoursesController
     {
         $q = isset($_REQUEST['q']) ? Security::remove_XSS($_REQUEST['q']) : null;
         $hiddenLinks = isset($_GET['hidden_links']) ? intval($_GET['hidden_links']) == 1 : false;
-        $courseUrl = CourseCategory::getCourseCategoryUrl(1, $limit['length'], null, 0, 'subscribe');
+        $courseUrl = CourseCategory::getCourseCategoryUrl(
+            1,
+            $limit['length'],
+            null,
+            0,
+            'subscribe'
+        );
         $searchDate = isset($_POST['date']) ? $_POST['date'] : date('Y-m-d');
 
         $sessions = $this->model->browseSessionsBySearch($q, $limit);
@@ -807,12 +821,10 @@ class CoursesController
             );
 
             $hasRequirements = false;
-
             foreach ($sequences['sequences'] as $sequence) {
                 if (count($sequence['requirements']) === 0) {
                     continue;
                 }
-
                 $hasRequirements = true;
                 break;
             }
@@ -846,7 +858,7 @@ class CoursesController
                 'is_subscribed' => SessionManager::isUserSubscribedAsStudent($session->getId(), $userId),
                 'icon' => $this->getSessionIcon($session->getName()),
                 'date' => $sessionDates['display'],
-                'price' => (!empty($isThisSessionOnSale['html']) ? $isThisSessionOnSale['html'] : ''),
+                'price' => !empty($isThisSessionOnSale['html']) ? $isThisSessionOnSale['html'] : '',
                 'subscribe_button' => isset($isThisSessionOnSale['buy_button']) ? $isThisSessionOnSale['buy_button'] : $this->getRegisteredInSessionButton(
                     $session->getId(),
                     $session->getName(),

+ 41 - 35
main/auth/courses_list.php

@@ -172,7 +172,6 @@ if (!empty($user_course_categories)) {
 }
 
 echo Display::page_subheader(get_lang('NoCourseCategory'));
-
 echo '<table class="data_table">';
 // COURSES WITHOUT CATEGORY
 if (!empty($courses_without_category)) {
@@ -185,9 +184,18 @@ if (!empty($courses_without_category)) {
             <a name="course<?php echo $course['code']; ?>"></a>
             <strong><?php echo $course['title']; ?></strong><br />
             <?php
-            if (api_get_setting('display_coursecode_in_courselist') === 'true') { echo $course['visual_code']; }
-            if (api_get_setting('display_coursecode_in_courselist') === 'true' && api_get_setting('display_teacher_in_courselist') === 'true') { echo " - "; }
-            if (api_get_setting('display_teacher_in_courselist') === 'true') { echo $course['tutor']; }
+            if (api_get_setting('display_coursecode_in_courselist') === 'true') {
+                echo $course['visual_code'];
+            }
+
+            if (api_get_setting('display_coursecode_in_courselist') === 'true' &&
+                api_get_setting('display_teacher_in_courselist') === 'true'
+            ) {
+                echo " - ";
+            }
+            if (api_get_setting('display_teacher_in_courselist') === 'true') {
+                echo $course['tutor'];
+            }
             ?>
         </td>
         <td valign="top">
@@ -209,56 +217,54 @@ if (!empty($courses_without_category)) {
             </form><br />
             </div>
             <?php } ?>
-
             <div style="float:left; width:110px">
             <?php
             if (api_get_setting('show_courses_descriptions_in_catalog') == 'true') {
-            $icon_title = get_lang('CourseDetails').' - '.$course['title'];
+                $icon_title = get_lang('CourseDetails').' - '.$course['title'];
             ?>
             <a href="<?php echo api_get_path(WEB_CODE_PATH); ?>inc/ajax/course_home.ajax.php?a=show_course_information&code=<?php echo $course['code'] ?>" data-title="<?php echo $icon_title ?>" title="<?php echo $icon_title ?>" class="ajax">
                 <?php echo Display::return_icon('info.png', $icon_title, '', '22'); ?>
             </a>
-            <?php } ?>
-
-             <?php if (isset($_GET['edit']) && $course['code'] == $_GET['edit']) {   ?>
-                    <?php echo Display::display_icon('edit_na.png', get_lang('Edit'), '', 22); ?>
-                  <?php } else { ?>
-                    <a href="courses.php?action=<?php echo $action; ?>&amp;edit=<?php echo $course['code']; ?>&amp;sec_token=<?php echo $stok; ?>">
-                    <?php echo Display::display_icon('edit.png', get_lang('Edit'), '', 22); ?>
-                    </a>
+            <?php }
+            if (isset($_GET['edit']) && $course['code'] == $_GET['edit']) {   ?>
+                <?php echo Display::display_icon('edit_na.png', get_lang('Edit'), '', 22); ?>
+              <?php } else { ?>
+                <a href="courses.php?action=<?php echo $action; ?>&amp;edit=<?php echo $course['code']; ?>&amp;sec_token=<?php echo $stok; ?>">
+                <?php echo Display::display_icon('edit.png', get_lang('Edit'), '', 22); ?>
+                </a>
              <?php } ?>
 
             <!-- up /down icons-->
             <?php if ($key > 0) { ?>
-                    <a href="courses.php?action=<?php echo $action; ?>&amp;move=up&amp;course=<?php echo $course['code']; ?>&amp;category=<?php echo $course['user_course_cat']; ?>&amp;sec_token=<?php echo $stok; ?>">
-                    <?php echo Display::display_icon('up.png', get_lang('Up'), '', 22) ?>
-                    </a>
+                <a href="courses.php?action=<?php echo $action; ?>&amp;move=up&amp;course=<?php echo $course['code']; ?>&amp;category=<?php echo $course['user_course_cat']; ?>&amp;sec_token=<?php echo $stok; ?>">
+                <?php echo Display::display_icon('up.png', get_lang('Up'), '', 22) ?>
+                </a>
             <?php } else {
-                    echo Display::display_icon('up_na.png', get_lang('Up'), '', 22);
-                    }
-
-                    if ($key < $number_of_courses - 1) { ?>
-                    <a href="courses.php?action=<?php echo $action; ?>&amp;move=down&amp;course=<?php echo $course['code']; ?>&amp;category=<?php echo $course['user_course_cat']; ?>&amp;sec_token=<?php echo $stok; ?>">
-                    <?php echo Display::display_icon('down.png', get_lang('Down'), '', 22); ?>
-                    </a>
+                echo Display::display_icon('up_na.png', get_lang('Up'), '', 22);
+            }
+            if ($key < $number_of_courses - 1) { ?>
+                <a href="courses.php?action=<?php echo $action; ?>&amp;move=down&amp;course=<?php echo $course['code']; ?>&amp;category=<?php echo $course['user_course_cat']; ?>&amp;sec_token=<?php echo $stok; ?>">
+                <?php echo Display::display_icon('down.png', get_lang('Down'), '', 22); ?>
+                </a>
             <?php } else {
                     echo Display::display_icon('down_na.png', get_lang('Down'), '', 22);
                     }?>
                 </div>
                  <div style="float:left; margin-right:10px;">
                   <!-- cancel subscrioption-->
-            <?php if ($course['status'] != 1) {
+            <?php
+                if ($course['status'] != 1) {
                     if ($course['unsubscr'] == 1) {
-            ?>
-                    <!-- changed link to submit to avoid action by the search tool indexer -->
-                    <form action="<?php echo api_get_self(); ?>" method="post" onsubmit="javascript: if (!confirm('<?php echo addslashes(api_htmlentities(get_lang("ConfirmUnsubscribeFromCourse"), ENT_QUOTES, api_get_system_encoding())) ?>')) return false;">
-                        <input type="hidden" name="sec_token" value="<?php echo $stok; ?>">
-                        <input type="hidden" name="unsubscribe" value="<?php echo $course['code']; ?>" />
-                        <button class="btn btn-default" value="<?php echo get_lang('Unsubscribe'); ?>" name="unsub">
-                            <?php echo get_lang('Unsubscribe'); ?>
-                        </button>
-                    </form>
-                    </div>
+                ?>
+                <!-- changed link to submit to avoid action by the search tool indexer -->
+                <form action="<?php echo api_get_self(); ?>" method="post" onsubmit="javascript: if (!confirm('<?php echo addslashes(api_htmlentities(get_lang("ConfirmUnsubscribeFromCourse"), ENT_QUOTES, api_get_system_encoding())) ?>')) return false;">
+                    <input type="hidden" name="sec_token" value="<?php echo $stok; ?>">
+                    <input type="hidden" name="unsubscribe" value="<?php echo $course['code']; ?>" />
+                    <button class="btn btn-default" value="<?php echo get_lang('Unsubscribe'); ?>" name="unsub">
+                        <?php echo get_lang('Unsubscribe'); ?>
+                    </button>
+                </form>
+                </div>
               <?php }
                 }
                 ?>

+ 1 - 1
main/auth/gotocourse.php

@@ -50,7 +50,7 @@ if (isset($_GET['firstpage'])) {
     }
 
     $msg .= '<div class="well_login">';
-    $msg .= $form->return_form();
+    $msg .= $form->returnForm();
     $msg .= '</div>';
     if (api_is_cas_activated()) {
         $msg .= "</div>";

+ 38 - 7
main/auth/inscription.php

@@ -109,7 +109,15 @@ if ($user_already_registered_show_terms === false) {
         if (api_get_setting('registration', 'email') != 'true') {
             $form->addRule('email', get_lang('ThisFieldIsRequired'), 'required');
         }
-        $form->addRule('email', sprintf(get_lang('UsernameMaxXCharacters'), (string) USERNAME_MAX_LENGTH), 'maxlength', USERNAME_MAX_LENGTH);
+        $form->addRule(
+            'email',
+            sprintf(
+                get_lang('UsernameMaxXCharacters'),
+                (string) USERNAME_MAX_LENGTH
+            ),
+            'maxlength',
+            USERNAME_MAX_LENGTH
+        );
         $form->addRule('email', get_lang('UserTaken'), 'username_available');
     }
 
@@ -151,7 +159,15 @@ if ($user_already_registered_show_terms === false) {
         );
         $form->applyFilter('username', 'trim');
         $form->addRule('username', get_lang('ThisFieldIsRequired'), 'required');
-        $form->addRule('username', sprintf(get_lang('UsernameMaxXCharacters'), (string) USERNAME_MAX_LENGTH), 'maxlength', USERNAME_MAX_LENGTH);
+        $form->addRule(
+            'username',
+            sprintf(
+                get_lang('UsernameMaxXCharacters'),
+                (string) USERNAME_MAX_LENGTH
+            ),
+            'maxlength',
+            USERNAME_MAX_LENGTH
+        );
         $form->addRule('username', get_lang('UsernameWrong'), 'username');
         $form->addRule('username', get_lang('UserTaken'), 'username_available');
     }
@@ -253,10 +269,25 @@ if ($user_already_registered_show_terms === false) {
         );
         $form->addElement('static', null, null, get_lang('ClickOnTheImageForANewOne'));
 
-        $form->addElement('text', 'captcha', get_lang('EnterTheLettersYouSee'), array('size' => 40));
-        $form->addRule('captcha', get_lang('EnterTheCharactersYouReadInTheImage'), 'required', null, 'client');
-
-        $form->addRule('captcha', get_lang('TheTextYouEnteredDoesNotMatchThePicture'), 'CAPTCHA', $captcha_question);
+        $form->addElement(
+            'text',
+            'captcha',
+            get_lang('EnterTheLettersYouSee'),
+            array('size' => 40)
+        );
+        $form->addRule(
+            'captcha',
+            get_lang('EnterTheCharactersYouReadInTheImage'),
+            'required',
+            null,
+            'client'
+        );
+        $form->addRule(
+            'captcha',
+            get_lang('TheTextYouEnteredDoesNotMatchThePicture'),
+            'CAPTCHA',
+            $captcha_question
+        );
     }
 
     // EXTENDED FIELDS
@@ -570,7 +601,7 @@ if ($form->validate()) {
             $form
         );
 
-        //update the extra fields
+        // Update the extra fields
         $count_extra_field = count($extras);
         if ($count_extra_field > 0 && is_integer($user_id)) {
             foreach ($extras as $key => $value) {

+ 13 - 7
main/auth/lostPassword.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  * SCRIPT PURPOSE :
  *
@@ -9,11 +10,11 @@
  *
  * Special case : If the password are encrypted in the database, we have
  * to generate a new one.
-*
-*	@todo refactor, move relevant functions to code libraries
-*
-*	@package chamilo.auth
-*/
+ *
+ * @todo refactor, move relevant functions to code libraries
+ *
+ * @package chamilo.auth
+ */
 
 require_once __DIR__.'/../inc/global.inc.php';
 
@@ -53,8 +54,13 @@ if ($reset && $userId) {
 
 $form = new FormValidator('lost_password');
 $form->addHeader($tool_name);
-$form->addText('user', [get_lang('LoginOrEmailAddress'), get_lang('EnterEmailUserAndWellSendYouPassword')], true);
-
+$form->addText('user',
+    [
+        get_lang('LoginOrEmailAddress'),
+        get_lang('EnterEmailUserAndWellSendYouPassword'),
+    ],
+    true
+);
 
 $captcha = api_get_setting('allow_captcha');
 $allowCaptcha = $captcha === 'true';

+ 1 - 1
main/auth/profile.php

@@ -230,7 +230,7 @@ if (api_get_setting('profile', 'language') !== 'true') {
     $form->freeze('language');
 }
 
-//THEME
+// THEME
 if (is_profile_editable() && api_get_setting('user_selected_theme') == 'true') {
     $form->addElement('SelectTheme', 'theme', get_lang('Theme'));
     if (api_get_setting('profile', 'theme') !== 'true') {

+ 6 - 1
main/auth/reset.php

@@ -15,7 +15,12 @@ $form = new FormValidator('reset', 'POST', api_get_self().'?token='.$token);
 $form->addElement('header', get_lang('ResetPassword'));
 $form->addHidden('token', $token);
 $form->addElement('password', 'pass1', get_lang('Password'));
-$form->addElement('password', 'pass2', get_lang('Confirmation'), array('id' => 'pass2', 'size' => 20, 'autocomplete' => 'off'));
+$form->addElement(
+    'password',
+    'pass2',
+    get_lang('Confirmation'),
+    array('id' => 'pass2', 'size' => 20, 'autocomplete' => 'off')
+);
 $form->addRule('pass1', get_lang('ThisFieldIsRequired'), 'required');
 $form->addRule('pass2', get_lang('ThisFieldIsRequired'), 'required');
 $form->addRule(array('pass1', 'pass2'), get_lang('PassTwo'), 'compare');

+ 5 - 1
main/auth/set_temp_password.php

@@ -28,7 +28,11 @@ $course_info = api_get_course_info_by_id($course_id);
 $tpl = new Template(null);
 
 // Build the form
-$form = new FormValidator('set_temp_password', 'POST', api_get_self().'?course_id='.$course_id.'&session_id='.$session_id);
+$form = new FormValidator(
+    'set_temp_password',
+    'POST',
+    api_get_self().'?course_id='.$course_id.'&session_id='.$session_id
+);
 $form->addElement('header', get_lang('CourseRequiresPassword'));
 $form->addElement('hidden', 'course_id', $course_id);
 $form->addElement('hidden', 'session_id', $session_id);

+ 17 - 3
main/auth/unsubscribe_account.php

@@ -12,14 +12,28 @@ $tool_name = get_lang('Unsubscribe');
 $message = Display::return_message(get_lang('UnsubscribeFromPlatform'), 'warning');
 
 $form = new FormValidator('user_add');
-$form->addElement('button', 'submit', get_lang('Unsubscribe'), array('onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang("UnsubscribeFromPlatformConfirm")))."')) return false;"));
-$content = $form->return_form();
+$form->addElement(
+    'button',
+    'submit',
+    get_lang('Unsubscribe'),
+    array(
+        'onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang("UnsubscribeFromPlatformConfirm")))."')) return false;",
+    )
+);
+$content = $form->returnForm();
 
 if ($form->validate()) {
     $user_info = api_get_user_info();
     $result = UserManager::delete_user($user_info['user_id']);
     if ($result) {
-        $message = Display::return_message(sprintf(get_lang('UnsubscribeFromPlatformSuccess', $user_info['username'])));
+        $message = Display::return_message(
+            sprintf(
+                get_lang(
+                    'UnsubscribeFromPlatformSuccess',
+                    $user_info['username']
+                )
+            )
+        );
         $content = null;
         online_logout($user_info['user_id'], false);
         api_not_allowed(true, $message);

+ 16 - 4
main/coursecopy/copy_course_session.php

@@ -48,8 +48,12 @@ $tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
 $tbl_session_rel_course = Database::get_main_table(TABLE_MAIN_SESSION_COURSE);
 $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
 
-/* FUNCTIONS */
-
+/**
+ * @param string $name
+ * @param array $sessions
+ * @param array $attr
+ * @return string
+ */
 function make_select_session_list($name, $sessions, $attr = array())
 {
     $attributes = '';
@@ -83,6 +87,9 @@ function make_select_session_list($name, $sessions, $attr = array())
     return $output;
 }
 
+/**
+ * @return string
+ */
 function display_form()
 {
     $html = '';
@@ -138,6 +145,11 @@ function display_form()
     echo $html;
 }
 
+/**
+ * @param int $id_session
+ * @param string $type
+ * @return xajaxResponse
+ */
 function search_courses($id_session, $type)
 {
     global $tbl_course, $tbl_session_rel_course, $course_list;
@@ -221,12 +233,12 @@ function search_courses($id_session, $type)
             );
         }
     }
+
     return $xajax_response;
 }
 $xajax->processRequests();
 
 /* HTML head extra */
-
 $htmlHeadXtra[] = $xajax->getJavascript(api_get_path(WEB_LIBRARY_PATH).'xajax/');
 $htmlHeadXtra[] = '<script>
 function checkSelected(id_select,id_radio,id_title,id_destination) {
@@ -278,7 +290,7 @@ if (Security::check_token('post') && (
     // Clear token
     Security::clear_token();
     $destination_course = $origin_course = $destination_session = $origin_session = '';
-    if (isset ($_POST['action']) && $_POST['action'] == 'course_select_form') {
+    if (isset($_POST['action']) && $_POST['action'] == 'course_select_form') {
         $destination_course = $_POST['destination_course'];
         $origin_course = $_POST['origin_course'];
         $destination_session = $_POST['destination_session'];

+ 0 - 2
main/cron/import_csv.php

@@ -2141,11 +2141,9 @@ class ImportCsv
                 $criteria = [
                     'externalCareId' => $row['External_care_id']
                 ];
-                var_dump($criteria);
                 $post = $em->getRepository('ChamiloPluginBundle:StudentFollowUp\CarePost')->findOneBy($criteria);
 
                 if (empty($post)) {
-                    var_dump('empty');
                     $post = new CarePost();
                 }
 

+ 14 - 6
main/inc/ajax/user_manager.ajax.php

@@ -90,13 +90,22 @@ switch ($action) {
         }
         break;
     case 'search_tags':
+        header('Content-Type: application/json');
+
+        $result = ['items' => []];
+
         if (api_is_anonymous()) {
-            echo '';
-        } else {
-            if (isset($_GET['tag']) && isset($_GET['field_id'])) {
-                echo UserManager::get_tags($_GET['tag'], $_GET['field_id'], 'json', '10');
-            }
+            echo json_encode($result);
+            break;
         }
+
+        if (!isset($_GET['q'], $_GET['field_id'])) {
+            echo json_encode($result);
+            break;
+        }
+
+        $result['items'] = UserManager::get_tags($_GET['q'], $_GET['field_id'], null, '10');
+        echo json_encode($result);
         break;
     case 'generate_api_key':
         if (api_is_anonymous()) {
@@ -174,4 +183,3 @@ switch ($action) {
         echo '';
 }
 exit;
-

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

@@ -7,6 +7,8 @@
  */
 class CoursesAndSessionsCatalog
 {
+    const PAGE_LENGTH = 12;
+
     /**
      * Check the configuration for the courses and sessions catalog
      * @global array $_configuration Configuration

+ 20 - 8
main/inc/lib/attendance.lib.php

@@ -91,12 +91,13 @@ class Attendance
     }
 
     /**
-     * Get the attendaces to display on the current page (fill the sortable-table)
+     * Get the attendances to display on the current page (fill the sortable-table)
      * @param   int     offset of first user to recover
      * @param   int     Number of users to get
      * @param   int     Column to sort on
      * @param   string  Order (ASC,DESC)
      * @see SortableTable#get_table_data($from)
+     * @return array
      */
     public static function get_attendance_data($from, $number_of_items, $column, $direction)
     {
@@ -306,7 +307,6 @@ class Attendance
         $last_id = Database::insert($tbl_attendance, $params);
 
         if (!empty($last_id)) {
-
             $sql = "UPDATE $tbl_attendance SET id = iid WHERE iid = $last_id";
             Database::query($sql);
 
@@ -699,7 +699,7 @@ class Attendance
             if (!empty($attendance_id)) {
                 $user_faults = $this->get_faults_of_user($uid, $attendance_id, $groupId);
                 $value['attendance_result'] = $user_faults['faults'].'/'.$user_faults['total'].' ('.$user_faults['faults_porcent'].'%)';
-                $value['result_color_bar'] 	= $user_faults['color_bar'];
+                $value['result_color_bar'] = $user_faults['color_bar'];
             }
 
             $photo = Display::img($userInfo['avatar_small'], $userInfo['complete_name'], [], false);
@@ -1002,10 +1002,24 @@ class Attendance
         $user_id = intval($user_id);
         $attendance_id = intval($attendance_id);
         $results = array();
-        $calendar_count = self::get_number_of_attendance_calendar($attendance_id, $groupId, NULL, $user_id);
+        $calendar_count = self::get_number_of_attendance_calendar(
+            $attendance_id,
+            $groupId,
+            null,
+            $user_id
+        );
         // $total_done_attendance 	= $attendance_data['attendance_qualify_max'];
-        $total_done_attendance 	= self::get_number_of_attendance_calendar($attendance_id, $groupId, true, $user_id);
-        $attendance_user_score  = $this->get_user_score($user_id, $attendance_id, $groupId);
+        $total_done_attendance = self::get_number_of_attendance_calendar(
+            $attendance_id,
+            $groupId,
+            true,
+            $user_id
+        );
+        $attendance_user_score = $this->get_user_score(
+            $user_id,
+            $attendance_id,
+            $groupId
+        );
 
         //This is the main change of the BT#1381
         //$total_done_attendance = $calendar_count;
@@ -1053,7 +1067,6 @@ class Attendance
             //$course_info = api_get_course_info($course_code);
             $course_id = $course['real_id'];
             $tbl_attendance_result = Database::get_course_table(TABLE_ATTENDANCE_RESULT);
-
             $attendances_by_course = $this->get_attendances_list($course_id);
 
             foreach ($attendances_by_course as $attendance) {
@@ -1513,7 +1526,6 @@ class Attendance
         return $count;
     }
 
-
     /**
      * Get count dates inside attendance calendar by attendance id
      * @param	int	$attendance_id

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

@@ -344,8 +344,8 @@ function return_navigation_array()
             $params = array('variable = ? AND subkey = ?' => ['status', 'studentfollowup']);
             $result = api_get_settings_params_simple($params);
             if (!empty($result) && $result['selected_value'] === 'installed') {
-                $navigation['follow_up']['url'] = api_get_path(WEB_PLUGIN_PATH).'studentfollowup/post.php';
-                $navigation['follow_up']['title'] = get_lang('CareSystem');
+                $navigation['follow_up']['url'] = api_get_path(WEB_PLUGIN_PATH).'studentfollowup/posts.php';
+                $navigation['follow_up']['title'] = get_lang('MyDossier');
                 $navigation['follow_up']['key'] = 'homepage';
                 $navigation['follow_up']['icon'] = 'homepage.png';
             }

Різницю між файлами не показано, бо вона завелика
+ 181 - 181
main/inc/lib/course.lib.php


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

@@ -938,7 +938,7 @@ class CourseCategory
     public static function getLimitArray()
     {
         $pageCurrent = isset($_REQUEST['pageCurrent']) ? intval($_GET['pageCurrent']) : 1;
-        $pageLength = isset($_REQUEST['pageLength']) ? intval($_GET['pageLength']) : 12;
+        $pageLength = isset($_REQUEST['pageLength']) ? intval($_GET['pageLength']) : CoursesAndSessionsCatalog::PAGE_LENGTH;
 
         return array(
             'start' => ($pageCurrent - 1) * $pageLength,

+ 22 - 2
main/inc/lib/course_home.lib.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CourseBundle\Entity\CTool;
+
 /**
  * Class CourseHome
  */
@@ -537,6 +539,8 @@ class CourseHome
             }
         }
 
+        $allowEditionInSession = api_get_configuration_value('allow_edit_tool_visibility_in_session');
+
         while ($temp_row = Database::fetch_assoc($result)) {
             $add = false;
             if ($check) {
@@ -547,6 +551,22 @@ class CourseHome
                 $add = true;
             }
 
+            if ($allowEditionInSession && !empty($session_id)) {
+                // Checking if exist row in session
+                $criteria = [
+                    'cId' => $course_id,
+                    'name' => $temp_row['name'],
+                    'sessionId' => $session_id,
+                ];
+                /** @var CTool $tool */
+                $toolObj = Database::getManager()->getRepository('ChamiloCourseBundle:CTool')->findOneBy($criteria);
+                if ($toolObj) {
+                    if ($toolObj->getVisibility() == 0) {
+                        continue;
+                    }
+                }
+            }
+
             if ($temp_row['image'] == 'scormbuilder.gif') {
                 $lp_id = self::get_published_lp_id_from_link($temp_row['link']);
                 $lp = new learnpath(
@@ -555,7 +575,7 @@ class CourseHome
                     api_get_user_id()
                 );
                 $path = $lp->get_preview_image_path(ICON_SIZE_BIG);
-                $add = $lp->is_lp_visible_for_student(
+                $add = learnpath::is_lp_visible_for_student(
                     $lp_id,
                     api_get_user_id(),
                     api_get_course_id(),
@@ -785,7 +805,7 @@ class CourseHome
                             'name' => $tool['name'],
                             'sessionId' => $session_id
                         ];
-                        /** @var \Chamilo\CourseBundle\Entity\CTool $tool */
+                        /** @var CTool $tool */
                         $toolObj = Database::getManager()->getRepository('ChamiloCourseBundle:CTool')->findOneBy($criteria);
                         if ($toolObj) {
                             $visibility = $toolObj->getVisibility();

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

@@ -106,7 +106,7 @@ class ExtraFieldValue extends Model
 
         // Parse params.
         foreach ($extraFields as $fieldDetails) {
-            if ($fieldDetails['visible_to_self'] != 1) {
+            if ($fieldDetails['visible_to_self'] != 1 && !api_is_platform_admin(true, true)) {
                 continue;
             }
 

BIN
main/inc/lib/javascript/chat/img/webrtc_chrome.png


BIN
main/inc/lib/javascript/chat/img/webrtc_firefox.png


+ 4 - 1
main/inc/lib/javascript/chat/video.php

@@ -35,14 +35,17 @@ if ($isSender) {
 $idUserLocal = api_get_user_id();
 $userLocal = api_get_user_info($idUserLocal, true);
 $htmlHeadXtra[] = '<script type="text/javascript" src="'
-    . api_get_path(WEB_PATH) . 'web/assets/simpleWebRTC/latest.js'
+    . api_get_path(WEB_PATH) . 'web/assets/simpleWebRTC/latest-v2.js'
     . '"></script>' . "\n";
 
+$navigator = api_get_navigator();
+
 $template = new Template();
 $template->assign('room_name', $chatVideo->getRoomName());
 $template->assign('chat_user', $chatUser);
 $template->assign('user_local', $userLocal);
 $template->assign('block_friends', $friend_html);
+$template->assign('navigator_is_firefox', $navigator['name'] == 'Mozilla');
 
 $tpl = $template->get_template('chat/video.tpl');
 $content = $template->fetch($tpl);

+ 37 - 26
main/inc/lib/myspace.lib.php

@@ -355,18 +355,18 @@ class MySpace
         $parameters['view'] = 'admin';
         $table->set_additional_parameters($parameters);
         if ($is_western_name_order) {
-            $table -> set_header(0, get_lang('FirstName'), true);
-            $table -> set_header(1, get_lang('LastName'), true);
+            $table->set_header(0, get_lang('FirstName'), true);
+            $table->set_header(1, get_lang('LastName'), true);
         } else {
-            $table -> set_header(0, get_lang('LastName'), true);
-            $table -> set_header(1, get_lang('FirstName'), true);
+            $table->set_header(0, get_lang('LastName'), true);
+            $table->set_header(1, get_lang('FirstName'), true);
         }
-        $table -> set_header(2, get_lang('TimeSpentOnThePlatform'), false);
-        $table -> set_header(3, get_lang('LastConnexion'), false);
-        $table -> set_header(4, get_lang('NbStudents'), false);
-        $table -> set_header(5, get_lang('CountCours'), false);
-        $table -> set_header(6, get_lang('NumberOfSessions'), false);
-        $table -> set_header(7, get_lang('Sessions'), false);
+        $table->set_header(2, get_lang('TimeSpentOnThePlatform'), false);
+        $table->set_header(3, get_lang('LastConnexion'), false);
+        $table->set_header(4, get_lang('NbStudents'), false);
+        $table->set_header(5, get_lang('CountCours'), false);
+        $table->set_header(6, get_lang('NumberOfSessions'), false);
+        $table->set_header(7, get_lang('Sessions'), false);
 
         if ($is_western_name_order) {
             $csv_header[] = array(
@@ -1052,7 +1052,12 @@ class MySpace
             $direction = 'ASC';
         }
 
-        $course_data = self::get_course_data_tracking_overview($from, 1000, $orderby, $direction);
+        $course_data = self::get_course_data_tracking_overview(
+            $from,
+            1000,
+            $orderby,
+            $direction
+        );
 
         $csv_content = array();
 
@@ -1745,9 +1750,9 @@ class MySpace
         global $courses, $csv_content, $charset, $session_id;
 
         // definition database tables
-        $tbl_course                 = Database::get_main_table(TABLE_MAIN_COURSE);
-        $tbl_course_user            = Database::get_main_table(TABLE_MAIN_COURSE_USER);
-        $tbl_session_course_user    = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
+        $tbl_course = Database::get_main_table(TABLE_MAIN_COURSE);
+        $tbl_course_user = Database::get_main_table(TABLE_MAIN_COURSE_USER);
+        $tbl_session_course_user = Database::get_main_table(TABLE_MAIN_SESSION_COURSE_USER);
 
         $course_data = array();
         $courses_code = array_keys($courses);
@@ -1798,19 +1803,18 @@ class MySpace
 
             if (count($users) > 0) {
                 $nb_students_in_course = count($users);
-                $avg_assignments_in_course  = Tracking::count_student_assignments($users, $course_code, $session_id);
-                $avg_messages_in_course     = Tracking::count_student_messages($users, $course_code, $session_id);
-                $avg_progress_in_course     = Tracking::get_avg_student_progress($users, $course_code, array(), $session_id);
-                $avg_score_in_course        = Tracking::get_avg_student_score($users, $course_code, array(), $session_id);
-                $avg_score_in_exercise      = Tracking::get_avg_student_exercise_score($users, $course_code, 0, $session_id);
-                $avg_time_spent_in_course   = Tracking::get_time_spent_on_the_course($users, $courseInfo['real_id'], $session_id);
+                $avg_assignments_in_course = Tracking::count_student_assignments($users, $course_code, $session_id);
+                $avg_messages_in_course = Tracking::count_student_messages($users, $course_code, $session_id);
+                $avg_progress_in_course = Tracking::get_avg_student_progress($users, $course_code, array(), $session_id);
+                $avg_score_in_course = Tracking::get_avg_student_score($users, $course_code, array(), $session_id);
+                $avg_score_in_exercise = Tracking::get_avg_student_exercise_score($users, $course_code, 0, $session_id);
+                $avg_time_spent_in_course = Tracking::get_time_spent_on_the_course($users, $courseInfo['real_id'], $session_id);
 
                 $avg_progress_in_course = round($avg_progress_in_course / $nb_students_in_course, 2);
                 if (is_numeric($avg_score_in_course)) {
                     $avg_score_in_course = round($avg_score_in_course / $nb_students_in_course, 2);
                 }
                 $avg_time_spent_in_course = api_time_to_hms($avg_time_spent_in_course / $nb_students_in_course);
-
             } else {
                 $avg_time_spent_in_course = null;
                 $avg_progress_in_course = null;
@@ -1868,6 +1872,7 @@ class MySpace
      * @param int $numberItems
      * @param int $column
      * @param string $direction
+     * @return array
      */
     public static function get_user_data_tracking_overview($from, $numberItems, $column, $direction)
     {
@@ -1934,6 +1939,7 @@ class MySpace
         $extra_data = UserManager::get_extra_user_data($user_id, true);
         return $extra_data;
     }
+
     /**
      * Checks if a username exist in the DB otherwise it create a "double"
      * i.e. if we look into for jmontoya but the user's name already exist we create the user jmontoya2
@@ -2116,7 +2122,7 @@ class MySpace
 
     /**
      * Validates imported data.
-     * @param list of users
+     * @param $users list of users
      */
     function validate_data($users, $id_session = null)
     {
@@ -2356,7 +2362,8 @@ class MySpace
     /**
      * XML-parser: the handler at the end of element.
      */
-    function element_end($parser, $data) {
+    function element_end($parser, $data)
+    {
         $data = api_utf8_decode($data);
         global $user;
         global $users;
@@ -2377,7 +2384,8 @@ class MySpace
     /**
      * XML-parser: the handler for character data.
      */
-    function character_data($parser, $data) {
+    function character_data($parser, $data)
+    {
         $data = trim(api_utf8_decode($data));
         global $current_value;
         $current_value = $data;
@@ -2388,7 +2396,8 @@ class MySpace
      * @param string $file Path to the XML-file
      * @return array All userinformation read from the file
      */
-    function parse_xml_data($file) {
+    function parse_xml_data($file)
+    {
         global $current_tag;
         global $current_value;
         global $user;
@@ -2537,7 +2546,9 @@ class MySpace
             $table->set_header(6, get_lang('TimeLoggedIn'), false);
         }
 
-        $template = new Template(null, false, false, false, false, false, false);
+        $template = new Template(
+            null, false, false, false, false, false, false
+        );
         $template->assign('form', $form->returnForm());
         $template->assign('table', $table ? $table->return_table() : null);
 

+ 10 - 3
main/inc/lib/pdf.lib.php

@@ -53,7 +53,10 @@ class PDF
         $this->params['student_info'] = isset($params['student_info']) ? $params['student_info'] : false;
         $this->params['show_grade_generated_date'] = isset($params['show_grade_generated_date']) ? $params['show_grade_generated_date'] : false;
         $this->params['show_teacher_as_myself'] = isset($params['show_teacher_as_myself']) ? $params['show_teacher_as_myself'] : true;
-        $this->params['pdf_date'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date(api_get_local_time(), DATE_TIME_FORMAT_LONG);
+        $localTime = api_get_local_time();
+        $this->params['pdf_date'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_TIME_FORMAT_LONG);
+        $this->params['pdf_date_only'] = isset($params['pdf_date']) ? $params['pdf_date'] : api_format_date($localTime, DATE_FORMAT_LONG);
+
 
         $this->pdf = new mPDF(
             'UTF-8',
@@ -127,6 +130,7 @@ class PDF
         $tpl->assign('pdf_course_info', $this->params['course_info']);
         $tpl->assign('pdf_session_info', $this->params['session_info']);
         $tpl->assign('pdf_date', $this->params['pdf_date']);
+        $tpl->assign('pdf_date_only', $this->params['pdf_date_only']);
         $tpl->assign('pdf_teachers', $teacher_list);
         $tpl->assign('pdf_title', $this->params['pdf_title']);
         $tpl->assign('pdf_student_info', $this->params['student_info']);
@@ -138,8 +142,11 @@ class PDF
         $html = $tpl->fetch($tableTemplate);
         $html = api_utf8_encode($html);
 
-        $css_file = api_get_path(SYS_CSS_PATH).'/print.css';
-        $css = file_exists($css_file) ? @file_get_contents($css_file) : '';
+        $css_file = api_get_path(SYS_CSS_PATH).'themes/'.$tpl->theme.'/print.css';
+        if (!file_exists($css_file)) {
+            $css_file = api_get_path(SYS_CSS_PATH).'print.css';
+        }
+        $css = file_get_contents($css_file);
 
         $html = self::content_to_pdf(
             $html,

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

@@ -389,10 +389,11 @@ class UserManager
                 UrlManager::add_user_to_url($userId, 1);
             }
 
+            $extra['item_id'] = $userId;
+
             if (is_array($extra) && count($extra) > 0) {
-                foreach ($extra as $fname => $fvalue) {
-                    self::update_extra_field_value($userId, $fname, $fvalue);
-                }
+                $courseFieldValue = new ExtraFieldValue('user');
+                $courseFieldValue->saveFieldValues($extra);
             } else {
                 // Create notify settings by default
                 self::update_extra_field_value($userId, 'mail_notify_invitation', '1');
@@ -3475,7 +3476,7 @@ class UserManager
         $return = array();
         if (Database::num_rows($result) > 0) {
             while ($row = Database::fetch_array($result, 'ASSOC')) {
-                $return[] = array('key' => $row['tag'], 'value' => $row['tag']);
+                $return[] = array('id' => $row['tag'], 'text' => $row['tag']);
             }
         }
         if ($return_format === 'json') {

+ 33 - 6
main/mySpace/myStudents.php

@@ -366,7 +366,7 @@ if (!empty($student_id)) {
     echo '<a href="'.api_get_self().'?'.Security::remove_XSS($_SERVER['QUERY_STRING']).'&export=xls">'.
     Display::return_icon('export_excel.png', get_lang('ExportAsXLS'), '', ICON_SIZE_MEDIUM).'</a> ';
 
-    if (!empty ($user_info['email'])) {
+    if (!empty($user_info['email'])) {
         $send_mail = '<a href="mailto:'.$user_info['email'].'">'.
             Display::return_icon('mail_send.png', get_lang('SendMail'), '', ICON_SIZE_MEDIUM).'</a>';
     } else {
@@ -385,11 +385,35 @@ if (!empty($student_id)) {
 
     if (api_is_platform_admin(false, true) || api_is_student_boss()) {
         echo Display::url(
-            Display::return_icon('skill-badges.png', get_lang('AssignSkill'), null, ICON_SIZE_MEDIUM),
+            Display::return_icon(
+                'skill-badges.png',
+                get_lang('AssignSkill'),
+                null,
+                ICON_SIZE_MEDIUM
+            ),
             api_get_path(WEB_CODE_PATH).'badge/assign.php?'.http_build_query(['user' => $student_id])
         );
     }
 
+    $permissions = StudentFollowUpPlugin::getPermissions(
+        $student_id,
+        api_get_user_id()
+    );
+    $isAllow = $permissions['is_allow'];
+    if ($isAllow) {
+        echo Display::url(
+            Display::return_icon(
+                'blog.png',
+                get_lang('Blog'),
+                null,
+                ICON_SIZE_MEDIUM
+            ),
+            api_get_path(WEB_PLUGIN_PATH).'studentfollowup/posts.php?student_id='.$student_id
+        );
+    }
+
+
+
     echo '</div>';
 
     // is the user online ?
@@ -1110,14 +1134,17 @@ if (!empty($student_id)) {
                     if (api_is_allowed_to_edit()) {
                         echo '<td>';
                         if ($any_result === true) {
-                            echo '<a href="myStudents.php?action=reset_lp&sec_token='.$token.'&cidReq='.$course_code.'&course='.$course_code.'&details='.Security::remove_XSS($_GET['details']).'&origin='.$origin.'&lp_id='.$learnpath->getId().'&student='.$user_info['user_id'].'&details=true&id_session='.$sessionId.'">';
-                            echo Display::return_icon(
+                            $url = 'myStudents.php?action=reset_lp&sec_token='.$token.'&cidReq='.$course_code.'&course='.$course_code.'&details='.Security::remove_XSS($_GET['details']).'&origin='.$origin.'&lp_id='.$learnpath->getId().'&student='.$user_info['user_id'].'&details=true&id_session='.$sessionId;
+                            echo Display::url(
+                                Display::return_icon(
                                     'clean.png',
                                     get_lang('Clean'),
                                     '',
                                     ICON_SIZE_SMALL
-                                ).'</a>';
-                            echo '</a>';
+                                ),
+                                $url,
+                                ['onclick' => "javascript:if(!confirm('".addslashes(api_htmlentities(get_lang('AreYouSureToDelete')))."')) return false;"]
+                            );
                         }
                         echo '</td>';
                         echo '</tr>';

+ 245 - 248
main/session/session_export.php

@@ -2,7 +2,7 @@
 /* For licensing terms, see /license.txt */
 
 /**
-*	@package chamilo.admin
+ * @package chamilo.admin
 */
 
 $cidReset = true;
@@ -31,245 +31,242 @@ $archivePath = api_get_path(SYS_ARCHIVE_PATH);
 $archiveURL = api_get_path(WEB_CODE_PATH).'course_info/download.php?archive_path=&archive=';
 
 $tool_name = get_lang('ExportSessionListXMLCSV');
-
 $interbreadcrumb[] = array('url' => 'session_list.php', 'name' => get_lang('SessionList'));
-
 set_time_limit(0);
-
 if (isset($_POST['formSent'])) {
-	$formSent = $_POST['formSent'];
-	$file_type = isset($_POST['file_type']) ? $_POST['file_type'] : 'csv';
-	$session_id = $_POST['session_id'];
-	if (empty($session_id)) {
-		$sql = "SELECT
-					s.id,
-					name,
-					id_coach,
-					username,
-					access_start_date,
-					access_end_date,
-					visibility,
-					session_category_id
-				FROM $tbl_session s
-				INNER JOIN $tbl_user
-				ON $tbl_user.user_id = s.id_coach
-				ORDER BY id";
-
-		if (api_is_multiple_url_enabled()) {
-			$tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
-			$access_url_id = api_get_current_access_url_id();
-			if ($access_url_id != -1) {
-			$sql = "SELECT s.id, name,id_coach,username,access_start_date,access_end_date,visibility,session_category_id
-					FROM $tbl_session s
-					INNER JOIN $tbl_session_rel_access_url as session_rel_url
-					ON (s.id= session_rel_url.session_id)
-					INNER JOIN $tbl_user u ON (u.user_id = s.id_coach)
-					WHERE access_url_id = $access_url_id
-					ORDER BY id";
-			}
-		}
-
-		$result = Database::query($sql);
-	} else {
-		$sql = "SELECT s.id,name,username,access_start_date,access_end_date,visibility,session_category_id
-				FROM $tbl_session s
-				INNER JOIN $tbl_user
-					ON $tbl_user.user_id = s.id_coach
-				WHERE s.id='$session_id'";
-		$result = Database::query($sql);
-	}
-
-	if (Database::num_rows($result)) {
-		$sessionListToExport = [];
-		if (in_array($file_type, ['csv', 'xls'])) {
-			$archiveFile = 'export_sessions_'.$session_id.'_'.api_get_local_time();
-			$cvs = true;
-			$sessionListToExport[] = [
-				'SessionName',
-				'Coach',
-				'DateStart',
-				'DateEnd',
-				'Visibility',
-				'SessionCategory',
-				'Users',
-				'Courses'
-			];
-		} else {
-			if (!file_exists($archivePath)) {
-				mkdir($archivePath, api_get_permissions_for_new_directories(), true);
-			}
-
-			if (!file_exists($archivePath.'index.html')) {
-				$fp = fopen($archivePath.'index.html', 'w');
-				fputs($fp, '<html><head></head><body></body></html>');
-				fclose($fp);
-			}
-
-			$archiveFile = 'export_sessions_'.$session_id.'_'.api_get_local_time().'.'.$file_type;
-			while (file_exists($archivePath.$archiveFile)) {
-				$archiveFile = 'export_users_'.$session_id.'_'.api_get_local_time().'_'.uniqid('').'.'.$file_type;
-			}
-
-			$cvs = false;
-			$fp = fopen($archivePath.$archiveFile, 'w');
-			fputs($fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Sessions>\n");
-		}
-
-		while ($row = Database::fetch_array($result)) {
-			$row['name'] = str_replace(';', ',', $row['name']);
-			$row['username'] = str_replace(';', ',', $row['username']);
-			$row['access_start_date'] = str_replace(';', ',', $row['access_start_date']);
-			$row['access_end_date'] = str_replace(';', ',', $row['access_end_date']);
-			$row['visibility'] = str_replace(';', ',', $row['visibility']);
-			$row['session_category'] = str_replace(';', ',', $row['session_category_id']);
-			// users
-			$sql = "SELECT DISTINCT $tbl_user.username
-					FROM $tbl_user
-					INNER JOIN $tbl_session_user
-					ON
-						$tbl_user.user_id = $tbl_session_user.user_id AND
-						$tbl_session_user.relation_type<>".SESSION_RELATION_TYPE_RRHH." AND
-						$tbl_session_user.session_id = '".$row['id']."'";
-
-			$rsUsers = Database::query($sql);
-			$users = '';
-			while ($rowUsers = Database::fetch_array($rsUsers)) {
-				if ($cvs) {
-					$users .= str_replace(';', ',', $rowUsers['username']).'|';
-				} else {
-					$users .= "\t\t<User>$rowUsers[username]</User>\n";
-				}
-			}
-
-			if (!empty($users) && $cvs) {
-				$users = api_substr($users, 0, api_strlen($users) - 1);
-			}
-
-			// Courses
-			$sql = "SELECT DISTINCT c.code, sc.id, c_id
-					FROM $tbl_course c
-					INNER JOIN $tbl_session_course_user sc
-						ON c.id = sc.c_id
-						AND sc.session_id = '".$row['id']."'";
-
-			$rsCourses = Database::query($sql);
-
-			$courses = '';
-			while ($rowCourses = Database::fetch_array($rsCourses)) {
-				// get coachs from a course
-				$sql = "SELECT u.username
-						FROM $tbl_session_course_user scu
-						INNER JOIN $tbl_user u
-						ON u.user_id = scu.user_id
-						WHERE
-							scu.c_id = '{$rowCourses['c_id']}' AND
-							scu.session_id = '".$row['id']."' AND
-							scu.status = 2 ";
-
-				$rs_coachs = Database::query($sql);
-				$coachs = array();
-				while ($row_coachs = Database::fetch_array($rs_coachs)) {
-					$coachs[] = $row_coachs['username'];
-				}
-
-				$coachs = implode(",", $coachs);
-
-				if ($cvs) {
-					$courses .= str_replace(';', ',', $rowCourses['code']);
-					$courses .= '['.str_replace(';', ',', $coachs).'][';
-				} else {
-					$courses .= "\t\t<Course>\n";
-					$courses .= "\t\t\t<CourseCode>$rowCourses[code]</CourseCode>\n";
-					$courses .= "\t\t\t<Coach>$coachs</Coach>\n";
-				}
-
-				// rel user courses
-				$sql = "SELECT DISTINCT u.username
-						FROM $tbl_session_course_user scu
-						INNER JOIN $tbl_session_user su
-						ON
-							scu.user_id = su.user_id AND
-							scu.session_id = su.session_id AND
-							su.relation_type<>".SESSION_RELATION_TYPE_RRHH."
-						INNER JOIN $tbl_user u
-						ON
-							scu.user_id = u.user_id AND
-							scu.c_id='".$rowCourses['c_id']."' AND
-							scu.session_id='".$row['id']."'";
-
-				$rsUsersCourse = Database::query($sql);
-				$userscourse = '';
-				while ($rowUsersCourse = Database::fetch_array($rsUsersCourse)) {
-					if ($cvs) {
-						$userscourse .= str_replace(';', ',', $rowUsersCourse['username']).',';
-					} else {
-						$courses .= "\t\t\t<User>$rowUsersCourse[username]</User>\n";
-					}
-				}
-
-				if ($cvs) {
-					if (!empty($userscourse)) {
-						$userscourse = api_substr(
-							$userscourse,
-							0,
-							api_strlen($userscourse) - 1
-						);
-					}
-
-					$courses .= $userscourse.']|';
-				} else {
-					$courses .= "\t\t</Course>\n";
-				}
-			}
-
-			if (!empty($courses) && $cvs) {
-				$courses = api_substr($courses, 0, api_strlen($courses) - 1);
-			}
-			$add = $courses;
-
-			if (in_array($file_type, ['csv', 'xls'])) {
-				$sessionListToExport[] = [
-					$row['name'],
-					$row['username'],
-					$row['access_start_date'],
-					$row['access_end_date'],
-					$row['visibility'],
-					$row['session_category'],
-					$users,
-					$courses
-				];
-			} else {
-				$add = "\t<Session>\n"
-						 ."\t\t<SessionName>$row[name]</SessionName>\n"
-						 ."\t\t<Coach>$row[username]</Coach>\n"
-						 ."\t\t<DateStart>$row[access_start_date]</DateStart>\n"
-						 ."\t\t<DateEnd>$row[access_end_date]</DateEnd>\n"
-						 ."\t\t<Visibility>$row[visibility]</Visibility>\n"
-						 ."\t\t<SessionCategory>$row[session_category]</SessionCategory>\n";
-			}
-
-		 	if (!$cvs) {
-				$add .= "\t</Session>\n";
-				fputs($fp, $add);
-			}
-		}
-
-		switch ($file_type) {
-			case 'xml':
-				fputs($fp, "</Sessions>\n");
-				fclose($fp);
-				$errorMsg = get_lang('UserListHasBeenExported').'<br/>
-				<a class="btn btn-default" href="'.$archiveURL.$archiveFile.'">'.get_lang('ClickHereToDownloadTheFile').'</a>';
-				break;
-			case 'csv':
-				Export::arrayToCsv($sessionListToExport, $archiveFile);
-				exit;
-			case 'xls':
-				Export::arrayToXls($sessionListToExport, $archiveFile);
-				exit;
-				break;
-		}
-	}
+    $formSent = $_POST['formSent'];
+    $file_type = isset($_POST['file_type']) ? $_POST['file_type'] : 'csv';
+    $session_id = $_POST['session_id'];
+    if (empty($session_id)) {
+        $sql = "SELECT
+                    s.id,
+                    name,
+                    id_coach,
+                    username,
+                    access_start_date,
+                    access_end_date,
+                    visibility,
+                    session_category_id
+                FROM $tbl_session s
+                INNER JOIN $tbl_user
+                ON $tbl_user.user_id = s.id_coach
+                ORDER BY id";
+
+        if (api_is_multiple_url_enabled()) {
+            $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
+            $access_url_id = api_get_current_access_url_id();
+            if ($access_url_id != -1) {
+            $sql = "SELECT s.id, name,id_coach,username,access_start_date,access_end_date,visibility,session_category_id
+                    FROM $tbl_session s
+                    INNER JOIN $tbl_session_rel_access_url as session_rel_url
+                    ON (s.id= session_rel_url.session_id)
+                    INNER JOIN $tbl_user u ON (u.user_id = s.id_coach)
+                    WHERE access_url_id = $access_url_id
+                    ORDER BY id";
+            }
+        }
+
+        $result = Database::query($sql);
+    } else {
+        $sql = "SELECT s.id,name,username,access_start_date,access_end_date,visibility,session_category_id
+                FROM $tbl_session s
+                INNER JOIN $tbl_user
+                    ON $tbl_user.user_id = s.id_coach
+                WHERE s.id='$session_id'";
+        $result = Database::query($sql);
+    }
+
+    if (Database::num_rows($result)) {
+        $sessionListToExport = [];
+        if (in_array($file_type, ['csv', 'xls'])) {
+            $archiveFile = 'export_sessions_'.$session_id.'_'.api_get_local_time();
+            $cvs = true;
+            $sessionListToExport[] = [
+                'SessionName',
+                'Coach',
+                'DateStart',
+                'DateEnd',
+                'Visibility',
+                'SessionCategory',
+                'Users',
+                'Courses'
+            ];
+        } else {
+            if (!file_exists($archivePath)) {
+                mkdir($archivePath, api_get_permissions_for_new_directories(), true);
+            }
+
+            if (!file_exists($archivePath.'index.html')) {
+                $fp = fopen($archivePath.'index.html', 'w');
+                fputs($fp, '<html><head></head><body></body></html>');
+                fclose($fp);
+            }
+
+            $archiveFile = 'export_sessions_'.$session_id.'_'.api_get_local_time().'.'.$file_type;
+            while (file_exists($archivePath.$archiveFile)) {
+                $archiveFile = 'export_users_'.$session_id.'_'.api_get_local_time().'_'.uniqid('').'.'.$file_type;
+            }
+
+            $cvs = false;
+            $fp = fopen($archivePath.$archiveFile, 'w');
+            fputs($fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Sessions>\n");
+        }
+
+        while ($row = Database::fetch_array($result)) {
+            $row['name'] = str_replace(';', ',', $row['name']);
+            $row['username'] = str_replace(';', ',', $row['username']);
+            $row['access_start_date'] = str_replace(';', ',', $row['access_start_date']);
+            $row['access_end_date'] = str_replace(';', ',', $row['access_end_date']);
+            $row['visibility'] = str_replace(';', ',', $row['visibility']);
+            $row['session_category'] = str_replace(';', ',', $row['session_category_id']);
+            // users
+            $sql = "SELECT DISTINCT $tbl_user.username
+                    FROM $tbl_user
+                    INNER JOIN $tbl_session_user
+                    ON
+                        $tbl_user.user_id = $tbl_session_user.user_id AND
+                        $tbl_session_user.relation_type<>".SESSION_RELATION_TYPE_RRHH." AND
+                        $tbl_session_user.session_id = '".$row['id']."'";
+
+            $rsUsers = Database::query($sql);
+            $users = '';
+            while ($rowUsers = Database::fetch_array($rsUsers)) {
+                if ($cvs) {
+                    $users .= str_replace(';', ',', $rowUsers['username']).'|';
+                } else {
+                    $users .= "\t\t<User>$rowUsers[username]</User>\n";
+                }
+            }
+
+            if (!empty($users) && $cvs) {
+                $users = api_substr($users, 0, api_strlen($users) - 1);
+            }
+
+            // Courses
+            $sql = "SELECT DISTINCT c.code, sc.id, c_id
+                    FROM $tbl_course c
+                    INNER JOIN $tbl_session_course_user sc
+                        ON c.id = sc.c_id
+                        AND sc.session_id = '".$row['id']."'";
+
+            $rsCourses = Database::query($sql);
+
+            $courses = '';
+            while ($rowCourses = Database::fetch_array($rsCourses)) {
+                // get coachs from a course
+                $sql = "SELECT u.username
+                        FROM $tbl_session_course_user scu
+                        INNER JOIN $tbl_user u
+                        ON u.user_id = scu.user_id
+                        WHERE
+                            scu.c_id = '{$rowCourses['c_id']}' AND
+                            scu.session_id = '".$row['id']."' AND
+                            scu.status = 2 ";
+
+                $rs_coachs = Database::query($sql);
+                $coachs = array();
+                while ($row_coachs = Database::fetch_array($rs_coachs)) {
+                    $coachs[] = $row_coachs['username'];
+                }
+
+                $coachs = implode(",", $coachs);
+
+                if ($cvs) {
+                    $courses .= str_replace(';', ',', $rowCourses['code']);
+                    $courses .= '['.str_replace(';', ',', $coachs).'][';
+                } else {
+                    $courses .= "\t\t<Course>\n";
+                    $courses .= "\t\t\t<CourseCode>$rowCourses[code]</CourseCode>\n";
+                    $courses .= "\t\t\t<Coach>$coachs</Coach>\n";
+                }
+
+                // rel user courses
+                $sql = "SELECT DISTINCT u.username
+                        FROM $tbl_session_course_user scu
+                        INNER JOIN $tbl_session_user su
+                        ON
+                            scu.user_id = su.user_id AND
+                            scu.session_id = su.session_id AND
+                            su.relation_type<>".SESSION_RELATION_TYPE_RRHH."
+                        INNER JOIN $tbl_user u
+                        ON
+                            scu.user_id = u.user_id AND
+                            scu.c_id='".$rowCourses['c_id']."' AND
+                            scu.session_id='".$row['id']."'";
+
+                $rsUsersCourse = Database::query($sql);
+                $userscourse = '';
+                while ($rowUsersCourse = Database::fetch_array($rsUsersCourse)) {
+                    if ($cvs) {
+                        $userscourse .= str_replace(';', ',', $rowUsersCourse['username']).',';
+                    } else {
+                        $courses .= "\t\t\t<User>$rowUsersCourse[username]</User>\n";
+                    }
+                }
+
+                if ($cvs) {
+                    if (!empty($userscourse)) {
+                        $userscourse = api_substr(
+                            $userscourse,
+                            0,
+                            api_strlen($userscourse) - 1
+                        );
+                    }
+
+                    $courses .= $userscourse.']|';
+                } else {
+                    $courses .= "\t\t</Course>\n";
+                }
+            }
+
+            if (!empty($courses) && $cvs) {
+                $courses = api_substr($courses, 0, api_strlen($courses) - 1);
+            }
+            $add = $courses;
+
+            if (in_array($file_type, ['csv', 'xls'])) {
+                $sessionListToExport[] = [
+                    $row['name'],
+                    $row['username'],
+                    $row['access_start_date'],
+                    $row['access_end_date'],
+                    $row['visibility'],
+                    $row['session_category'],
+                    $users,
+                    $courses
+                ];
+            } else {
+                $add = "\t<Session>\n"
+                         ."\t\t<SessionName>$row[name]</SessionName>\n"
+                         ."\t\t<Coach>$row[username]</Coach>\n"
+                         ."\t\t<DateStart>$row[access_start_date]</DateStart>\n"
+                         ."\t\t<DateEnd>$row[access_end_date]</DateEnd>\n"
+                         ."\t\t<Visibility>$row[visibility]</Visibility>\n"
+                         ."\t\t<SessionCategory>$row[session_category]</SessionCategory>\n";
+            }
+
+            if (!$cvs) {
+                $add .= "\t</Session>\n";
+                fputs($fp, $add);
+            }
+        }
+
+        switch ($file_type) {
+            case 'xml':
+                fputs($fp, "</Sessions>\n");
+                fclose($fp);
+                $errorMsg = get_lang('UserListHasBeenExported').'<br/>
+                <a class="btn btn-default" href="'.$archiveURL.$archiveFile.'">'.get_lang('ClickHereToDownloadTheFile').'</a>';
+                break;
+            case 'csv':
+                Export::arrayToCsv($sessionListToExport, $archiveFile);
+                exit;
+            case 'xls':
+                Export::arrayToXls($sessionListToExport, $archiveFile);
+                exit;
+                break;
+        }
+    }
 }
 
 // display the header
@@ -279,26 +276,26 @@ Display::display_header($tool_name);
 $sql = "SELECT id, name FROM $tbl_session ORDER BY name";
 
 if (api_is_multiple_url_enabled()) {
-	$tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
-	$access_url_id = api_get_current_access_url_id();
-	if ($access_url_id != -1) {
-	$sql = "SELECT s.id, name FROM $tbl_session s
-			INNER JOIN $tbl_session_rel_access_url as session_rel_url
-			ON (s.id = session_rel_url.session_id)
-			WHERE access_url_id = $access_url_id
-			ORDER BY name";
-	}
+    $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
+    $access_url_id = api_get_current_access_url_id();
+    if ($access_url_id != -1) {
+        $sql = "SELECT s.id, name FROM $tbl_session s
+                INNER JOIN $tbl_session_rel_access_url as session_rel_url
+                ON (s.id = session_rel_url.session_id)
+                WHERE access_url_id = $access_url_id
+                ORDER BY name";
+    }
 }
 $result = Database::query($sql);
 $Sessions = Database::store_result($result);
 
 echo '<div class="actions">';
 echo '<a href="../session/session_list.php">'.
-		Display::return_icon('back.png', get_lang('BackTo').' '.get_lang('SessionList'), '', ICON_SIZE_MEDIUM).'</a>';
+        Display::return_icon('back.png', get_lang('BackTo').' '.get_lang('SessionList'), '', ICON_SIZE_MEDIUM).'</a>';
 echo '</div>';
 
 if (!empty($errorMsg)) {
-	echo Display::return_message($errorMsg, 'normal', false); //main API
+    echo Display::return_message($errorMsg, 'normal', false); //main API
 }
 
 $form = new FormValidator('session_export', 'post', api_get_self());

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

@@ -11,7 +11,7 @@
     <div class="row">
         <div class="col-md-12">
         <!-- header catalog session -->
-        <h2 class="title-session">{{ 'Sessions'|get_lang }}</h2>      	
+        <h2 class="title-session">{{ 'Sessions'|get_lang }}</h2>
        	<div class="search-session">
                 <div class="row">
                     <div class="col-md-6">
@@ -61,7 +61,7 @@
             <div class="row">
                 {% for item in sessions %}
                     <div class="col-md-4 col-sm-6 col-xs-12">
-                        <div id="session-{{ item.id }}" class="items items-courses">
+                        <div id="session-{{ item.id }}" class="items items-courses items-sessions">
                             <div class="image">
                                 <a href="{{ _p.web ~ 'session/' ~ item.id ~ '/about/' }}" title="{{ item.name }}">
                                 	<img class="img-responsive" src="{{ item.image ? _p.web_upload ~ item.image : 'session_default.png'|icon() }}">
@@ -145,7 +145,7 @@
                                         </div>
                                     	{% endif %}
                                     </div>
-                                    
+
                                 </div>
                             </div>
                             {% if _u.logged %}

+ 9 - 3
main/template/default/chat/video.tpl

@@ -4,9 +4,11 @@
             <span aria-hidden="true">&times;</span>
         </button>
         <h4>{{ 'Warning'|get_lang }}</h4>
-        <p>
-            <em class="fa fa-warning"></em> {{ 'AvoidChangingPageAsThisWillCutYourCurrentVideoChatSession'|get_lang }}
-        </p>
+        <div id="dlg-webrtc-help">
+            <p>{{ 'WebRTCDialogHelp'|get_lang }}</p>
+            <img src="{{ _p.web_lib ~ 'javascript/chat/img/webrtc_' ~ (navigator_is_firefox ? 'firefox' : 'chrome') }}.png"
+                 alt="{{ 'Permissions'|get_lang }}" class="img-thumbnail img-responsive">
+        </div>
     </div>
     <div class="row">
         <div class="col-md-8 col-sm-7">
@@ -65,6 +67,10 @@
                     });
 
                     webRTC.on('readyToCall', function () {
+                        $('#dlg-webrtc-help').replaceWith("<p>" +
+                            "<em class=\"fa fa-warning\"></em> {{ 'AvoidChangingPageAsThisWillCutYourCurrentVideoChatSession'|get_lang }}" +
+                            "</p>");
+
                         webRTC.joinRoom('{{ room_name }}');
                     });
                     webRTC.on('videoAdded', function (video, peer) {

+ 23 - 11
main/webservices/access_url.php

@@ -62,19 +62,22 @@ function WSHelperVerifyKey($params)
         list($ip1, $ip2) = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
         $ip = trim($ip1);
     }
-    if ($debug)
+    if ($debug) {
         error_log("ip: $ip");
+    }
     // Check if a file that limits access from webservices exists and contains
     // the restraining check
     if (is_file('webservice-auth-ip.conf.php')) {
         include 'webservice-auth-ip.conf.php';
-        if ($debug)
+        if ($debug) {
             error_log("webservice-auth-ip.conf.php file included");
+        }
         if (!empty($ws_auth_ip)) {
             $check_ip = true;
             $ip_matches = api_check_ip_in_range($ip, $ws_auth_ip);
-            if ($debug)
+            if ($debug) {
                 error_log("ip_matches: $ip_matches");
+            }
         }
     }
 
@@ -91,8 +94,9 @@ function WSHelperVerifyKey($params)
 
     $result = api_is_valid_secret_key($secret_key, $security_key);
     //error_log($secret_key.'-'.$security_key);
-    if ($debug)
+    if ($debug) {
         error_log('WSHelperVerifyKey result: '.intval($result));
+    }
     return $result;
 }
 
@@ -114,8 +118,6 @@ $server->soap_defencoding = 'UTF-8';
 // Initialize WSDL support
 $server->configureWSDL('WSAccessUrl', 'urn:WSAccessUrl');
 
-
-
 $server->wsdl->addComplexType(
     'portalItem',
     'complexType',
@@ -135,7 +137,13 @@ $server->wsdl->addComplexType(
     '',
     'SOAP-ENC:Array',
     array(),
-    array(array('ref'=>'SOAP-ENC:arrayType', 'wsdl:arrayType' => 'tns:portalItem[]')), 'tns:portalItem'
+    array(
+        array(
+            'ref' => 'SOAP-ENC:arrayType',
+            'wsdl:arrayType' => 'tns:portalItem[]',
+        ),
+    ),
+    'tns:portalItem'
 );
 
 $server->wsdl->addComplexType(
@@ -197,7 +205,8 @@ $server->wsdl->addComplexType(
 );
 
 // Register the method to expose
-$server->register('WSAddUserToPortal', // method name
+$server->register(
+    'WSAddUserToPortal', // method name
     array('addUserToPortal' => 'tns:AddUserToPortal'), // input parameters
     array('return' => 'xsd:string'), // output parameters
     'urn:WSAccessUrl', // namespace
@@ -228,7 +237,8 @@ function WSAddUserToPortal($params)
 }
 
 // Register the method to expose
-$server->register('WSRemoveUserFromPortal', // method name
+$server->register(
+    'WSRemoveUserFromPortal', // method name
     array('removeUserFromPortal' => 'tns:AddUserToPortal'), // input parameters
     array('return' => 'xsd:string'), // output parameters
     'urn:WSAccessUrl', // namespace
@@ -271,7 +281,8 @@ $server->wsdl->addComplexType(
 );
 
 // Register the method to expose
-$server->register('WSGetPortalListFromUser', // method name
+$server->register(
+    'WSGetPortalListFromUser', // method name
     array('getPortalListFromUser' => 'tns:getPortalListFromUser'), // input parameters
     array('return' => 'tns:portalList'), // output parameters
     'urn:WSAccessUrl', // namespace
@@ -315,7 +326,8 @@ $server->wsdl->addComplexType(
 );
 
 // Register the method to expose
-$server->register('WSGetPortalListFromCourse', // method name
+$server->register(
+    'WSGetPortalListFromCourse', // method name
     array('getPortalListFromCourse' => 'tns:getPortalListFromCourse'), // input parameters
     array('return' => 'tns:portalList'), // output parameters
     'urn:WSAccessUrl', // namespace

+ 8 - 1
plugin/buycourses/src/service_process_confirm.php

@@ -83,7 +83,14 @@ switch ($serviceSale['payment_type']) {
     case BuyCoursesPlugin::PAYMENT_TYPE_TRANSFER:
         $transferAccounts = $plugin->getTransferAccounts();
 
-        $form = new FormValidator('success', 'POST', api_get_self(), null, null, FormValidator::LAYOUT_INLINE);
+        $form = new FormValidator(
+            'success',
+            'POST',
+            api_get_self(),
+            null,
+            null,
+            FormValidator::LAYOUT_INLINE
+        );
 
         if ($form->validate()) {
             $formValues = $form->getSubmitValues();

+ 18 - 7
plugin/buycourses/src/service_success.php

@@ -15,7 +15,6 @@ if (!$paypalEnabled) {
 }
 
 $serviceSaleId = $_SESSION['bc_service_sale_id'];
-
 $serviceSale = $plugin->getServiceSale($serviceSaleId);
 $itemPrice = $serviceSale['price'];
 
@@ -34,8 +33,20 @@ require_once("paypalfunctions.php");
 
 $buyerInformation = GetShippingDetails(urlencode($_SESSION['TOKEN']));
 
-$form = new FormValidator('success', 'POST', api_get_self(), null, null, FormValidator::LAYOUT_INLINE);
-$form->addButton('confirm', $plugin->get_lang('ConfirmOrder'), 'check', 'success');
+$form = new FormValidator(
+    'success',
+    'POST',
+    api_get_self(),
+    null,
+    null,
+    FormValidator::LAYOUT_INLINE
+);
+$form->addButton(
+    'confirm',
+    $plugin->get_lang('ConfirmOrder'),
+    'check',
+    'success'
+);
 $form->addButtonCancel($plugin->get_lang('CancelOrder'), 'cancel');
 
 if ($form->validate()) {
@@ -54,7 +65,6 @@ if ($form->validate()) {
     }
 
     $confirmPayments = ConfirmPayment($itemPrice);
-
     if ($confirmPayments['ACK'] !== 'Success') {
         $erroMessage = vsprintf(
             $plugin->get_lang('ErrorOccurred'),
@@ -154,16 +164,17 @@ if ($form->validate()) {
 }
 
 $token = isset($_GET['token']) ? Security::remove_XSS($_GET['token']) : null;
-
 if (empty($token)) {
     api_not_allowed(true);
 }
 
-$interbreadcrumb[] = array("url" => "service_catalog.php", "name" => $plugin->get_lang('ListOfServicesOnSale'));
+$interbreadcrumb[] = array(
+    "url" => "service_catalog.php",
+    "name" => $plugin->get_lang('ListOfServicesOnSale'),
+);
 
 $templateName = $plugin->get_lang('PaymentMethods');
 $tpl = new Template($templateName);
-
 $tpl->assign('title', $serviceSale['service']['name']);
 $tpl->assign('price', $serviceSale['price']);
 $tpl->assign('currency', $serviceSale['currency_id']);

+ 0 - 5
plugin/buycourses/src/services_edit.php

@@ -131,21 +131,16 @@ if ($form->validate()) {
     $values = $form->getSubmitValues();
 
     if (isset($values['delete_service'])) {
-
         $plugin->deleteService($serviceId);
         Display::addFlash(
             Display::return_message($plugin->get_lang('ServiceDeleted'), 'error')
         );
     } else {
-
         $plugin->updateService($values, $serviceId);
         Display::addFlash(
             Display::return_message($plugin->get_lang('ServiceEdited'), 'success')
         );
     }
-
-
-
     header('Location: configuration.php');
     exit;
 }

+ 11 - 5
plugin/buycourses/src/success.php

@@ -44,7 +44,14 @@ $paypalSignature = $paypalParams['signature'];
 
 require_once("paypalfunctions.php");
 
-$form = new FormValidator('success', 'POST', api_get_self(), null, null, FormValidator::LAYOUT_INLINE);
+$form = new FormValidator(
+    'success',
+    'POST',
+    api_get_self(),
+    null,
+    null,
+    FormValidator::LAYOUT_INLINE
+);
 $form->addButton('confirm', $plugin->get_lang('ConfirmOrder'), 'check', 'success');
 $form->addButtonCancel($plugin->get_lang('CancelOrder'), 'cancel');
 
@@ -74,13 +81,12 @@ if ($form->validate()) {
         exit;
     }
 
-    $transactionId = $confirmPayments["PAYMENTINFO_0_TRANSACTIONID"];
-    $transactionType = $confirmPayments["PAYMENTINFO_0_TRANSACTIONTYPE"];
+    $transactionId = $confirmPayments['PAYMENTINFO_0_TRANSACTIONID'];
+    $transactionType = $confirmPayments['PAYMENTINFO_0_TRANSACTIONTYPE'];
 
-    switch ($confirmPayments["PAYMENTINFO_0_PAYMENTSTATUS"]) {
+    switch ($confirmPayments['PAYMENTINFO_0_PAYMENTSTATUS']) {
         case 'Completed':
             $saleIsCompleted = $plugin->completeSale($sale['id']);
-
             if ($saleIsCompleted && $buyingSession) {
                 Display::addFlash(
                     Display::return_message(

+ 7 - 7
plugin/dashboard/block_course/block_course.class.php

@@ -7,7 +7,8 @@
  */
 /**
  * This class is used like controller for this course block plugin,
- * the class name must be registered inside path.info file (e.g: controller = "BlockCourse"), so dashboard controller will be instantiate it
+ * the class name must be registered inside path.info file
+ * (e.g: controller = "BlockCourse"), so dashboard controller will be instantiate it
  * @package chamilo.dashboard
  */
 class BlockCourse extends Block
@@ -51,7 +52,8 @@ class BlockCourse extends Block
     }
 
     /**
-     * This method return content html containing information about courses and its position for showing it inside dashboard interface
+     * This method return content html containing information
+     * about courses and its position for showing it inside dashboard interface
      * it's important to use the name 'get_block' for beeing used from dashboard controller
      * @return array   column and content html
      */
@@ -92,10 +94,7 @@ class BlockCourse extends Block
     public function get_content_html()
     {
         $course_data = $this->get_course_information_data();
-        //$content = '<div style="margin:10px;">';
-        $content = '<h4>'.get_lang(
-                'YourCourseList'
-            ).'</h4>';
+        $content = '<h4>'.get_lang('YourCourseList').'</h4>';
         $data_table = null;
         if (!empty($course_data)) {
             $data_table .= '<table class="data_table" width:"95%">';
@@ -172,7 +171,8 @@ class BlockCourse extends Block
             if (count($users) > 0) {
                 $nb_students_in_course = count($users);
                 $avg_time_spent_in_course = api_time_to_hms(
-                    Tracking::get_time_spent_on_the_course($users, $courseId) / $nb_students_in_course);
+                    Tracking::get_time_spent_on_the_course($users, $courseId) / $nb_students_in_course
+                );
             } else {
                 $avg_time_spent_in_course = null;
             }

+ 1 - 5
plugin/dashboard/block_daily/block_daily.class.php

@@ -66,7 +66,6 @@ class BlockDaily extends Block
      */
     public function get_block()
     {
-
         global $charset;
         $column = 2;
         $data = array();
@@ -101,10 +100,7 @@ class BlockDaily extends Block
     public function get_content_html()
     {
         $course_data = $this->get_course_information_data();
-        //$content = '<div style="margin:10px;">';
-        $content = '<h4>'.get_lang(
-                'YourCourseList'
-            ).'</h4>';
+        $content = '<h4>'.get_lang('YourCourseList').'</h4>';
         $data_table = null;
         if (!empty($course_data)) {
             $data_table .= '<table class="data_table" width:"95%">';

+ 122 - 110
plugin/dashboard/block_evaluation_graph/block_evaluation_graph.class.php

@@ -5,7 +5,6 @@ use CpChart\Chart\Data as pData;
 use CpChart\Chart\Image as pImage;
 use CpChart\Chart\Cache as pCache;
 
-
 /**
  * Class BlockEvaluationGraph
  * This class is used like controller for this evaluations graph block plugin,
@@ -23,59 +22,59 @@ use CpChart\Chart\Cache as pCache;
 class BlockEvaluationGraph extends Block
 {
     private $user_id;
-	private $courses;
-	private $sessions;
-	private $path;
-	private $permission = array(DRH, SESSIONADMIN);
-
-	/**
-	 * Constructor
-	 */
+    private $courses;
+    private $sessions;
+    private $path;
+    private $permission = array(DRH, SESSIONADMIN);
+
+    /**
+     * Constructor
+     */
     public function __construct($user_id)
     {
-    	$this->path = 'block_evaluation_graph';
-    	$this->user_id 	= $user_id;
-    	$this->bg_width = 450;
-    	$this->bg_height = 350;
-    	if ($this->is_block_visible_for_user($user_id)) {
+        $this->path = 'block_evaluation_graph';
+        $this->user_id = $user_id;
+        $this->bg_width = 450;
+        $this->bg_height = 350;
+        if ($this->is_block_visible_for_user($user_id)) {
             if (!api_is_session_admin()) {
                 $this->courses = CourseManager::get_courses_followed_by_drh($user_id);
             }
             $this->sessions = SessionManager::get_sessions_followed_by_drh($user_id);
-    	}
+        }
     }
 
-	/**
-	 * This method check if a user is allowed to see the block inside dashboard interface
-	 * @param	int		User id
-	 * @return	bool	Is block visible for user
-	 */
+    /**
+     * This method check if a user is allowed to see the block inside dashboard interface
+     * @param int        User id
+     * @return bool    Is block visible for user
+     */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-		$user_status = $user_info['status'];
-		$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
     /**
-     * This method return content html containing information about sessions and its position for showing it inside dashboard interface
+     * This method return content html containing
+     * information about sessions and its position for showing it inside dashboard interface
      * it's important to use the name 'get_block' for beeing used from dashboard controller
      * @return array   column and content html
      */
     public function get_block()
     {
-		global $charset;
-    	$column = 1;
-    	$data   = array();
-
-		$evaluations_base_courses_graph         = $this->get_evaluations_base_courses_graph();
-		$evaluations_courses_in_sessions_graph  = $this->get_evaluations_courses_in_sessions_graph();
+        global $charset;
+        $column = 1;
+        $data = array();
+        $evaluations_base_courses_graph = $this->get_evaluations_base_courses_graph();
+        $evaluations_courses_in_sessions_graph = $this->get_evaluations_courses_in_sessions_graph();
 
-		$html = '<div class="panel panel-default" id="intro">
+        $html = '<div class="panel panel-default" id="intro">
                     <div class="panel-heading">
                         '.get_lang('EvaluationsGraph').'
                         <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
@@ -105,43 +104,51 @@ class BlockEvaluationGraph extends Block
                 }
             }
         }
-		$html .= '</div>
-			     </div>';
+        $html .= '</div>
+                 </div>';
 
-    	$data['column'] = $column;
-    	$data['content_html'] = $html;
+        $data['column'] = $column;
+        $data['content_html'] = $html;
 
-    	return $data;
-	}
+        return $data;
+    }
 
     /**
- 	 * This method return a graph containing informations about evaluations
+     * This method return a graph containing informations about evaluations
      * inside base courses, it's used inside get_block method for showing
      * it inside dashboard interface
- 	 * @return string  img html
- 	 */
+     * @return string  img html
+     */
     public function get_evaluations_base_courses_graph()
     {
-		$graphs = array();
-		if (!empty($this->courses)) {
-			$courses_code = array_keys($this->courses);
-			foreach ($courses_code as $course_code) {
-				$cats = Category::load(null, null, $course_code, null, null, null, false);
-
-				if (isset($cats) && isset($cats[0])) {
-					$alleval = $cats[0]->get_evaluations(null, true, $course_code);
-					$alllinks = $cats[0]->get_links(null, true);
-					$users = GradebookUtils::get_all_users($alleval, $alllinks);
-					$datagen = new FlatViewDataGenerator($users, $alleval, $alllinks);
-					$evaluation_sumary = $datagen->get_evaluation_sumary_results();
-					if (!empty($evaluation_sumary)) {
-						$items = array_keys($evaluation_sumary);
-						$max = $min = $avg = array();
-						foreach ($evaluation_sumary as $evaluation) {
-							$max[] = $evaluation['max'];
+        $graphs = array();
+        if (!empty($this->courses)) {
+            $courses_code = array_keys($this->courses);
+            foreach ($courses_code as $course_code) {
+                $cats = Category::load(
+                    null,
+                    null,
+                    $course_code,
+                    null,
+                    null,
+                    null,
+                    false
+                );
+
+                if (isset($cats) && isset($cats[0])) {
+                    $alleval = $cats[0]->get_evaluations(null, true, $course_code);
+                    $alllinks = $cats[0]->get_links(null, true);
+                    $users = GradebookUtils::get_all_users($alleval, $alllinks);
+                    $datagen = new FlatViewDataGenerator($users, $alleval, $alllinks);
+                    $evaluation_sumary = $datagen->get_evaluation_sumary_results();
+                    if (!empty($evaluation_sumary)) {
+                        $items = array_keys($evaluation_sumary);
+                        $max = $min = $avg = array();
+                        foreach ($evaluation_sumary as $evaluation) {
+                            $max[] = $evaluation['max'];
                             $min[] = !empty($evaluation['min']) ? $evaluation['min'] : 0;
-							$avg[] = $evaluation['avg'];
-						}
+                            $avg[] = $evaluation['avg'];
+                        }
                         // Dataset definition
                         $dataSet = new pData();
                         $dataSet->addPoints($min, 'Serie3');
@@ -283,43 +290,43 @@ class BlockEvaluationGraph extends Block
                         if (!empty($imgPath)) {
                             $courses_graph[$course_code] = '<img src="'.$imgPath.'">';
                         }
-					}
-				}
-			} // end for
-		}
-    	return $graphs;
- 	}
-
-	/**
- 	 * This method return a graph containing information about evaluations
+                    }
+                }
+            } // end for
+        }
+        return $graphs;
+    }
+
+    /**
+     * This method return a graph containing information about evaluations
      * inside courses in sessions, it's used inside get_block method for
      * showing it inside dashboard interface
- 	 * @return string  img html
- 	 */
+     * @return string  img html
+     */
     public function get_evaluations_courses_in_sessions_graph()
     {
-		$graphs = array();
-		if (!empty($this->sessions)) {
-			$session_ids = array_keys($this->sessions);
-			foreach ($session_ids as $session_id) {
-				$courses_code = array_keys(Tracking::get_courses_list_from_session($session_id));
-				$courses_graph = array();
-				foreach ($courses_code as $course_code) {
-					$cats = Category::load(null, null, $course_code, null, null, $session_id);
-					if (isset($cats) && isset($cats[0])) {
-						$alleval = $cats[0]->get_evaluations(null, true, $course_code);
-						$alllinks = $cats[0]->get_links(null, true);
-						$users = GradebookUtils::get_all_users($alleval, $alllinks);
-						$datagen = new FlatViewDataGenerator($users, $alleval, $alllinks);
-						$evaluation_sumary = $datagen->get_evaluation_sumary_results();
-						if (!empty($evaluation_sumary)) {
-							$items = array_keys($evaluation_sumary);
-							$max = $min = $avg = array();
-							foreach ($evaluation_sumary as $evaluation) {
-								$max[] = $evaluation['max'];
-								$min[] = $evaluation['min'];
-								$avg[] = $evaluation['avg'];
-							}
+        $graphs = array();
+        if (!empty($this->sessions)) {
+            $session_ids = array_keys($this->sessions);
+            foreach ($session_ids as $session_id) {
+                $courses_code = array_keys(Tracking::get_courses_list_from_session($session_id));
+                $courses_graph = array();
+                foreach ($courses_code as $course_code) {
+                    $cats = Category::load(null, null, $course_code, null, null, $session_id);
+                    if (isset($cats) && isset($cats[0])) {
+                        $alleval = $cats[0]->get_evaluations(null, true, $course_code);
+                        $alllinks = $cats[0]->get_links(null, true);
+                        $users = GradebookUtils::get_all_users($alleval, $alllinks);
+                        $datagen = new FlatViewDataGenerator($users, $alleval, $alllinks);
+                        $evaluation_sumary = $datagen->get_evaluation_sumary_results();
+                        if (!empty($evaluation_sumary)) {
+                            $items = array_keys($evaluation_sumary);
+                            $max = $min = $avg = array();
+                            foreach ($evaluation_sumary as $evaluation) {
+                                $max[] = $evaluation['max'];
+                                $min[] = $evaluation['min'];
+                                $avg[] = $evaluation['avg'];
+                            }
                             // Dataset definition
                             $dataSet = new pData();
                             $dataSet->addPoints($min, 'Serie3');
@@ -331,16 +338,21 @@ class BlockEvaluationGraph extends Block
                             $dataSet->setSerieDescription('Serie2', get_lang('Avg'));
                             $dataSet->setSerieDescription('Serie3', get_lang('Min'));
                             $dataSet->setAbscissa('Labels');
-
                             $dataSet->setAbscissaName(get_lang('EvaluationName'));
-
                             $dataSet->normalize(100, '%');
-
                             $dataSet->loadPalette(api_get_path(SYS_CODE_PATH).'palettes/pchart/default.color', true);
 
                             // Cache definition
                             $cachePath = api_get_path(SYS_ARCHIVE_PATH);
-                            $myCache = new pCache(array('CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)));
+                            $myCache = new pCache(
+                                array(
+                                    'CacheFolder' => substr(
+                                        $cachePath,
+                                        0,
+                                        strlen($cachePath) - 1
+                                    ),
+                                )
+                            );
                             $chartHash = $myCache->getHash($dataSet);
                             if ($myCache->isInCache($chartHash)) {
                                 $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
@@ -455,14 +467,14 @@ class BlockEvaluationGraph extends Block
                             if (!empty($imgPath)) {
                                 $courses_graph[$course_code] = '<img src="'.$imgPath.'">';
                             }
-						}
-					}
-				}
-				if (!empty($courses_graph)) {
-					$graphs[$session_id] = $courses_graph;
-				}
-			}
-		}
-    	return $graphs;
- 	}
+                        }
+                    }
+                }
+                if (!empty($courses_graph)) {
+                    $graphs[$session_id] = $courses_graph;
+                }
+            }
+        }
+        return $graphs;
+    }
 }

+ 60 - 60
plugin/dashboard/block_global_info/block_global_info.class.php

@@ -40,84 +40,84 @@ class BlockGlobalInfo extends Block
 
     /**
      * This method check if a user is allowed to see the block inside dashboard interface
-     * @param	int		User id
-     * @return	bool	Is block visible for user
+     * @param int        User id
+     * @return bool    Is block visible for user
      */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-    	$user_status = $user_info['status'];
-    	$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
 
     /**
-     * This method return content html containing information about courses and its position for showing it inside dashboard interface
+     * This method return content html containing information
+     * about courses and its position for showing it inside dashboard interface
      * it's important to use the name 'get_block' for beeing used from dashboard controller
      * @return array   column and content html
      */
     public function get_block()
     {
-    	global $charset;
-    	$column = 2;
-    	$data   = array();
+        global $charset;
+        $column = 2;
+        $data = array();
         $content = $this->get_content_html();
-    	$html = '
-    	            <div class="panel panel-default" id="intro">
-    	                <div class="panel-heading">'.get_lang('GlobalPlatformInformation').'
-    	                    <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
-    	                    <em class="fa fa-times"></em>
-    	                    </a></div>
-    	                </div>
-    	                <div class="panel-body">
-    	                   '.$content.'
-    	                </div>
-    	            </div>
-    			';
-    	$data['column'] = $column;
-    	$data['content_html'] = $html;
+        $html = '<div class="panel panel-default" id="intro">
+                    <div class="panel-heading">'.get_lang('GlobalPlatformInformation').'
+                        <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
+                        <em class="fa fa-times"></em>
+                        </a></div>
+                    </div>
+                    <div class="panel-body">
+                       '.$content.'
+                    </div>
+                </div>
+                ';
+        $data['column'] = $column;
+        $data['content_html'] = $html;
 
-    	return $data;
+        return $data;
     }
 
     /**
- 	 * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
- 	 * @return string  content html
- 	 */
-     public function get_content_html()
-     {
-         $global_data = $this->get_global_information_data();
-         //$content = '<div style="margin:10px;">';
-         $content = '<h4>'.get_lang('GlobalPlatformInformation').'</h4>';
-         $data_table = null;
-         if (!empty($global_data)) {
-             $data_table = '<table class="table table-bordered" width="95%">';
-             $i = 1;
-             foreach ($global_data as $data) {
-                 if ($i % 2 == 0) {
-                     $class_tr = 'row_odd';
-                 } else {
-                     $class_tr = 'row_even';
-                 }
-                 $data_table .= '<tr class="'.$class_tr.'">';
-                 foreach ($data as $cell) {
-                     $data_table .= '<td align="right">'.$cell.'</td>';
-                 }
-                 $data_table .= '</tr>';
-                 $i++;
-             }
-             $data_table .= '</table>';
-         } else {
-             $data_table .= get_lang('ThereIsNoInformationAboutThePlatform');
-         }
-         $content .= $data_table;
-         //$content .= '</div>';
+     * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
+     * @return string  content html
+     */
+    public function get_content_html()
+    {
+        $global_data = $this->get_global_information_data();
+        $content = '<h4>'.get_lang('GlobalPlatformInformation').'</h4>';
+        $data_table = null;
+        if (!empty($global_data)) {
+            $data_table = '<table class="table table-bordered" width="95%">';
+            $i = 1;
+            foreach ($global_data as $data) {
+                if ($i % 2 == 0) {
+                    $class_tr = 'row_odd';
+                } else {
+                    $class_tr = 'row_even';
+                }
+                $data_table .= '<tr class="'.$class_tr.'">';
+                foreach ($data as $cell) {
+                    $data_table .= '<td align="right">'.$cell.'</td>';
+                }
+                $data_table .= '</tr>';
+                $i++;
+            }
+            $data_table .= '</table>';
+        } else {
+            $data_table .= get_lang('ThereIsNoInformationAboutThePlatform');
+        }
+        $content .= $data_table;
+
+        //$content .= '</div>';
 
-         return $content;
+        return $content;
     }
 
     /**

+ 102 - 110
plugin/dashboard/block_session/block_session.class.php

@@ -15,101 +15,92 @@
 class BlockSession extends Block
 {
     private $user_id;
-	private $sessions;
-	private $path;
-	private $permission = array(DRH, SESSIONADMIN);
+    private $sessions;
+    private $path;
+    private $permission = array(DRH, SESSIONADMIN);
 
-	/**
-	 * Constructor
-	 */
+    /**
+     * Constructor
+     */
     public function __construct($user_id)
     {
-    	$this->user_id = $user_id;
-    	$this->path = 'block_session';
-    	if ($this->is_block_visible_for_user($user_id)) {
+        $this->user_id = $user_id;
+        $this->path = 'block_session';
+        if ($this->is_block_visible_for_user($user_id)) {
             $this->sessions = SessionManager::get_sessions_followed_by_drh($user_id);
-    	}
+        }
     }
 
-	/**
-	 * This method check if a user is allowed to see the block inside dashboard interface
-	 * @param	int		User id
-	 * @return	bool	Is block visible for user
-	 */
+    /**
+     * This method check if a user is allowed to see the block inside dashboard interface
+     * @param int        User id
+     * @return bool    Is block visible for user
+     */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-		$user_status = $user_info['status'];
-		$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
     /**
-     * This method return content html containing information about sessions and its position for showing it inside dashboard interface
+     * This method return content html containing
+     * information about sessions and its position for showing it inside dashboard interface
      * it's important to use the name 'get_block' for beeing used from dashboard controller
      * @return array   column and content html
      */
     public function get_block()
     {
-
-		global $charset;
-
-    	$column = 2;
-    	$data   = array();
-
-		$content = $this->get_content_html();
-
-		$content_html = '
-			            <div class="panel panel-default" id="intro">
-			                <div class="panel-heading">
-			                    '.get_lang('SessionsInformation').'
-			                    <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
-			                    <em class="fa fa-times"></em>
-			                    </a></div>
-			                </div>
-			                <div class="panel-body">
-							'.$content.'
-			                </div>
-			            </div>
-				';
-
-    	$data['column'] = $column;
-    	$data['content_html'] = $content_html;
-
-    	return $data;
+        global $charset;
+        $column = 2;
+        $data = array();
+        $content = $this->get_content_html();
+        $content_html = '<div class="panel panel-default" id="intro">
+                            <div class="panel-heading">
+                                '.get_lang('SessionsInformation').'
+                                <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
+                                <em class="fa fa-times"></em>
+                                </a></div>
+                            </div>
+                            <div class="panel-body">
+                            '.$content.'
+                            </div>
+                        </div>
+                ';
+
+        $data['column'] = $column;
+        $data['content_html'] = $content_html;
+
+        return $data;
     }
 
     /**
- 	 * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
- 	 * @return string  content html
- 	 */
+     * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
+     * @return string  content html
+     */
     public function get_content_html()
     {
-
- 		$content = '';
-		$sessions = $this->sessions;
-
-		//$content = '<div style="margin:10px;">';
-		$content .= '<h4>'.get_lang('YourSessionsList').'</h4>';
-
-		if (count($sessions) > 0) {
-			$sessions_table = '<table class="data_table" width:"95%">';
- 			$sessions_table .= '<tr>
-									<th >'.get_lang('Title').'</th>
-									<th >'.get_lang('Date').'</th>
-									<th width="100px">'.get_lang('NbCoursesPerSession').'</th>
-								</tr>';
-			$i = 1;
-			foreach ($sessions as $session) {
-
-				$session_id = intval($session['id']);
-				$title = $session['name'];
-
-				if (!empty($session['access_start_date'])) {
-				    $dateFrom = api_convert_and_format_date(
+        $content = '';
+        $sessions = $this->sessions;
+        $content .= '<h4>'.get_lang('YourSessionsList').'</h4>';
+        if (count($sessions) > 0) {
+            $sessions_table = '<table class="data_table" width:"95%">';
+            $sessions_table .= '<tr>
+                                    <th >'.get_lang('Title').'</th>
+                                    <th >'.get_lang('Date').'</th>
+                                    <th width="100px">'.get_lang('NbCoursesPerSession').'</th>
+                                </tr>';
+            $i = 1;
+            foreach ($sessions as $session) {
+                $session_id = intval($session['id']);
+                $title = $session['name'];
+
+                if (!empty($session['access_start_date'])) {
+                    $dateFrom = api_convert_and_format_date(
                         $session['access_start_date'],
                         DATE_FORMAT_SHORT,
                         date_default_timezone_get()
@@ -121,44 +112,45 @@ class BlockSession extends Block
                     );
 
                     $date = vsprintf(get_lang('FromDateXToDateY'), [$dateFrom, $dateUntil]);
-				} else {
-					$date = ' - ';
-				}
-
-	 			$count_courses_in_session = count(Tracking::get_courses_list_from_session($session_id));
-
-				if ($i % 2 == 0) $class_tr = 'row_odd';
-	    		else $class_tr = 'row_even';
-
-				$sessions_table .= '<tr class="'.$class_tr.'">
-										<td>'.$title.'</td>
-										<td align="center">'.$date.'</td>
-										<td align="center">'.$count_courses_in_session.'</td>
-								   </tr>';
-				$i++;
-			}
-			$sessions_table .= '</table>';
-			$content .= $sessions_table;
-		} else {
-			$content .= get_lang('ThereIsNoInformationAboutYourSessions');
-		}
-
-		if (count($sessions) > 0) {
-			$content .= '<div style="text-align:right;margin-top:10px;"><a href="'.api_get_path(WEB_CODE_PATH).'mySpace/session.php">'.get_lang('SeeMore').'</a></div>';
-		}
-
-		//$content .= '</div>';
-
- 		return $content;
- 	}
+                } else {
+                    $date = ' - ';
+                }
+
+                $count_courses_in_session = count(Tracking::get_courses_list_from_session($session_id));
+
+                if ($i % 2 == 0) {
+                    $class_tr = 'row_odd';
+                } else {
+                    $class_tr = 'row_even';
+                }
+
+                $sessions_table .= '<tr class="'.$class_tr.'">
+                                        <td>'.$title.'</td>
+                                        <td align="center">'.$date.'</td>
+                                        <td align="center">'.$count_courses_in_session.'</td>
+                                   </tr>';
+                $i++;
+            }
+            $sessions_table .= '</table>';
+            $content .= $sessions_table;
+        } else {
+            $content .= get_lang('ThereIsNoInformationAboutYourSessions');
+        }
+
+        if (count($sessions) > 0) {
+            $content .= '<div style="text-align:right;margin-top:10px;"><a href="'.api_get_path(WEB_CODE_PATH).'mySpace/session.php">'.get_lang('SeeMore').'</a></div>';
+        }
+
+        return $content;
+    }
 
     /**
-	 * Get number of sessions
-	 * @return int
-	 */
-	function get_number_of_sessions()
+     * Get number of sessions
+     * @return int
+     */
+    function get_number_of_sessions()
     {
-		return count($this->sessions);
-	}
+        return count($this->sessions);
+    }
 
 }

+ 183 - 175
plugin/dashboard/block_student/block_student.class.php

@@ -8,57 +8,58 @@
 
 /**
  * This class is used like controller for student block plugin,
- * the class name must be registered inside path.info file (e.g: controller = "BlockStudent"), so dashboard controller will be instantiate it
+ * the class name must be registered inside path.info file
+ * (e.g: controller = "BlockStudent"), so dashboard controller will be instantiate it
  * @package chamilo.dashboard
  */
 class BlockStudent extends Block
 {
     private $user_id;
-	private $students;
-	private $path;
-	private $permission = array(DRH);
+    private $students;
+    private $path;
+    private $permission = array(DRH);
 
-	/**
-	 * Constructor
-	 */
+    /**
+     * Constructor
+     */
     public function __construct($user_id)
     {
-    	$this->user_id = $user_id;
-    	$this->path = 'block_student';
-    	if ($this->is_block_visible_for_user($user_id)) {
+        $this->user_id = $user_id;
+        $this->path = 'block_student';
+        if ($this->is_block_visible_for_user($user_id)) {
             $this->students = UserManager::get_users_followed_by_drh($user_id, STUDENT);
-    	}
+        }
     }
 
     /**
-	 * This method check if a user is allowed to see the block inside dashboard interface
-	 * @param	int		User id
-	 * @return	bool	Is block visible for user
-	 */
+     * This method check if a user is allowed to see the block inside dashboard interface
+     * @param int        User id
+     * @return bool    Is block visible for user
+     */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-		$user_status = $user_info['status'];
-		$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
     /**
-     * This method return content html containing information about students and its position for showing it inside dashboard interface
+     * This method return content html containing information
+     * about students and its position for showing it inside dashboard interface
      * it's important to use the name 'get_block' for beeing used from dashboard controller
      * @return array   column and content html
      */
     public function get_block()
     {
-    	global $charset;
-    	$column = 1;
-    	$data   = array();
-
+        global $charset;
+        $column = 1;
+        $data = array();
         $student_content_html = $this->get_students_content_html_for_drh();
-		$html = '<div class="panel panel-default" id="intro">
+        $html = '<div class="panel panel-default" id="intro">
                     <div class="panel-heading">
                         '.get_lang('StudentsInformationsList').'
                         <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
@@ -70,173 +71,180 @@ class BlockStudent extends Block
                         '.$student_content_html.'
                     </div>
                     </div>';
-    	$data['column'] = $column;
-    	$data['content_html'] = $html;
-    	return $data;
+        $data['column'] = $column;
+        $data['content_html'] = $html;
+
+        return $data;
     }
 
     /**
- 	 * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
- 	 * @return string  content html
- 	 */
+     * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
+     * @return string  content html
+     */
     public function get_students_content_html_for_platform_admin()
     {
- 		$students = $this->students;
- 		//$content = '<div style="margin:10px;">';
- 		$content = '<h4>'.get_lang('YourStudents').'</h4>';
+        $students = $this->students;
+        $content = '<h4>'.get_lang('YourStudents').'</h4>';
         $students_table = null;
- 		if (count($students) > 0) {
-	 		$students_table .= '<table class="data_table">';
-	 		$students_table .= '<tr>
-									<th width="10%" rowspan="2">'.get_lang('FirstName').'</th>
-									<th width="10%" rowspan="2">'.get_lang('LastName').'</th>
-									<th width="30%" colspan="2">'.get_lang('CourseInformation').'</th>
-								</tr>
-								<tr>
-									<th width="10%">'.get_lang('Courses').'</th>
-									<th width="10%">'.get_lang('Time').'</th>
-								</tr>';
-
-	 		$i = 1;
-	 		foreach ($students as $student) {
-
-	 			$courses_by_user = CourseManager::get_courses_list_by_user_id($student['user_id'], true);
-	 			$count_courses = count($courses_by_user);
-				$rowspan = $count_courses ? $count_courses + 1 : 2;
-
-				if ($i % 2 == 0) {
+        if (count($students) > 0) {
+            $students_table .= '<table class="data_table">';
+            $students_table .= '<tr>
+                                    <th width="10%" rowspan="2">'.get_lang('FirstName').'</th>
+                                    <th width="10%" rowspan="2">'.get_lang('LastName').'</th>
+                                    <th width="30%" colspan="2">'.get_lang('CourseInformation').'</th>
+                                </tr>
+                                <tr>
+                                    <th width="10%">'.get_lang('Courses').'</th>
+                                    <th width="10%">'.get_lang('Time').'</th>
+                                </tr>';
+
+            $i = 1;
+            foreach ($students as $student) {
+                $courses_by_user = CourseManager::get_courses_list_by_user_id($student['user_id'], true);
+                $count_courses = count($courses_by_user);
+                $rowspan = $count_courses ? $count_courses + 1 : 2;
+
+                if ($i % 2 == 0) {
                     $style = ' style="background-color:#F2F2F2" ';
                 } else {
                     $style = ' style="background-color:#FFF" ';
                 }
 
-	 			$students_table .= '<tr '.$style.'>
-										<td rowspan="'.$rowspan.'">'.$student['firstname'].'</td>
-										<td rowspan="'.$rowspan.'">'.$student['lastname'].'</td>
-									</tr>';
-
-	 			// courses information about the student
-	 			if (!empty($courses_by_user)) {
-		 			foreach ($courses_by_user as $course) {
-		 				$course_code = $course['code'];
-						$courseInfo = api_get_course_info($course_code);
-						$courseId = $courseInfo['real_id'];
-		 				$course_title = $course['title'];
-		 				$time = api_time_to_hms(Tracking::get_time_spent_on_the_course($student['user_id'], $courseId));
-		 				$students_table .= '<tr '.$style.'>
-											<td align="right">'.$course_title.'</td>
-											<td align="right">'.$time.'</td>
-											</tr>';
-		 			}
-	 			} else {
-	 				$students_table .= '<tr '.$style.'>
-											<td align="center" colspan="2"><i>'.get_lang('Empty').'</i></td>
-										</tr>';
-	 			}
-	 			$i++;
-	 		}
-	 		$students_table .= '</table>';
- 		} else {
- 			$students_table .= get_lang('ThereIsNoInformationAboutYourStudents');
- 		}
-
- 		$content .= $students_table;
-
- 		if (count($students) > 0) {
-			$content .= '<div style="text-align:right;margin-top:10px;"><a href="'.api_get_path(WEB_CODE_PATH).'mySpace/index.php?view=admin&display=useroverview">'.get_lang('SeeMore').'</a></div>';
-		}
-		//$content .= '</div>';
-
- 		return $content;
- 	}
-
-  	public function get_students_content_html_for_drh() {
-  		$attendance = new Attendance();
-  		$students = $this->students;
- 		//$content = '<div style="margin:5px;">';
- 		$content = '<h4>'.get_lang('YourStudents').'</h4>';
+                $students_table .= '<tr '.$style.'>
+                                        <td rowspan="'.$rowspan.'">'.$student['firstname'].'</td>
+                                        <td rowspan="'.$rowspan.'">'.$student['lastname'].'</td>
+                                    </tr>';
+
+                // courses information about the student
+                if (!empty($courses_by_user)) {
+                    foreach ($courses_by_user as $course) {
+                        $course_code = $course['code'];
+                        $courseInfo = api_get_course_info($course_code);
+                        $courseId = $courseInfo['real_id'];
+                        $course_title = $course['title'];
+                        $time = api_time_to_hms(Tracking::get_time_spent_on_the_course($student['user_id'], $courseId));
+                        $students_table .= '<tr '.$style.'>
+                                            <td align="right">'.$course_title.'</td>
+                                            <td align="right">'.$time.'</td>
+                                            </tr>';
+                    }
+                } else {
+                    $students_table .= '<tr '.$style.'>
+                                            <td align="center" colspan="2"><i>'.get_lang('Empty').'</i></td>
+                                        </tr>';
+                }
+                $i++;
+            }
+            $students_table .= '</table>';
+        } else {
+            $students_table .= get_lang('ThereIsNoInformationAboutYourStudents');
+        }
+
+        $content .= $students_table;
+
+        if (count($students) > 0) {
+            $content .= '<div style="text-align:right;margin-top:10px;"><a href="'.api_get_path(WEB_CODE_PATH).'mySpace/index.php?view=admin&display=useroverview">'.get_lang('SeeMore').'</a></div>';
+        }
+        //$content .= '</div>';
+
+        return $content;
+    }
+
+    /**
+     * @return string
+     */
+    public function get_students_content_html_for_drh()
+    {
+        $attendance = new Attendance();
+        $students = $this->students;
+        $content = '<h4>'.get_lang('YourStudents').'</h4>';
         $students_table = null;
- 		if (count($students) > 0) {
-	 		$students_table .= '<table class="data_table">';
-	 		$students_table .= '<tr>
-									<th>'.get_lang('User').'</th>
-									<th>'.get_lang('AttendancesFaults').'</th>
-									<th>'.get_lang('Evaluations').'</th>
-								</tr>';
-	 		$i = 1;
-	 		foreach ($students as $student) {
-
-	 			$student_id = $student['user_id'];
-	 			$firstname  = $student['firstname'];
-	 			$lastname   = $student['lastname'];
-	 			$username = $student['username'];
-
-
-				// get average of faults in attendances by student
-	 			$results_faults_avg = $attendance->get_faults_average_inside_courses($student_id);
-	 			if (!empty($results_faults_avg)) {
-	 				$attendances_faults_avg = '<a title="'.get_lang('GoToStudentDetails').'" href="'.api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$student_id.'">'.$results_faults_avg['faults'].'/'.$results_faults_avg['total'].' ('.$results_faults_avg['porcent'].'%)</a>';
-	 			} else {
-	 				$attendances_faults_avg = '0%';
-	 			}
-
-	 			$courses_by_user = CourseManager::get_courses_list_by_user_id($student_id, true);
-				$evaluations_avg  = 0;
-				$score = $weight = 0;
-	 			foreach ($courses_by_user as $course) {
-	 				$course_code = $course['code'];
-	 				$cats = Category::load(null, null, $course_code, null, null, null, false);
-	 				$scoretotal = array();
-	 				if (isset($cats) && isset($cats[0])) {
-		 				$scoretotal = $cats[0]->calc_score($student_id, null, $course_code);
-	 				}
-
-	 				if (!empty($scoretotal)) {
-	 					$score  += $scoretotal[0];
-	 					$weight += $scoretotal[1];
-	 				}
-	 			}
-
-	 			if (!empty($weight)) {
-	 				$evaluations_avg = '<a title="'.get_lang('GoToStudentDetails').'" href="'.api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$student_id.'">'.round($score, 2).'/'.round($weight, 2).'('.round(($score / $weight) * 100, 2).' %)</a>';
-	 			}
-
-	 			if ($i % 2 == 0) {
+        if (count($students) > 0) {
+            $students_table .= '<table class="data_table">';
+            $students_table .= '<tr>
+                                    <th>'.get_lang('User').'</th>
+                                    <th>'.get_lang('AttendancesFaults').'</th>
+                                    <th>'.get_lang('Evaluations').'</th>
+                                </tr>';
+            $i = 1;
+            foreach ($students as $student) {
+                $student_id = $student['user_id'];
+                $firstname  = $student['firstname'];
+                $lastname   = $student['lastname'];
+                $username = $student['username'];
+                // get average of faults in attendances by student
+                $results_faults_avg = $attendance->get_faults_average_inside_courses($student_id);
+                if (!empty($results_faults_avg)) {
+                    $attendances_faults_avg = '<a title="'.get_lang('GoToStudentDetails').'" href="'.api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$student_id.'">'.$results_faults_avg['faults'].'/'.$results_faults_avg['total'].' ('.$results_faults_avg['porcent'].'%)</a>';
+                } else {
+                    $attendances_faults_avg = '0%';
+                }
+
+                $courses_by_user = CourseManager::get_courses_list_by_user_id($student_id, true);
+                $evaluations_avg  = 0;
+                $score = $weight = 0;
+                foreach ($courses_by_user as $course) {
+                    $course_code = $course['code'];
+                    $cats = Category::load(
+                        null,
+                        null,
+                        $course_code,
+                        null,
+                        null,
+                        null,
+                        false
+                    );
+                    $scoretotal = array();
+                    if (isset($cats) && isset($cats[0])) {
+                        $scoretotal = $cats[0]->calc_score($student_id, null, $course_code);
+                    }
+
+                    if (!empty($scoretotal)) {
+                        $score  += $scoretotal[0];
+                        $weight += $scoretotal[1];
+                    }
+                }
+
+                if (!empty($weight)) {
+                    $evaluations_avg = '<a title="'.get_lang('GoToStudentDetails').'" href="'.api_get_path(WEB_CODE_PATH).'mySpace/myStudents.php?student='.$student_id.'">'.round($score, 2).'/'.round($weight, 2).'('.round(($score / $weight) * 100, 2).' %)</a>';
+                }
+
+                if ($i % 2 == 0) {
                     $class_tr = 'row_odd';
                 } else {
                     $class_tr = 'row_even';
                 }
-	    		$students_table .= '<tr class="'.$class_tr.'">
-										<td>'.api_get_person_name($firstname, $lastname).' ('.$username.')</td>
-										<td>'.$attendances_faults_avg.'</td>
-										<td>'.$evaluations_avg.'</td>
-									</tr>';
-
-	 			$i++;
-	 		}
-	 		$students_table .= '</table>';
- 		} else {
- 			$students_table .= get_lang('ThereIsNoInformationAboutYourStudents');
- 		}
-
-  		$content .= $students_table;
-
- 		if (count($students) > 0) {
-			$content .= '<div style="text-align:right;margin-top:10px;">
+                $students_table .= '<tr class="'.$class_tr.'">
+                                        <td>'.api_get_person_name($firstname, $lastname).' ('.$username.')</td>
+                                        <td>'.$attendances_faults_avg.'</td>
+                                        <td>'.$evaluations_avg.'</td>
+                                    </tr>';
+
+                $i++;
+            }
+            $students_table .= '</table>';
+        } else {
+            $students_table .= get_lang('ThereIsNoInformationAboutYourStudents');
+        }
+
+        $content .= $students_table;
+
+        if (count($students) > 0) {
+            $content .= '<div style="text-align:right;margin-top:10px;">
                             <a href="'.api_get_path(WEB_CODE_PATH).'mySpace/index.php?view=admin&display=yourstudents">'.get_lang('SeeMore').'</a>
                          </div>';
-		}
-		//$content .= '</div>';
+        }
+        //$content .= '</div>';
 
-  		return $content;
-  	}
+        return $content;
+    }
 
     /**
-	 * Get number of students
-	 * @return int
-	 */
-	function get_number_of_students()
+     * Get number of students
+     * @return int
+     */
+    function get_number_of_students()
     {
-		return count($this->students);
-	}
+        return count($this->students);
+    }
 }

+ 99 - 96
plugin/dashboard/block_student_graph/block_student_graph.class.php

@@ -15,47 +15,47 @@ use CpChart\Chart\Cache as pCache;
 
 /**
  * This class is used like controller for student graph block plugin,
- * the class name must be registered inside path.info file (e.g: controller = "BlockStudentGraph"), so dashboard controller will be instantiate it
+ * the class name must be registered inside path.info file
+ * (e.g: controller = "BlockStudentGraph"), so dashboard controller will be instantiate it
  * @package chamilo.dashboard
  */
 class BlockStudentGraph extends Block
 {
-
     private $user_id;
-	private $students;
-	private $path;
-	private $permission = array(DRH);
+    private $students;
+    private $path;
+    private $permission = array(DRH);
 
-	/**
-	 * Constructor
-	 */
+    /**
+     * Constructor
+     */
     public function __construct($user_id)
     {
-    	$this->user_id = $user_id;
-    	$this->path = 'block_student_graph';
-    	if ($this->is_block_visible_for_user($user_id)) {
-    		/*if (api_is_platform_admin()) {
-	    		$this->students = UserManager::get_user_list(array('status' => STUDENT));
-	    	} else if (api_is_drh()) {*/
-	    		$this->students = UserManager::get_users_followed_by_drh($user_id, STUDENT);
-	    	//}
-    	}
+        $this->user_id = $user_id;
+        $this->path = 'block_student_graph';
+        if ($this->is_block_visible_for_user($user_id)) {
+            /*if (api_is_platform_admin()) {
+                $this->students = UserManager::get_user_list(array('status' => STUDENT));
+            } else if (api_is_drh()) {*/
+                $this->students = UserManager::get_users_followed_by_drh($user_id, STUDENT);
+            //}
+        }
     }
 
     /**
-	 * This method check if a user is allowed to see the block inside dashboard interface
-	 * @param	int		User id
-	 * @return	bool	Is block visible for user
-	 */
+     * This method check if a user is allowed to see the block inside dashboard interface
+     * @param int        User id
+     * @return bool    Is block visible for user
+     */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-		$user_status = $user_info['status'];
-		$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
     /**
@@ -66,70 +66,66 @@ class BlockStudentGraph extends Block
      */
     public function get_block()
     {
-
-    	global $charset;
-    	$column = 1;
-    	$data   = array();
-		$students_attendance_graph = $this->get_students_attendance_graph();
-
-		$html = '<div class="panel panel-default" id="intro">
-	                <div class="panel-heading">
-	                    '.get_lang('StudentsInformationsGraph').'
-	                    <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
-	                    <em class="fa fa-times"></em>
-	                    </a></div>
-	                </div>
-	                <div class="panel-body" align="center">
-	                	<div style="padding:10px;"><strong>'.get_lang('AttendancesFaults').'</strong></div>
-						'.$students_attendance_graph.'
-	                </div>
-	            </div>';
-    	$data['column'] = $column;
-    	$data['content_html'] = $html;
-    	return $data;
+        global $charset;
+        $column = 1;
+        $data = array();
+        $students_attendance_graph = $this->get_students_attendance_graph();
+
+        $html = '<div class="panel panel-default" id="intro">
+                    <div class="panel-heading">
+                        '.get_lang('StudentsInformationsGraph').'
+                        <div class="pull-right"><a class="btn btn-danger btn-xs" onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
+                        <em class="fa fa-times"></em>
+                        </a></div>
+                    </div>
+                    <div class="panel-body" align="center">
+                        <div style="padding:10px;"><strong>'.get_lang('AttendancesFaults').'</strong></div>
+                        '.$students_attendance_graph.'
+                    </div>
+                </div>';
+        $data['column'] = $column;
+        $data['content_html'] = $html;
+        return $data;
     }
 
     /**
- 	 * This method return a graph containing information about students evaluation,
+     * This method return a graph containing information about students evaluation,
      * it's used inside get_block method for showing it inside dashboard interface
- 	 * @return string  img html
- 	 */
+     * @return string  img html
+     */
     public function get_students_attendance_graph()
     {
+        $students = $this->students;
+        $attendance = new Attendance();
+
+        // get data
+        $attendances_faults_avg = array();
+        if (is_array($students) && count($students) > 0) {
+            foreach ($students as $student) {
+                $student_id = $student['user_id'];
+                //$student_info = api_get_user_info($student_id);
+                // get average of faults in attendances by student
+                $results_faults_avg = $attendance->get_faults_average_inside_courses($student_id);
+
+                if (!empty($results_faults_avg)) {
+                    $attendances_faults_avg[$student['lastname']] = $results_faults_avg['porcent'];
+                } else {
+                    $attendances_faults_avg[$student['lastname']] = 0;
+                }
+            }
+        }
 
-		$students = $this->students;
- 		$attendance = new Attendance();
-
- 		// get data
- 		$attendances_faults_avg = array();
- 		if (is_array($students) && count($students) > 0) {
-	 		foreach ($students as $student) {
-	 			$student_id = $student['user_id'];
-	 			//$student_info = api_get_user_info($student_id);
-				// get average of faults in attendances by student
-	 			$results_faults_avg = $attendance->get_faults_average_inside_courses($student_id);
-
-	 			if (!empty($results_faults_avg)) {
-	 				$attendances_faults_avg[$student['lastname']] = $results_faults_avg['porcent'];
-	 			} else {
-	 				$attendances_faults_avg[$student['lastname']] = 0;
-	 			}
-	 		}
- 		}
-
- 		arsort($attendances_faults_avg);
-		$usernames = array_keys($attendances_faults_avg);
-
-		$faults = array();
-		foreach ($usernames as $username) {
-			$faults[] = $attendances_faults_avg[$username];
-		}
-
-		$graph = '';
-		$img_file = '';
+        arsort($attendances_faults_avg);
+        $usernames = array_keys($attendances_faults_avg);
 
-		if (is_array($usernames) && count($usernames) > 0) {
+        $faults = array();
+        foreach ($usernames as $username) {
+            $faults[] = $attendances_faults_avg[$username];
+        }
 
+        $graph = '';
+        $img_file = '';
+        if (is_array($usernames) && count($usernames) > 0) {
             // Defining data
             $dataSet = new pData();
             $dataSet->addPoints($faults, 'Serie1');
@@ -152,14 +148,21 @@ class BlockStudentGraph extends Block
             );
             // Cache definition
             $cachePath = api_get_path(SYS_ARCHIVE_PATH);
-            $myCache = new pCache(array('CacheFolder' => substr($cachePath, 0, strlen($cachePath) - 1)));
+            $myCache = new pCache(
+                array(
+                    'CacheFolder' => substr(
+                        $cachePath,
+                        0,
+                        strlen($cachePath) - 1
+                    ),
+                )
+            );
             $chartHash = $myCache->getHash($dataSet);
             if ($myCache->isInCache($chartHash)) {
                 $imgPath = api_get_path(SYS_ARCHIVE_PATH).$chartHash;
                 $myCache->saveFromCache($chartHash, $imgPath);
                 $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
             } else {
-
                 $maxCounts = max(count($usernames), count($faults));
                 if ($maxCounts < 5) {
                     $heightSize = 200;
@@ -233,19 +236,19 @@ class BlockStudentGraph extends Block
                 $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
             }
             $graph = '<img src="'.$imgPath.'" >';
-		} else {
-			$graph = '<p>'.api_convert_encoding(get_lang('GraphicNotAvailable'), 'UTF-8').'</p>';
-		}
+        } else {
+            $graph = '<p>'.api_convert_encoding(get_lang('GraphicNotAvailable'), 'UTF-8').'</p>';
+        }
 
- 		return $graph;
- 	}
+        return $graph;
+    }
 
     /**
-	 * Get number of students
-	 * @return int
-	 */
-	function get_number_of_students()
+     * Get number of students
+     * @return int
+     */
+    function get_number_of_students()
     {
-		return count($this->students);
-	}
+        return count($this->students);
+    }
 }

+ 0 - 3
plugin/dashboard/block_teacher/block_teacher.class.php

@@ -62,7 +62,6 @@ class BlockTeacher extends Block
         $column = 1;
         $data = array();
         $teacher_content_html = $this->get_teachers_content_html_for_drh();
-
         $html = '
                 <div class="panel panel-default" id="intro">
                     <div class="panel-heading">
@@ -139,8 +138,6 @@ class BlockTeacher extends Block
             <a href="'.api_get_path(WEB_CODE_PATH).'mySpace/index.php?view=admin">'.get_lang('SeeMore').'</a></div>';
         }
 
-        //$content .= '</div>';
-
         return $content;
     }
 

+ 79 - 78
plugin/dashboard/block_teacher_graph/block_teacher_graph.class.php

@@ -26,32 +26,32 @@ class BlockTeacherGraph extends Block
     private $path;
     private $permission = array(DRH);
 
-	/**
-	 * Controller
-	 */
+    /**
+     * Controller
+     */
     public function __construct($user_id)
     {
-    	$this->user_id = $user_id;
-    	$this->path = 'block_teacher_graph';
-    	if ($this->is_block_visible_for_user($user_id)) {
+        $this->user_id = $user_id;
+        $this->path = 'block_teacher_graph';
+        if ($this->is_block_visible_for_user($user_id)) {
             $this->teachers = UserManager::get_users_followed_by_drh($user_id, COURSEMANAGER);
-    	}
+        }
     }
 
     /**
-	 * This method check if a user is allowed to see the block inside dashboard interface
-	 * @param	int		User id
-	 * @return	bool	Is block visible for user
-	 */
+     * This method check if a user is allowed to see the block inside dashboard interface
+     * @param int        User id
+     * @return bool    Is block visible for user
+     */
     public function is_block_visible_for_user($user_id)
     {
-    	$user_info = api_get_user_info($user_id);
-		$user_status = $user_info['status'];
-		$is_block_visible_for_user = false;
-    	if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
-    		$is_block_visible_for_user = true;
-    	}
-    	return $is_block_visible_for_user;
+        $user_info = api_get_user_info($user_id);
+        $user_status = $user_info['status'];
+        $is_block_visible_for_user = false;
+        if (UserManager::is_admin($user_id) || in_array($user_status, $this->permission)) {
+            $is_block_visible_for_user = true;
+        }
+        return $is_block_visible_for_user;
     }
 
     /**
@@ -61,11 +61,11 @@ class BlockTeacherGraph extends Block
      */
     public function get_block()
     {
-    	global $charset;
-    	$column = 1;
-    	$data   = array();
-		$teacher_information_graph = $this->get_teachers_information_graph();
-		$html = '
+        global $charset;
+        $column = 1;
+        $data   = array();
+        $teacher_information_graph = $this->get_teachers_information_graph();
+        $html = '
                 <div class="panel panel-default" id="intro">
                     <div class="panel-heading">'.get_lang('TeachersInformationsGraph').'
                         <div class="pull-right"><a class="btn btn-danger btn-xs"  onclick="javascript:if(!confirm(\''.addslashes(api_htmlentities(get_lang('ConfirmYourChoice'), ENT_QUOTES, $charset)).'\')) return false;" href="index.php?action=disable_block&path='.$this->path.'">
@@ -77,55 +77,58 @@ class BlockTeacherGraph extends Block
                         '.$teacher_information_graph.'
                     </div>
                 </div>
-				';
-
-    	$data['column'] = $column;
-    	$data['content_html'] = $html;
+                ';
 
-    	return $data;
+        $data['column'] = $column;
+        $data['content_html'] = $html;
 
+        return $data;
     }
 
     /**
- 	 * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
- 	 * @return string  content html
- 	 */
+     * This method return a content html, it's used inside get_block method for showing it inside dashboard interface
+     * @return string  content html
+     */
     public function get_teachers_information_graph()
     {
-	 	$teachers = $this->teachers;
-		$graph = '';
-
- 		$user_ids = array_keys($teachers);
- 		$a_last_week = get_last_week();
-
-		if (is_array($user_ids) && count($user_ids) > 0) {
-			$dataSet = new pData;
-			foreach ($user_ids as $user_id) {
-				$teacher_info = api_get_user_info($user_id);
-				$username = $teacher_info['username'];
-				$time_by_days = array();
-				foreach ($a_last_week as $day) {
-					// day is received as y-m-d 12:00:00
-					$start_date = api_get_utc_datetime($day);
-					$end_date = api_get_utc_datetime($day + (3600 * 24 - 1));
-
-					$time_on_platform_by_day = Tracking::get_time_spent_on_the_platform($user_id, 'custom', $start_date, $end_date);
-					$hours = floor($time_on_platform_by_day / 3600);
-					$min = floor(($time_on_platform_by_day - ($hours * 3600)) / 60);
-					$time_by_days[] = $min;
-				}
-				$dataSet->addPoints($time_by_days, $username);
-			}
-
-			$last_week = date('Y-m-d', $a_last_week[0]).' '.get_lang('To').' '.date('Y-m-d', $a_last_week[6]);
-			$days_on_week = array();
-			foreach ($a_last_week as $weekday) {
-				$days_on_week[] = date('d/m', $weekday);
-			}
-
-			$dataSet->addPoints($days_on_week, 'Days');
-			$dataSet->setAbscissaName($last_week);
-			$dataSet->setAxisName(0, get_lang('Minutes'));
+        $teachers = $this->teachers;
+        $graph = '';
+        $user_ids = array_keys($teachers);
+        $a_last_week = get_last_week();
+
+        if (is_array($user_ids) && count($user_ids) > 0) {
+            $dataSet = new pData;
+            foreach ($user_ids as $user_id) {
+                $teacher_info = api_get_user_info($user_id);
+                $username = $teacher_info['username'];
+                $time_by_days = array();
+                foreach ($a_last_week as $day) {
+                    // day is received as y-m-d 12:00:00
+                    $start_date = api_get_utc_datetime($day);
+                    $end_date = api_get_utc_datetime($day + (3600 * 24 - 1));
+
+                    $time_on_platform_by_day = Tracking::get_time_spent_on_the_platform(
+                        $user_id,
+                        'custom',
+                        $start_date,
+                        $end_date
+                    );
+                    $hours = floor($time_on_platform_by_day / 3600);
+                    $min = floor(($time_on_platform_by_day - ($hours * 3600)) / 60);
+                    $time_by_days[] = $min;
+                }
+                $dataSet->addPoints($time_by_days, $username);
+            }
+
+            $last_week = date('Y-m-d', $a_last_week[0]).' '.get_lang('To').' '.date('Y-m-d', $a_last_week[6]);
+            $days_on_week = array();
+            foreach ($a_last_week as $weekday) {
+                $days_on_week[] = date('d/m', $weekday);
+            }
+
+            $dataSet->addPoints($days_on_week, 'Days');
+            $dataSet->setAbscissaName($last_week);
+            $dataSet->setAxisName(0, get_lang('Minutes'));
             $dataSet->setAbscissa('Days');
             $dataSet->loadPalette(api_get_path(SYS_CODE_PATH).'palettes/pchart/default.color', true);
 
@@ -138,12 +141,10 @@ class BlockTeacherGraph extends Block
                 $myCache->saveFromCache($chartHash, $imgPath);
                 $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
             } else {
-
                 /* Create the pChart object */
                 $widthSize = 440;
                 $heightSize = 350;
                 $angle = 50;
-
                 $myPicture = new pImage($widthSize, $heightSize, $dataSet);
 
                 /* Turn of Antialiasing */
@@ -194,20 +195,20 @@ class BlockTeacherGraph extends Block
                 $imgPath = api_get_path(WEB_ARCHIVE_PATH).$chartHash;
             }
             $graph = '<img src="'.$imgPath.'" >';
-		} else {
-			$graph = '<p>'.api_convert_encoding(get_lang('GraphicNotAvailable'), 'UTF-8').'</p>';
-		}
+        } else {
+            $graph = '<p>'.api_convert_encoding(get_lang('GraphicNotAvailable'), 'UTF-8').'</p>';
+        }
 
- 		return $graph;
-	}
+        return $graph;
+    }
 
     /**
-	 * Get number of teachers
-	 * @return int
-	 */
-	function get_number_of_teachers()
+     * Get number of teachers
+     * @return int
+     */
+    function get_number_of_teachers()
     {
-		return count($this->teachers);
-	}
+        return count($this->teachers);
+    }
 
 }

+ 2 - 6
plugin/studentfollowup/Entity/CarePost.php

@@ -41,7 +41,7 @@ class CarePost
     /**
      * @var string
      *
-     * @ORM\Column(name="external_care_id", type="bigint", nullable=true)
+     * @ORM\Column(name="external_care_id", type="string", nullable=true)
      */
     protected $externalCareId;
 
@@ -58,13 +58,11 @@ class CarePost
     private $user;
 
     /**
-     * @Gedmo\Timestampable(on="create")
      * @ORM\Column(name="created_at", type="datetime", nullable=true)
      */
     protected $createdAt;
 
     /**
-     * @Gedmo\Timestampable(on="update")
      * @ORM\Column(name="updated_at", type="datetime", nullable=true)
      */
     protected $updatedAt;
@@ -106,7 +104,7 @@ class CarePost
 
      /**
      * @ORM\OneToMany(targetEntity="CarePost", mappedBy="parent")
-     * @ORM\OrderBy({"lft" = "ASC"})
+     * @ORM\OrderBy({"createdAt" = "DESC"})
      */
     private $children;
 
@@ -421,6 +419,4 @@ class CarePost
         return $this;
     }
 
-
-
 }

+ 15 - 1
plugin/studentfollowup/StudentFollowUpPlugin.php

@@ -46,7 +46,7 @@ class StudentFollowUpPlugin extends Plugin
         $fs = new Filesystem();
         $fs->mirror(__DIR__.'/Entity/', $pluginEntityPath, null, ['override']);
 
-        $sql = "CREATE TABLE sfu_post (id INT AUTO_INCREMENT NOT NULL, insert_user_id INT NOT NULL, user_id INT NOT NULL, parent_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, content LONGTEXT DEFAULT NULL, external_care_id BIGINT DEFAULT NULL, created_at DATETIME DEFAULT NULL, updated_at DATETIME DEFAULT NULL, private TINYINT(1) NOT NULL, external_source TINYINT(1) NOT NULL, tags LONGTEXT NOT NULL COMMENT '(DC2Type:array)', attachment VARCHAR(255) NOT NULL, lft INT DEFAULT NULL, rgt INT DEFAULT NULL, lvl INT DEFAULT NULL, root INT DEFAULT NULL, INDEX IDX_35F9473C9C859CC3 (insert_user_id), INDEX IDX_35F9473CA76ED395 (user_id), INDEX IDX_35F9473C727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;";
+        $sql = "CREATE TABLE sfu_post (id INT AUTO_INCREMENT NOT NULL, insert_user_id INT NOT NULL, user_id INT NOT NULL, parent_id INT DEFAULT NULL, title VARCHAR(255) NOT NULL, content LONGTEXT DEFAULT NULL, external_care_id VARCHAR(255) DEFAULT NULL, created_at DATETIME DEFAULT NULL, updated_at DATETIME DEFAULT NULL, private TINYINT(1) NOT NULL, external_source TINYINT(1) NOT NULL, tags LONGTEXT NOT NULL COMMENT '(DC2Type:array)', attachment VARCHAR(255) NOT NULL, lft INT DEFAULT NULL, rgt INT DEFAULT NULL, lvl INT DEFAULT NULL, root INT DEFAULT NULL, INDEX IDX_35F9473C9C859CC3 (insert_user_id), INDEX IDX_35F9473CA76ED395 (user_id), INDEX IDX_35F9473C727ACA70 (parent_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB;";
         Database::query($sql);
         $sql = "ALTER TABLE sfu_post ADD CONSTRAINT FK_35F9473C9C859CC3 FOREIGN KEY (insert_user_id) REFERENCES user (id);";
         Database::query($sql);
@@ -86,6 +86,20 @@ class StudentFollowUpPlugin extends Plugin
      */
     public static function getPermissions($studentId, $currentUserId)
     {
+        $params = ['variable = ? AND subkey = ?' => ['status', 'studentfollowup']];
+        $result = api_get_settings_params_simple($params);
+        $installed = false;
+        if (!empty($result) && $result['selected_value'] === 'installed') {
+            $installed = true;
+        }
+
+        if ($installed == false) {
+            return [
+                'is_allow' => false,
+                'show_private' => false,
+            ];
+        }
+
         $isAllow = false;
         $showPrivate = false;
         if ($studentId === $currentUserId) {

+ 23 - 2
plugin/studentfollowup/post.php

@@ -2,7 +2,7 @@
 /* For licensing terms, see /license.txt */
 
 use Doctrine\Common\Collections\Criteria;
-use Doctrine\ORM\Tools\Pagination\Paginator;
+use Chamilo\PluginBundle\Entity\StudentFollowUp\CarePost;
 
 require_once __DIR__.'/../../main/inc/global.inc.php';
 
@@ -40,12 +40,33 @@ $qb
     ->addCriteria($criteria)
     ->setMaxResults(1)
 ;
-
 $query = $qb->getQuery();
+/** @var CarePost $post */
 $post = $query->getOneOrNullResult();
 
+// Get related posts (post with same parent)
+$relatedPosts = [];
+if ($post && !empty($post->getParent())) {
+    $qb = $em->createQueryBuilder();
+    $criteria = Criteria::create();
+    if ($showPrivate == false) {
+        $criteria->andWhere(Criteria::expr()->eq('private', false));
+    }
+    $criteria->andWhere(Criteria::expr()->eq('parent', $post->getParent()));
+    $criteria->andWhere(Criteria::expr()->neq('id', $post->getId()));
+    $qb
+        ->select('p')
+        ->from('ChamiloPluginBundle:StudentFollowUp\CarePost', 'p')
+        ->addCriteria($criteria)
+        ->orderBy('p.createdAt', 'desc')
+    ;
+    $query = $qb->getQuery();
+    $relatedPosts = $query->getResult();
+}
+
 $tpl = new Template($plugin->get_lang('plugin_title'));
 $tpl->assign('post', $post);
+$tpl->assign('related_posts', $relatedPosts);
 $url = api_get_path(WEB_PLUGIN_PATH).'/studentfollowup/post.php?student_id='.$studentId;
 $tpl->assign('post_url', $url);
 $tpl->assign(

+ 1 - 1
plugin/studentfollowup/posts.php

@@ -38,7 +38,7 @@ $qb
     ->select('p')
     ->from('ChamiloPluginBundle:StudentFollowUp\CarePost', 'p')
     ->addCriteria($criteria)
-    ->setFirstResult($pageSize * ($currentPage-1))
+    ->setFirstResult($pageSize * ($currentPage - 1))
     ->setMaxResults($pageSize)
     ->orderBy('p.createdAt', 'desc')
 ;

+ 35 - 19
plugin/studentfollowup/view/post.html.twig

@@ -1,4 +1,4 @@
-{% macro post_template(post, information_icon, post_url, current_url) %}
+{% macro post_template(type, post, information_icon, post_url, current_url, related_posts) %}
     {% if post %}
         <h2>{{ post.title }}</h2>
         <p>{{ post.content }}</p>
@@ -22,29 +22,45 @@
         {% set countElements = post.hasParent  + post.children.count %}
 
         {% if countElements %}
-            {% if countElements > 1%}
-                <p>{{ information_icon }} + {{ countElements }}</p>
+            <a href="{{ post_url }}&post_id={{ post.id }}">
+            {% if countElements > 1 %}
+                {{ information_icon }} + {{ countElements }}
             {% else %}
-                <p>{{ information_icon }} </p>
+                {{ information_icon }}
             {% endif %}
-        {% endif %}
-
-        {% if post.parent %}
-            <h3>Parent</h3>
-            <a href="{{ post_url }}&post_id={{ post.parent.id }}">
-                {{ post.parent.title }}
             </a>
         {% endif %}
 
-        {% if post.children.count %}
-            <h3>Children</h3>
-            {% for child in post.children %}
-                <p>
-                <a href="{{ post_url }}&post_id={{ child.id }}">
-                {{ child.title }}
+
+        {% if type == 'all' %}
+            {% if post.parent %}
+                <h3>Parent</h3>
+                <a href="{{ post_url }}&post_id={{ post.parent.id }}">
+                    {{ post.parent.title }}
                 </a>
-                </p>
-            {% endfor %}
+            {% endif %}
+
+            {% if post.children.count %}
+                <h3>Children</h3>
+                {% for child in post.children %}
+                    <p>
+                        <a href="{{ post_url }}&post_id={{ child.id }}">
+                        {{ child.title }}
+                        </a>
+                    </p>
+                {% endfor %}
+            {% endif %}
+
+            {% if related_posts %}
+                <h3>Related</h3>
+                 {% for post in related_posts %}
+                    <p>
+                        <a href="{{ post_url }}&post_id={{ post.id }}">
+                        {{ post.title }}
+                        </a>
+                    </p>
+                {% endfor %}
+            {% endif %}
         {% endif %}
     {% endif %}
 {% endmacro %}
@@ -55,4 +71,4 @@
     {{ back_link }}
 </div>
 <h1>Care detail view</h1>
-{{ template.post_template(post, information_icon, post_url, current_url) }}
+{{ template.post_template('all', post, information_icon, post_url, current_url, related_posts) }}

+ 1 - 3
plugin/studentfollowup/view/posts.html.twig

@@ -3,9 +3,7 @@
 <h1>Care detail view</h1>
 {% if posts %}
     {% for post in posts %}
-        {{ template.post_template(post, information_icon, post_url, current_url) }}
-
-
+        {{ template.post_template('simple', post, information_icon, post_url, current_url) }}
     {% endfor %}
     <div>
     {{ pagination }}

Деякі файли не було показано, через те що забагато файлів було змінено