瀏覽代碼

Merge with 1.11.x

jmontoyaa 8 年之前
父節點
當前提交
e13f027c78
共有 100 個文件被更改,包括 2974 次插入846 次删除
  1. 35 9
      app/Migrations/Schema/V110/Version110.php
  2. 6 4
      app/Migrations/Schema/V110/Version20150603181728.php
  3. 16 0
      app/Migrations/Schema/V110/Version20151221150100.php
  4. 4 0
      app/Migrations/Schema/V111/Version111.php
  5. 3 6
      app/Migrations/Schema/V111/Version20160603113100.php
  6. 54 0
      app/Migrations/Schema/V111/Version20160825155200.php
  7. 4 4
      app/Resources/public/assets/blueimp-load-image/.bower.json
  8. 3 0
      app/Resources/public/assets/blueimp-load-image/js/load-image-exif-map.js
  9. 0 0
      app/Resources/public/assets/blueimp-load-image/js/load-image.all.min.js
  10. 0 0
      app/Resources/public/assets/blueimp-load-image/js/load-image.all.min.js.map
  11. 1 1
      app/Resources/public/assets/blueimp-load-image/package.json
  12. 4 4
      app/Resources/public/assets/mediaelement/.bower.json
  13. 二進制
      app/Resources/public/assets/mediaelement/build/flashmediaelement-cdn.swf
  14. 二進制
      app/Resources/public/assets/mediaelement/build/flashmediaelement-debug.swf
  15. 二進制
      app/Resources/public/assets/mediaelement/build/flashmediaelement.swf
  16. 25 9
      app/Resources/public/assets/mediaelement/build/mediaelement-and-player.js
  17. 0 0
      app/Resources/public/assets/mediaelement/build/mediaelement-and-player.min.js
  18. 24 27
      app/Resources/public/assets/mediaelement/build/mediaelementplayer.css
  19. 25 9
      app/Resources/public/assets/mediaelement/build/mediaelementplayer.js
  20. 0 0
      app/Resources/public/assets/mediaelement/build/mediaelementplayer.min.css
  21. 0 0
      app/Resources/public/assets/mediaelement/build/mediaelementplayer.min.js
  22. 12 1
      app/Resources/public/assets/mediaelement/changelog.md
  23. 4 4
      app/Resources/public/assets/pwstrength-bootstrap/.bower.json
  24. 11 1
      app/Resources/public/assets/pwstrength-bootstrap/OPTIONS.md
  25. 13 1
      app/Resources/public/assets/pwstrength-bootstrap/README.md
  26. 1 1
      app/Resources/public/assets/pwstrength-bootstrap/bower.json
  27. 6 1
      app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.js
  28. 1 1
      app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.min.js
  29. 0 0
      app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.min.map
  30. 1 0
      app/Resources/public/assets/pwstrength-bootstrap/src/options.js
  31. 4 0
      app/Resources/public/assets/pwstrength-bootstrap/src/rules.js
  32. 34 0
      app/Resources/public/assets/readmore-js/.bower.json
  33. 36 0
      app/Resources/public/assets/readmore-js/CHANGELOG.md
  34. 22 0
      app/Resources/public/assets/readmore-js/LICENSE
  35. 177 0
      app/Resources/public/assets/readmore-js/README.md
  36. 25 0
      app/Resources/public/assets/readmore-js/ajax-demo.html
  37. 25 0
      app/Resources/public/assets/readmore-js/bower.json
  38. 235 0
      app/Resources/public/assets/readmore-js/demo.html
  39. 14 0
      app/Resources/public/assets/readmore-js/gulpfile.js
  40. 692 0
      app/Resources/public/assets/readmore-js/jquery.mockjax.js
  41. 34 0
      app/Resources/public/assets/readmore-js/package.json
  42. 330 0
      app/Resources/public/assets/readmore-js/readmore.js
  43. 10 0
      app/Resources/public/assets/readmore-js/readmore.min.js
  44. 4 4
      app/Resources/public/assets/webcamjs/.bower.json
  45. 10 1
      app/Resources/public/assets/webcamjs/README.md
  46. 1 1
      app/Resources/public/assets/webcamjs/bower.json
  47. 1 1
      app/Resources/public/assets/webcamjs/package.json
  48. 9 4
      app/Resources/public/assets/webcamjs/webcam.js
  49. 0 1
      app/Resources/public/assets/webcamjs/webcam.min.js
  50. 408 224
      app/Resources/public/css/base.css
  51. 2 1
      bower.json
  52. 5 2
      documentation/installation_guide.html
  53. 0 1
      main/admin/course_intro_pdf_import.php
  54. 12 11
      main/admin/course_list.php
  55. 8 9
      main/admin/course_user_import_by_email.php
  56. 3 9
      main/admin/dashboard_add_courses_to_user.php
  57. 84 89
      main/admin/dashboard_add_sessions_to_user.php
  58. 20 20
      main/admin/dashboard_add_users_to_user.php
  59. 7 1
      main/admin/email_tester.php
  60. 21 41
      main/admin/event_controller.php
  61. 22 5
      main/admin/event_type.php
  62. 0 3
      main/admin/export_certificates.php
  63. 18 9
      main/admin/extra_field_workflow.php
  64. 0 5
      main/admin/extra_fields.php
  65. 6 6
      main/admin/filler.php
  66. 50 19
      main/admin/grade_models.php
  67. 4 5
      main/admin/inactive_user_list.php
  68. 14 20
      main/admin/ldap_form_add_users_group.php
  69. 0 3
      main/admin/ldap_import_students.php
  70. 2 2
      main/admin/legal_add.php
  71. 2 0
      main/admin/legal_list.php
  72. 9 1
      main/admin/resource_sequence.php
  73. 3 2
      main/admin/skill_badge_create.php
  74. 2 1
      main/admin/skill_badge_list.php
  75. 3 1
      main/admin/skill_create.php
  76. 3 1
      main/admin/skill_edit.php
  77. 0 1
      main/admin/skill_level.php
  78. 0 2
      main/admin/skill_list.php
  79. 4 5
      main/admin/skills.php
  80. 38 12
      main/admin/skills_gradebook.php
  81. 123 117
      main/admin/skills_import.php
  82. 4 8
      main/admin/skills_profile.php
  83. 5 6
      main/admin/special_exports.php
  84. 8 3
      main/admin/system_announcements.php
  85. 1 2
      main/admin/teacher_time_report.php
  86. 3 3
      main/admin/teachers_time_by_session_report.php
  87. 29 11
      main/admin/user_import.php
  88. 7 1
      main/admin/usergroup_import.php
  89. 15 17
      main/announcements/announcements.php
  90. 1 0
      main/auth/courses.php
  91. 6 11
      main/auth/courses_controller.php
  92. 74 25
      main/auth/external_login/functions.inc.php
  93. 14 7
      main/auth/external_login/login.ws.php
  94. 1 1
      main/auth/external_login/newUser.php
  95. 2 2
      main/auth/gotocourse.php
  96. 8 21
      main/auth/profile.php
  97. 4 5
      main/auth/set_temp_password.php
  98. 1 0
      main/badge/assertion.php
  99. 1 0
      main/badge/class.php
  100. 1 0
      main/badge/criteria.php

+ 35 - 9
app/Migrations/Schema/V110/Version110.php

@@ -37,6 +37,8 @@ class Version110 extends AbstractMigrationChamilo
         // Use $schema->createTable
         $this->addSql('set sql_mode=""');
 
+        $connection = $this->connection;
+
         $this->addSql("CREATE TABLE IF NOT EXISTS course_field_options (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, field_id INT NOT NULL, option_value TEXT, option_display_text VARCHAR(64), option_order INT, tms DATETIME)");
         $this->addSql("CREATE TABLE IF NOT EXISTS session_field_options (id INT NOT NULL PRIMARY KEY AUTO_INCREMENT, field_id INT NOT NULL, option_value TEXT, option_display_text VARCHAR(64), option_order INT, tms DATETIME)");
         $this->addSql("CREATE TABLE IF NOT EXISTS hook_observer( id int UNSIGNED NOT NULL AUTO_INCREMENT, class_name varchar(255) UNIQUE, path varchar(255) NOT NULL, plugin_name varchar(255) NULL, PRIMARY KEY PK_hook_management_hook_observer(id))");
@@ -61,7 +63,9 @@ class Version110 extends AbstractMigrationChamilo
             $this->addSql("ALTER TABLE skill_rel_user ADD COLUMN session_id INT NOT NULL DEFAULT 0 AFTER course_id");
         }
 
-        $this->addSql("ALTER TABLE skill_rel_user ADD INDEX idx_select_cs (course_id, session_id)");
+        if (!$table->hasIndex('idx_select_cs')) {
+            $this->addSql("ALTER TABLE skill_rel_user ADD INDEX idx_select_cs (course_id, session_id)");
+        }
 
         // Delete info of session_rel_user if session does not exists;
         $this->addSql("DELETE FROM session_rel_user WHERE id_session NOT IN (SELECT id FROM session)");
@@ -95,8 +99,14 @@ class Version110 extends AbstractMigrationChamilo
             $this->addSql("ALTER TABLE session_rel_user ADD COLUMN duration int");
         }
 
-        $this->addSql("ALTER TABLE skill ADD COLUMN criteria text");
-        $this->addSql("ALTER TABLE gradebook_category ADD COLUMN generate_certificates TINYINT NOT NULL DEFAULT 0");
+        $table = $schema->getTable('skill');
+        if (!$table->hasColumn('criteria')) {
+            $this->addSql("ALTER TABLE skill ADD COLUMN criteria text");
+        }
+        $table = $schema->getTable('gradebook_category');
+        if (!$table->hasColumn('generate_certificates')) {
+            $this->addSql("ALTER TABLE gradebook_category ADD COLUMN generate_certificates TINYINT NOT NULL DEFAULT 0");
+        }
         $this->addSql("ALTER TABLE track_e_access ADD COLUMN c_id int NOT NULL");
 
         $this->addSql("ALTER TABLE track_e_lastaccess ADD COLUMN c_id int NOT NULL");
@@ -135,7 +145,10 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("ALTER TABLE user MODIFY COLUMN expiration_date datetime default NULL");
         $this->addSql("ALTER TABLE user MODIFY COLUMN registration_date datetime NOT NULL");
 
-        $this->addSql("ALTER TABLE course ADD COLUMN add_teachers_to_sessions_courses tinyint NOT NULL default 0");
+        $table = $schema->getTable('course');
+        if (!$table->hasColumn('add_teachers_to_sessions_courses')) {
+            $this->addSql("ALTER TABLE course ADD COLUMN add_teachers_to_sessions_courses tinyint NOT NULL default 0");
+        }
         $this->addSql("ALTER TABLE course DROP COLUMN target_course_code");
         $this->addSql("ALTER TABLE session MODIFY COLUMN name char(100) NOT NULL DEFAULT ''");
         $this->addSql("ALTER TABLE course_rel_user ADD COLUMN c_id int default NULL");
@@ -399,6 +412,7 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("ALTER TABLE c_tool_intro ADD COLUMN iid int NOT NULL PRIMARY KEY AUTO_INCREMENT");
 
         $this->addSql("ALTER TABLE c_quiz_answer MODIFY COLUMN c_id INT NOT NULL");
+        $this->addSql("ALTER TABLE c_quiz_answer MODIFY COLUMN id INT DEFAULT NULL");
         $this->addSql("ALTER TABLE c_quiz_answer MODIFY COLUMN id_auto int unsigned NOT NULL");
         $this->addSql("ALTER TABLE c_quiz_answer DROP PRIMARY KEY");
         $this->addSql("ALTER TABLE c_quiz_answer MODIFY COLUMN id_auto int unsigned DEFAULT NULL");
@@ -417,7 +431,7 @@ class Version110 extends AbstractMigrationChamilo
 
         $this->addSql("ALTER TABLE session_rel_user CHANGE id_session session_id int");
         $this->addSql("ALTER TABLE session_rel_user CHANGE id_user user_id int");
-        $this->addSql("DELETE FROM session_rel_user WHERE user_id NOT IN (SELECT user_id FROM user)");
+        $this->addSql("DELETE FROM session_rel_user WHERE user_id NOT IN (SELECT id FROM user)");
         $this->addSql("ALTER TABLE session_rel_user ADD COLUMN id int NOT NULL PRIMARY KEY AUTO_INCREMENT");
 
         $this->addSql("ALTER TABLE c_item_property CHANGE id_session session_id int");
@@ -482,7 +496,12 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("ALTER TABLE c_lp_item ADD COLUMN prerequisite_min_score float");
         $this->addSql("ALTER TABLE c_lp_item ADD COLUMN prerequisite_max_score float");
         $this->addSql("ALTER TABLE c_group_info ADD COLUMN status tinyint DEFAULT 1");
-        $this->addSql("ALTER TABLE c_student_publication ADD COLUMN document_id int DEFAULT 0");
+
+        $table = $schema->getTable('c_student_publication');
+        if (!$table->hasColumn('document_id')) {
+            $this->addSql("ALTER TABLE c_student_publication ADD COLUMN document_id int DEFAULT 0");
+        }
+
         $this->addSql("ALTER TABLE c_lp_item MODIFY COLUMN description VARCHAR(511) DEFAULT ''");
         $this->addSql("ALTER TABLE course_category MODIFY COLUMN auth_course_child VARCHAR(40) DEFAULT 'TRUE' ");
         $this->addSql("ALTER TABLE course_category MODIFY COLUMN auth_cat_child VARCHAR(40) DEFAULT 'TRUE'");
@@ -535,9 +554,16 @@ class Version110 extends AbstractMigrationChamilo
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'exercise', 'Exercise')");
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'lp', 'Learning path')");
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('show_glossary_in_extra_tools', 'exercise_and_lp', 'ExerciseAndLearningPath')");
-        $this->addSql("INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES ('documents_default_visibility_defined_in_course', NULL,'radio','Tools','false','DocumentsDefaultVisibilityDefinedInCourseTitle','DocumentsDefaultVisibilityDefinedInCourseComment',NULL, NULL, 1)");
-        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('documents_default_visibility_defined_in_course', 'true', 'Yes')");
-        $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('documents_default_visibility_defined_in_course', 'false', 'No')");
+
+        $sql = "SELECT * FROM settings_current WHERE variable = 'documents_default_visibility_defined_in_course'";
+        $result = $connection->executeQuery($sql);
+        $count = $result->rowCount();
+        if (empty($count)) {
+            $this->addSql("INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES ('documents_default_visibility_defined_in_course', NULL,'radio','Tools','false','DocumentsDefaultVisibilityDefinedInCourseTitle','DocumentsDefaultVisibilityDefinedInCourseComment',NULL, NULL, 1)");
+            $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('documents_default_visibility_defined_in_course', 'true', 'Yes')");
+            $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('documents_default_visibility_defined_in_course', 'false', 'No')");
+        }
+
         $this->addSql("INSERT INTO settings_current (variable, subkey, type, category, selected_value, title, comment, scope, subkeytext, access_url_changeable) VALUES ('enabled_mathjax', NULL, 'radio', 'Editor', 'false', 'EnableMathJaxTitle', 'EnableMathJaxComment', NULL, NULL, 0)");
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('enabled_mathjax', 'true', 'Yes')");
         $this->addSql("INSERT INTO settings_options (variable, value, display_text) VALUES ('enabled_mathjax', 'false', 'No')");

+ 6 - 4
app/Migrations/Schema/V110/Version20150603181728.php

@@ -56,18 +56,20 @@ class Version20150603181728 extends AbstractMigrationChamilo
         // Remove inconsistencies about non-existing courses
 
         $this->addSql("DELETE FROM c_item_property WHERE session_id IS NOT NULL and session_id <> 0 AND session_id NOT IN (SELECT id FROM session)");
-        $this->addSql("DELETE FROM c_item_property WHERE to_group_id IS NOT NULL AND to_group_id <> 0 AND to_group_id NOT IN (SELECT iid FROM c_group_info)");
-        $this->addSql("DELETE FROM c_item_property WHERE to_user_id IS NOT NULL and to_user_id <> 0 AND  to_user_id NOT IN (SELECT user_id FROM user)");
+        $this->addSql("DELETE FROM c_item_property WHERE to_user_id IS NOT NULL and to_user_id <> 0 AND  to_user_id NOT IN (SELECT id FROM user)");
         $this->addSql("DELETE FROM c_item_property WHERE to_user_id IS NOT NULL AND to_user_id <> 0 AND to_user_id NOT IN (SELECT id FROM user)");
         $this->addSql("DELETE FROM c_item_property WHERE insert_user_id IS NOT NULL AND insert_user_id <> 0 AND insert_user_id NOT IN (SELECT id FROM user)");
 
         // Remove inconsistencies about non-existing users
-        $this->addSql("DELETE FROM course_rel_user WHERE user_id NOT IN (SELECT user_id FROM user)");
+        $this->addSql("DELETE FROM c_item_property WHERE c_id NOT IN (SELECT id FROM course)");
+        // Remove inconsistencies about non-existing users
+        $this->addSql("DELETE FROM course_rel_user WHERE user_id NOT IN (SELECT id FROM user)");
 
         $this->addSql("DELETE FROM c_item_property WHERE c_id NOT IN (SELECT id FROM course)");
         $this->addSql("UPDATE c_item_property SET to_group_id = NULL WHERE to_group_id = 0");
-        $this->addSql("DELETE FROM c_item_property WHERE to_group_id IS NOT NULL AND to_group_id NOT IN (SELECT id FROM c_group_info)");
+
         $this->addSql('UPDATE c_item_property cip SET cip.to_group_id = (SELECT cgi.iid FROM c_group_info cgi WHERE cgi.c_id = cip.c_id AND cgi.id = cip.to_group_id)');
+        $this->addSql("DELETE FROM c_item_property WHERE to_group_id IS NOT NULL AND to_group_id <> 0 AND to_group_id NOT IN (SELECT iid FROM c_group_info)");
         $this->addSql('UPDATE c_item_property SET to_user_id = NULL WHERE to_user_id = 0');
         $this->addSql('UPDATE c_item_property SET insert_user_id = NULL WHERE insert_user_id = 0');
         $this->addSql('UPDATE c_item_property SET session_id = NULL WHERE session_id = 0');

+ 16 - 0
app/Migrations/Schema/V110/Version20151221150100.php

@@ -16,6 +16,16 @@ class Version20151221150100 extends AbstractMigrationChamilo
      */
     public function up(Schema $schema)
     {
+        $this->addSql("ALTER TABLE c_quiz_answer ENGINE=InnoDB");
+        $this->addSql("ALTER TABLE c_quiz_question ENGINE=InnoDB");
+        $this->addSql("ALTER TABLE c_quiz_answer ADD INDEX idx_qa_question_id_temp (question_id)");
+        $this->addSql("ALTER TABLE c_quiz_answer ADD INDEX idx_qa_id_temp (id)");
+        $this->addSql("ALTER TABLE c_quiz_answer ADD INDEX idx_qa_correct_temp (correct)");
+        $this->addSql("ALTER TABLE track_e_attempt CHANGE tms tms datetime default null");
+        $this->addSql("UPDATE track_e_attempt SET tms = NULL WHERE tms = '0000-00-00 00:00:00'");
+        $this->addSql("ALTER TABLE track_e_attempt ADD INDEX idx_tea_answer_temp (answer(6))");
+        $this->addSql("ALTER TABLE c_quiz_question ADD INDEX idx_qq_id_temp (id)");
+        $this->addSql("ALTER TABLE c_quiz_question ADD INDEX idx_qq_type_temp (type)");
         $this->addSql("
             UPDATE track_e_attempt a
             INNER JOIN c_quiz_answer qa
@@ -41,6 +51,12 @@ class Version20151221150100 extends AbstractMigrationChamilo
                 q.c_id = a.c_id AND
                 q.type IN (" . MATCHING . ", " . DRAGGABLE . ", " . MATCHING_DRAGGABLE . ")
         ");
+        $this->addSql("ALTER TABLE c_quiz_answer DROP INDEX idx_qa_question_id_temp");
+        $this->addSql("ALTER TABLE c_quiz_answer DROP INDEX idx_qa_id_temp");
+        $this->addSql("ALTER TABLE c_quiz_answer DROP INDEX idx_qa_correct_temp");
+        $this->addSql("ALTER TABLE track_e_attempt DROP INDEX idx_tea_answer_temp");
+        $this->addSql("ALTER TABLE c_quiz_question DROP INDEX idx_qq_id_temp");
+        $this->addSql("ALTER TABLE c_quiz_question DROP INDEX idx_qq_type_temp");
     }
 
     /**

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

@@ -303,6 +303,10 @@ class Version111 extends AbstractMigrationChamilo
             $this->addSql('CREATE TABLE version (id int unsigned NOT NULL AUTO_INCREMENT, version varchar(255), PRIMARY KEY(id), UNIQUE(version));');
         }
 
+        if ($schema->hasTable('resource')) {
+            $schema->dropTable('resource');
+        }
+
         $this->addSql('DELETE FROM settings_current WHERE variable = "service_visio"');
         $this->addSql('DELETE FROM settings_current WHERE variable = "course_create_active_tools" AND subkey = "online_conference"');
         $this->addSql('DELETE FROM settings_options WHERE variable = "visio_use_rtmpt"');

+ 3 - 6
app/Migrations/Schema/V111/Version20160603113100.php

@@ -21,12 +21,9 @@ class Version20160603113100 extends AbstractMigrationChamilo
      */
     public function up(Schema $schema)
     {
-        $languageTable = $schema->getTable('language');
-        $languageTable
-            ->getColumn('parent_id')
-            ->setType(Type::getType(Type::INTEGER))
-            ->setNotnull(false);
-        $languageTable->addForeignKeyConstraint('language', ['parent_id'], ['id'], [], 'language_parent');
+        $this->addSql('ALTER TABLE language MODIFY id INT');
+        $this->addSql('ALTER TABLE language MODIFY parent_id INT');
+        $this->addSql('ALTER TABLE language ADD CONSTRAINT language_parent FOREIGN KEY (parent_id) REFERENCES language (id)');
     }
 
     /**

+ 54 - 0
app/Migrations/Schema/V111/Version20160825155200.php

@@ -0,0 +1,54 @@
+<?php
+/* For licensing terms, see /license.txt */
+
+namespace Application\Migrations\Schema\V111;
+
+use Application\Migrations\AbstractMigrationChamilo;
+use Doctrine\DBAL\Schema\Schema;
+use Doctrine\DBAL\Types\Type;
+
+/**
+ * Class Version20160825155200
+ * Add option to allow download documents with the api key
+ * @package Application\Migrations\Schema\V111
+ */
+class Version20160825155200 extends AbstractMigrationChamilo
+{
+    /**
+     * @param Schema $schema
+     * @throws \Doctrine\DBAL\DBALException
+     * @throws \Doctrine\DBAL\Schema\SchemaException
+     */
+    public function up(Schema $schema)
+    {
+        $this->addSettingCurrent(
+            'allow_download_documents_by_api_key',
+            null,
+            'radio',
+            'WebServices',
+            'false',
+            'AllowDownloadDocumentsByApiKeyTitle',
+            'AllowDownloadDocumentsByApiKeyComment',
+            null,
+            null,
+            1,
+            true,
+            true,
+            [
+                ['value' => 'false', 'text' => 'No'],
+                ['value' => 'true', 'text' => 'Yes']
+            ]
+        );
+    }
+
+    /**
+     * @param Schema $schema
+     * @throws \Doctrine\DBAL\DBALException
+     * @throws \Doctrine\DBAL\Schema\SchemaException
+     */
+    public function down(Schema $schema)
+    {
+        $this->addSql("DELETE FROM settings_current WHERE variable = 'allow_download_documents_by_api_key'");
+        $this->addSql("DELETE FROM settings_options WHERE variable = 'allow_download_documents_by_api_key'");
+    }
+}

+ 4 - 4
app/Resources/public/assets/blueimp-load-image/.bower.json

@@ -1,12 +1,12 @@
 {
   "name": "blueimp-load-image",
   "homepage": "https://github.com/blueimp/JavaScript-Load-Image",
-  "version": "2.6.1",
-  "_release": "2.6.1",
+  "version": "2.6.2",
+  "_release": "2.6.2",
   "_resolution": {
     "type": "version",
-    "tag": "v2.6.1",
-    "commit": "e96ea2ce372c84b26d6cb6556ca2d8d2b32c163e"
+    "tag": "v2.6.2",
+    "commit": "d6fdf1575c2f0376d29750a626d096f997e9572a"
   },
   "_source": "https://github.com/blueimp/JavaScript-Load-Image.git",
   "_target": ">=1.13.0",

+ 3 - 0
app/Resources/public/assets/blueimp-load-image/js/load-image-exif-map.js

@@ -343,13 +343,16 @@
         return this.stringValues[id][value]
       case 'ExifVersion':
       case 'FlashpixVersion':
+        if (!value) return
         return String.fromCharCode(value[0], value[1], value[2], value[3])
       case 'ComponentsConfiguration':
+        if (!value) return
         return this.stringValues[id][value[0]] +
         this.stringValues[id][value[1]] +
         this.stringValues[id][value[2]] +
         this.stringValues[id][value[3]]
       case 'GPSVersionID':
+        if (!value) return
         return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3]
     }
     return String(value)

文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/blueimp-load-image/js/load-image.all.min.js


文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/blueimp-load-image/js/load-image.all.min.js.map


+ 1 - 1
app/Resources/public/assets/blueimp-load-image/package.json

@@ -1,6 +1,6 @@
 {
   "name": "blueimp-load-image",
-  "version": "2.6.1",
+  "version": "2.6.2",
   "main": "index.js",
   "title": "JavaScript Load Image",
   "description": "JavaScript Load Image is a library to load images provided as File or Blob objects or via URL. It returns an optionally scaled and/or cropped HTML img or canvas element. It also provides a method to parse image meta data to extract Exif tags and thumbnails and to restore the complete image header after resizing.",

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

@@ -23,12 +23,12 @@
     "./build/mediaelement-and-player.js",
     "./build/mediaelementplayer.css"
   ],
-  "version": "2.22.0",
-  "_release": "2.22.0",
+  "version": "2.22.1",
+  "_release": "2.22.1",
   "_resolution": {
     "type": "version",
-    "tag": "2.22.0",
-    "commit": "c8ab5f4c70f3ea1655bb36ef6af20976c940f65a"
+    "tag": "2.22.1",
+    "commit": "add004709742e81e578022ff6adafd372d25e107"
   },
   "_source": "https://github.com/johndyer/mediaelement.git",
   "_target": "2.*",

二進制
app/Resources/public/assets/mediaelement/build/flashmediaelement-cdn.swf


二進制
app/Resources/public/assets/mediaelement/build/flashmediaelement-debug.swf


二進制
app/Resources/public/assets/mediaelement/build/flashmediaelement.swf


+ 25 - 9
app/Resources/public/assets/mediaelement/build/mediaelement-and-player.js

@@ -2430,7 +2430,7 @@ if (typeof jQuery != 'undefined') {
 					.addClass(t.$media[0].className)
 					.insertBefore(t.$media)
 					.focus(function ( e ) {
-						if( !t.controlsAreVisible && !t.hasFocus ) {
+						if( !t.controlsAreVisible && !t.hasFocus && t.controlsEnabled) {
 							t.showControls(true);
 							// In versions older than IE11, the focus causes the playbar to be displayed
 							// if user clicks on the Play/Pause button in the control bar once it attempts
@@ -2732,6 +2732,10 @@ if (typeof jQuery != 'undefined') {
 								} else {
 									t.pause();
 								}
+
+								var button = t.$media.closest('.mejs-container').find('.mejs-overlay-button'),
+									pressed = button.attr('aria-pressed');
+								button.attr('aria-pressed', !pressed);
 							}
 						};
 
@@ -2878,7 +2882,10 @@ if (typeof jQuery != 'undefined') {
 						var $target = $(e.relatedTarget);
 						if (t.keyboardAction && $target.parents('.mejs-container').length === 0) {
 							t.keyboardAction = false;
-							t.hideControls(true);
+							if (t.isVideo && !t.options.alwaysShowControls) {
+								t.hideControls(true);
+							}
+
 						}
 					}
 				});
@@ -3247,7 +3254,7 @@ if (typeof jQuery != 'undefined') {
 			// this needs to come last so it's on top
 			bigPlay =
 				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
-					'<div class="mejs-overlay-button"></div>'+
+					'<div class="mejs-overlay-button" role="button" aria-label="' + mejs.i18n.t('Play') + '" aria-pressed="false"></div>'+
 				'</div>')
 				.appendTo(layers)
 				.bind('click', function() {	 // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video
@@ -3255,6 +3262,10 @@ if (typeof jQuery != 'undefined') {
 						if (media.paused) {
 							media.play();
 						}
+
+						var button = $(this).find('.mejs-overlay-button'),
+							pressed = button.attr('aria-pressed');
+						button.attr('aria-pressed', !!pressed);
 					}
 				});
 
@@ -3807,8 +3818,8 @@ if (typeof jQuery != 'undefined') {
 				var keyCode = e.keyCode,
 					duration = media.duration,
 					seekTime = media.currentTime,
-					seekForward  = player.options.defaultSeekForwardInterval(duration),
-					seekBackward = player.options.defaultSeekBackwardInterval(duration);
+					seekForward  = player.options.defaultSeekForwardInterval(media),
+					seekBackward = player.options.defaultSeekBackwardInterval(media);
 
 				switch (keyCode) {
 				case 37: // left
@@ -3988,7 +3999,10 @@ if (typeof jQuery != 'undefined') {
 			t.currenttime = t.controls.find('.mejs-currenttime');
 
 			media.addEventListener('timeupdate',function() {
-				player.updateCurrent();
+				if (t.controlsAreVisible) {
+					player.updateCurrent();
+				}
+
 			}, false);
 		},
 
@@ -4018,7 +4032,9 @@ if (typeof jQuery != 'undefined') {
 			t.durationD = t.controls.find('.mejs-duration');
 
 			media.addEventListener('timeupdate',function() {
-				player.updateDuration();
+				if (t.controlsAreVisible) {
+					player.updateDuration();
+				}
 			}, false);
 		},
 		
@@ -4646,8 +4662,8 @@ if (typeof jQuery != 'undefined') {
 
 			var t = this;
 
-			if (mejs.MediaFeatures.hasiOSFullScreen) {
-				t.media.webkitEnterFullscreen();
+			if (mejs.MediaFeatures.isiOS && mejs.MediaFeatures.hasiOSFullScreen && typeof t.media.webkitEnterFullscreen === 'function') {
+			    t.media.webkitEnterFullscreen();
 				return;
 			}
 

文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/mediaelement/build/mediaelement-and-player.min.js


+ 24 - 27
app/Resources/public/assets/mediaelement/build/mediaelementplayer.css

@@ -12,7 +12,7 @@
 .mejs-container {
 	position: relative;
 	background: #000;
-	font-family: Helvetica, Arial;
+	font-family: "Helvetica", Arial, serif;
 	text-align: left;
 	vertical-align: top;
 	text-indent: 0;
@@ -98,7 +98,6 @@
 .mejs-poster img {
 	border: 0;
 	padding: 0;
-	border: 0;
 }
 
 .mejs-overlay {
@@ -118,11 +117,11 @@
 	width: 100px;
 	height: 100px;
 	margin: -50px 0 0 -50px;
-	background: url(bigplay.svg) no-repeat;
+	background: url("bigplay.svg") no-repeat;
 }
 
 .no-svg .mejs-overlay-button {
-	background-image: url(bigplay.png);
+	background-image: url("bigplay.png");
 }
 
 .mejs-overlay:hover .mejs-overlay-button {
@@ -137,7 +136,7 @@
 	height: 80px;
 	margin: -40px 0 0 -40px;
 	background: #333;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(0, 0, 0, 0.9);
 	background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.9)), to(rgba(0,0,0,0.9)));
 	background: -webkit-linear-gradient(top, rgba(50,50,50,0.9), rgba(0,0,0,0.9));
@@ -151,7 +150,7 @@
 	display: block;
 	width: 80px;
 	height: 80px;
-	background: transparent url(loading.gif) 50% 50% no-repeat;
+	background: transparent url("loading.gif") 50% 50% no-repeat;
 }
 
 /* End: LAYERS */
@@ -164,7 +163,7 @@
 	padding: 0;
 	bottom: 0;
 	left: 0;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(0, 0, 0, 0.7);
 	background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(rgba(50,50,50,0.7)), to(rgba(0,0,0,0.7)));
 	background: -webkit-linear-gradient(top, rgba(50,50,50,0.7), rgba(0,0,0,0.7));
@@ -186,7 +185,7 @@
 	height: 26px;
 	font-size: 11px;
 	line-height: 11px;
-	font-family: Helvetica, Arial;
+	font-family: "Helvetica", Arial, serif;
 	border: 0;
 }
 
@@ -202,11 +201,11 @@
 	height: 16px;
 	width: 16px;
 	border: 0;
-	background: transparent url(controls.svg) no-repeat;
+	background: transparent url("controls.svg") no-repeat;
 }
 
 .no-svg .mejs-controls .mejs-button button {
-	background-image: url(controls.png);
+	background-image: url("controls.png");
 }
 
 	/* :focus for accessibility */
@@ -435,7 +434,7 @@
 	display: none;
 	height: 115px;
 	width: 25px;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50, 50, 50, 0.7);
 	-webkit-border-radius: 0;
 	-moz-border-radius: 0;
@@ -571,7 +570,7 @@
 	right: -51px;
 	width: 85px;
 	height: 100px;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50,50,50,0.7);
 	border: solid 1px transparent;
 	padding: 10px 10px 0 10px;
@@ -613,7 +612,7 @@
 	float: left;
 	padding: 4px 0 0 0;
 	line-height: 15px;
-	font-family: helvetica, arial;
+	font-family: "Helvetica", Arial, serif;
 	font-size: 10px;
 }
 
@@ -626,7 +625,7 @@
 	position: absolute;
 	top: 0;
 	left: 0;
-	-xborder-right: solid 1px #fff;
+	border-right: solid 1px #fff;
 	width: 10000px;
 	z-index: 1;
 }
@@ -725,7 +724,7 @@
 
 .mejs-captions-text {
 	padding: 3px 5px;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(20, 20, 20, 0.5);
 	white-space: pre-wrap;
 }
@@ -795,7 +794,7 @@
 }
 
 .mejs-contextmenu .mejs-contextmenu-item {
-	font-family: Helvetica, Arial;
+	font-family: "Helvetica", Arial, serif;
 	font-size: 12px;
 	padding: 4px 6px;
 	cursor: pointer;
@@ -822,7 +821,7 @@
 	right: -10px;
 	width: 130px;
 	height: 100px;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50,50,50,0.7);
 	border: solid 1px transparent;
 	padding: 10px;
@@ -860,7 +859,7 @@
 	float: left;
 	padding: 4px 0 0 0;
 	line-height: 15px;
-	font-family: helvetica, arial;
+	font-family: "Helvetica", Arial, serif;
 	font-size: 10px;
 }
 /* End: Source Chooser */
@@ -872,7 +871,7 @@
 	left: 0;
 	width: 100%;
 	height: 100%;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50,50,50,0.7);
 	z-index: 1000;
 	overflow: hidden;
@@ -885,7 +884,7 @@
 	position: absolute;
 	right: 0;
 	top: 0;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50,50,50,0.7);
 	color: #fff;
 	padding: 4px;
@@ -916,7 +915,7 @@ div.mejs-speed-button {
 	left: -10px;
 	width: 60px;
 	height: 100px;
-	background: url(background.png);
+	background: url("background.png");
 	background: rgba(50, 50, 50, 0.7);
 	border: solid 1px transparent;
 	padding: 0;
@@ -964,8 +963,8 @@ div.mejs-speed-button {
 	float: left;
 	padding: 4px 0 0 0;
 	line-height: 15px;
-	font-family: helvetica, arial;
-	font-size: 11.5px;
+	font-family: "Helvetica", Arial, serif;
+	font-size: 11px;
 	color: white;
 	margin-left: 5px;
 	cursor: pointer;
@@ -980,8 +979,7 @@ div.mejs-speed-button {
 /* Start: Jump Forward */
 
 .mejs-controls .mejs-button.mejs-jump-forward-button {
-	background: transparent url(jumpforward.png) no-repeat;
-	background-position: 3px 3px;
+	background: transparent url("jumpforward.png") no-repeat 3px 3px;
 }
 .mejs-controls .mejs-button.mejs-jump-forward-button button {
 	background: transparent;
@@ -995,8 +993,7 @@ div.mejs-speed-button {
 /* Start: Skip Back */
 
 .mejs-controls .mejs-button.mejs-skip-back-button {
-	background: transparent url(skipback.png) no-repeat;
-	background-position: 3px 3px;
+	background: transparent url("skipback.png") no-repeat 3px 3px;
 }
 .mejs-controls .mejs-button.mejs-skip-back-button button {
 	background: transparent;

+ 25 - 9
app/Resources/public/assets/mediaelement/build/mediaelementplayer.js

@@ -363,7 +363,7 @@ if (typeof jQuery != 'undefined') {
 					.addClass(t.$media[0].className)
 					.insertBefore(t.$media)
 					.focus(function ( e ) {
-						if( !t.controlsAreVisible && !t.hasFocus ) {
+						if( !t.controlsAreVisible && !t.hasFocus && t.controlsEnabled) {
 							t.showControls(true);
 							// In versions older than IE11, the focus causes the playbar to be displayed
 							// if user clicks on the Play/Pause button in the control bar once it attempts
@@ -665,6 +665,10 @@ if (typeof jQuery != 'undefined') {
 								} else {
 									t.pause();
 								}
+
+								var button = t.$media.closest('.mejs-container').find('.mejs-overlay-button'),
+									pressed = button.attr('aria-pressed');
+								button.attr('aria-pressed', !pressed);
 							}
 						};
 
@@ -811,7 +815,10 @@ if (typeof jQuery != 'undefined') {
 						var $target = $(e.relatedTarget);
 						if (t.keyboardAction && $target.parents('.mejs-container').length === 0) {
 							t.keyboardAction = false;
-							t.hideControls(true);
+							if (t.isVideo && !t.options.alwaysShowControls) {
+								t.hideControls(true);
+							}
+
 						}
 					}
 				});
@@ -1180,7 +1187,7 @@ if (typeof jQuery != 'undefined') {
 			// this needs to come last so it's on top
 			bigPlay =
 				$('<div class="mejs-overlay mejs-layer mejs-overlay-play">'+
-					'<div class="mejs-overlay-button"></div>'+
+					'<div class="mejs-overlay-button" role="button" aria-label="' + mejs.i18n.t('Play') + '" aria-pressed="false"></div>'+
 				'</div>')
 				.appendTo(layers)
 				.bind('click', function() {	 // Removed 'touchstart' due issues on Samsung Android devices where a tap on bigPlay started and immediately stopped the video
@@ -1188,6 +1195,10 @@ if (typeof jQuery != 'undefined') {
 						if (media.paused) {
 							media.play();
 						}
+
+						var button = $(this).find('.mejs-overlay-button'),
+							pressed = button.attr('aria-pressed');
+						button.attr('aria-pressed', !!pressed);
 					}
 				});
 
@@ -1740,8 +1751,8 @@ if (typeof jQuery != 'undefined') {
 				var keyCode = e.keyCode,
 					duration = media.duration,
 					seekTime = media.currentTime,
-					seekForward  = player.options.defaultSeekForwardInterval(duration),
-					seekBackward = player.options.defaultSeekBackwardInterval(duration);
+					seekForward  = player.options.defaultSeekForwardInterval(media),
+					seekBackward = player.options.defaultSeekBackwardInterval(media);
 
 				switch (keyCode) {
 				case 37: // left
@@ -1921,7 +1932,10 @@ if (typeof jQuery != 'undefined') {
 			t.currenttime = t.controls.find('.mejs-currenttime');
 
 			media.addEventListener('timeupdate',function() {
-				player.updateCurrent();
+				if (t.controlsAreVisible) {
+					player.updateCurrent();
+				}
+
 			}, false);
 		},
 
@@ -1951,7 +1965,9 @@ if (typeof jQuery != 'undefined') {
 			t.durationD = t.controls.find('.mejs-duration');
 
 			media.addEventListener('timeupdate',function() {
-				player.updateDuration();
+				if (t.controlsAreVisible) {
+					player.updateDuration();
+				}
 			}, false);
 		},
 		
@@ -2579,8 +2595,8 @@ if (typeof jQuery != 'undefined') {
 
 			var t = this;
 
-			if (mejs.MediaFeatures.hasiOSFullScreen) {
-				t.media.webkitEnterFullscreen();
+			if (mejs.MediaFeatures.isiOS && mejs.MediaFeatures.hasiOSFullScreen && typeof t.media.webkitEnterFullscreen === 'function') {
+			    t.media.webkitEnterFullscreen();
 				return;
 			}
 

文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/mediaelement/build/mediaelementplayer.min.css


文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/mediaelement/build/mediaelementplayer.min.js


+ 12 - 1
app/Resources/public/assets/mediaelement/changelog.md

@@ -1,5 +1,16 @@
 ### Version History
 
+*2.22.1 (2016/08/18)*
+
+* Fix wrong parameter sent to defaultSeekForwardInterval and defaultSeekBackwardInterval options (https://github.com/johndyer/mediaelement/pull/1784) @marjune163
+* Fullscreen in Safari Mac must show player skin (https://github.com/johndyer/mediaelement/pull/1794) @ale-grosselle
+* Fix for aspect ratio when playing Hls Flash video if m3u8 does not have a resolution indicated (#1790) (https://github.com/johndyer/mediaelement/pull/1791) @ale-grosselle
+* Only focus the play button when the control bar is not hidden (https://github.com/johndyer/mediaelement/pull/1798) @schrolli
+* Avoid hiding audio controls when tabbing (#1668) (https://github.com/johndyer/mediaelement/pull/1804) @ron666
+* Stylesheet minor fixes (https://github.com/johndyer/mediaelement/pull/1808) @ron666
+* Time update on rail only when controls are visible (#1683) (https://github.com/johndyer/mediaelement/pull/1801) @ron666
+* ARIA attributes for big play button to continue improving player accessibility (https://github.com/johndyer/mediaelement/pull/1803) @ron666
+
 *2.22.0 (2016/07/17)*
 
 * Introduce new stretching models (stretching: 'fill','responsive') (https://github.com/johndyer/mediaelement/pull/1760) @ron666
@@ -858,4 +869,4 @@ Fixes and updates
 * thin line when controls are off
 * system-wide events
 * Ogg/Theora playback
-* Better alignment with native MediaElement (using shimichanga.com techniques)
+* Better alignment with native MediaElement (using shimichanga.com techniques)

+ 4 - 4
app/Resources/public/assets/pwstrength-bootstrap/.bower.json

@@ -1,6 +1,6 @@
 {
   "name": "pwstrength-bootstrap",
-  "version": "2.0.1",
+  "version": "2.0.2",
   "homepage": "https://github.com/ablanco/jquery.pwstrength.bootstrap",
   "authors": [
     "Alejandro Blanco <alejandro.b.e@gmail.com>"
@@ -35,11 +35,11 @@
     "Gruntfile.js",
     "package.json"
   ],
-  "_release": "2.0.1",
+  "_release": "2.0.2",
   "_resolution": {
     "type": "version",
-    "tag": "2.0.1",
-    "commit": "b132030c05fc2d979590dd44b37a14deb1330745"
+    "tag": "2.0.2",
+    "commit": "cafffc55da15c4e9c2c7ee32fa39231c460ef718"
   },
   "_source": "https://github.com/ablanco/jquery.pwstrength.bootstrap.git",
   "_target": "*",

+ 11 - 1
app/Resources/public/assets/pwstrength-bootstrap/OPTIONS.md

@@ -56,6 +56,16 @@ Let's see the options of each section.
   This handler will also be called when the `change` or the `onpaste` events
   happen.
 
+* __onScore__:
+
+  Default: `undefined` (Function)
+
+  A callback function that will be called when the score is calculted by the
+  rules engine, allowing for a final modification before rendering the result.
+
+  The options, the word and the score will be passed as arguments, in that
+  order.
+
 * __zxcvbn__:
 
   Default: `false` (Boolean)
@@ -352,7 +362,7 @@ Let's see the options of each section.
 ```javascript
 var options = {};
 options.common = {
-    minChar: 8;
+    minChar: 8
 };
 options.rules = {
     activated: {

+ 13 - 1
app/Resources/public/assets/pwstrength-bootstrap/README.md

@@ -133,7 +133,7 @@ That would avoid looking for sequences in the password being tested.
 
 ## Callback Functions
 
-The plugin provides two callback functions, onLoad and onKeyUp.  You can use
+The plugin provides three callback functions, onLoad, onKeyUp, and scoreCalculated.  You can use
 them like this:
 
 ```javascript
@@ -145,6 +145,18 @@ $(document).ready(function () {
         },
         onKeyUp: function (evt, data) {
             $("#length-help-text").text("Current length: " + $(evt.target).val().length + " and score: " + data.score);
+        },
+        onScore: function (options, word, totalScoreCalculated) {
+            // If my word meets a specific scenario, I want the min score to
+            // be the level 1 score, for example.
+            if (word.length === 20 && totalScoreCalculated < options.ui.scores[1]) {
+                // Score doesn't meet the score[1]. So we will return the min
+                // numbers of points to get that score instead.
+                return options.ui.score[1]
+            }
+            // Fall back to the score that was calculated by the rules engine.
+            // Must pass back the score to set the total score variable.
+            return totalScoreCalculated;
         }
     };
     $(':password').pwstrength(options);

+ 1 - 1
app/Resources/public/assets/pwstrength-bootstrap/bower.json

@@ -1,6 +1,6 @@
 {
   "name": "pwstrength-bootstrap",
-  "version": "2.0.1",
+  "version": "2.0.2",
   "homepage": "https://github.com/ablanco/jquery.pwstrength.bootstrap",
   "authors": [
     "Alejandro Blanco <alejandro.b.e@gmail.com>"

+ 6 - 1
app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.js

@@ -1,6 +1,6 @@
 /*!
 * jQuery Password Strength plugin for Twitter Bootstrap
-* Version: 2.0.1
+* Version: 2.0.2
 *
 * Copyright (c) 2008-2013 Tane Piper
 * Copyright (c) 2013 Alejandro Blanco
@@ -198,6 +198,10 @@ try {
             }
         });
 
+        if ($.isFunction(options.common.onScore)) {
+            totalScore = options.common.onScore(options, word, totalScore);
+        }
+
         return totalScore;
     };
 }(jQuery, rulesEngine));
@@ -223,6 +227,7 @@ defaultOptions.common.userInputs = [
 ];
 defaultOptions.common.onLoad = undefined;
 defaultOptions.common.onKeyUp = undefined;
+defaultOptions.common.onScore = undefined;
 defaultOptions.common.zxcvbn = false;
 defaultOptions.common.zxcvbnTerms = [
     // List of disrecommended words

文件差異過大導致無法顯示
+ 1 - 1
app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.min.js


文件差異過大導致無法顯示
+ 0 - 0
app/Resources/public/assets/pwstrength-bootstrap/dist/pwstrength-bootstrap.min.map


+ 1 - 0
app/Resources/public/assets/pwstrength-bootstrap/src/options.js

@@ -19,6 +19,7 @@ defaultOptions.common.userInputs = [
 ];
 defaultOptions.common.onLoad = undefined;
 defaultOptions.common.onKeyUp = undefined;
+defaultOptions.common.onScore = undefined;
 defaultOptions.common.zxcvbn = false;
 defaultOptions.common.zxcvbnTerms = [
     // List of disrecommended words

+ 4 - 0
app/Resources/public/assets/pwstrength-bootstrap/src/rules.js

@@ -153,6 +153,10 @@ try {
             }
         });
 
+        if ($.isFunction(options.common.onScore)) {
+            totalScore = options.common.onScore(options, word, totalScore);
+        }
+
         return totalScore;
     };
 }(jQuery, rulesEngine));

+ 34 - 0
app/Resources/public/assets/readmore-js/.bower.json

@@ -0,0 +1,34 @@
+{
+  "name": "Readmore.js",
+  "main": "readmore.min.js",
+  "version": "2.1.0",
+  "homepage": "http://jedfoster.com/Readmore.js/",
+  "authors": [
+    "Jed Foster <jed@jedfoster.com>"
+  ],
+  "description": "A lightweight jQuery plugin for collapsing and expanding long blocks of text with \"Read more\" and \"Close\" links.",
+  "keywords": [
+    "css",
+    "jquery",
+    "readmore",
+    "expand",
+    "collapse"
+  ],
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ],
+  "_release": "2.1.0",
+  "_resolution": {
+    "type": "version",
+    "tag": "v2.1.0",
+    "commit": "f0c27b356dea9b54968bc2734cb9be0695d20c1a"
+  },
+  "_source": "https://github.com/jedfoster/Readmore.js.git",
+  "_target": "2.1.0",
+  "_originalSource": "readmore-js"
+}

+ 36 - 0
app/Resources/public/assets/readmore-js/CHANGELOG.md

@@ -0,0 +1,36 @@
+# 2.0.0
+
+## New features
+
+- Install with Bower: `bower install readmore`
+- Blocks can now be toggled programmatically: `$('article:nth-of-type(3)').readmore('toggle')`
+- ARIA semantics describe expanded state and relationship between blocks and their toggles
+- Blocks are now assigned an ID if they don't already have one
+- Install development dependencies with NPM
+- Gulp task to minifiy with UglifyJS 
+
+## Improvements
+
+- Height calculations on window resize are "debounced", resulting in more efficient rendering
+- Height calculation in general has been improved
+- The value of the `expanded` argument passed to the `beforeToggle` callback now correctly reflects the _pre-toggle_ state
+- Multiple instances are now fully supported: e.g. `$('article').readmore({speed: 200})` and `$('fieldset').readmore({speed: 900})` will work on the same page
+- Fully responsive, plugin now prefers max-heights set in CSS, even inside media queries
+
+## Potentially breaking changes
+
+- `maxHeight` option is now `collapsedHeight`
+- `sectionCSS` option is now `blockCSS`
+- `toggleSlider()` method is now just `toggle()`
+- Animation is now performed with CSS3 transitions, rather than `jQuery.animate()`
+- IE 8 and 9 are no longer supported, because those browsers hate kittens
+- `init()` is now called within a `window.onload` event handler, which can briefly delay collapsing content
+- `setBoxHeight()` is now a "private" method called `setBoxHeights()`
+- `resizeBoxes()` is also now private
+- Readmore.js now uses attribute selectors, rather than classes
+    - The `.readmore-js-section` and `.readmore-js-toggle` classes are gone
+    - The `expandedClass` and `collapsedClass` options are also gone
+    - Every Readmore.js block needs an ID, if one is not already present, one will be generated 
+
+
+

+ 22 - 0
app/Resources/public/assets/readmore-js/LICENSE

@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Jed Foster
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+

+ 177 - 0
app/Resources/public/assets/readmore-js/README.md

@@ -0,0 +1,177 @@
+# Readmore.js
+
+A smooth, responsive jQuery plugin for collapsing and expanding long blocks of text with "Read more" and "Close" links.
+
+The markup Readmore.js requires is so simple, you can probably use it with your existing HTML—there's no need for complicated sets of `div`'s or hardcoded classes, just call `.readmore()` on the element containing your block of text and Readmore.js takes care of the rest. Readmore.js plays well in a responsive environment, too.
+
+Readmore.js is tested with—and supported on—all versions of jQuery greater than 1.9.1. All the "good" browsers are supported, as well as IE10+; IE8 & 9 _should_ work, but are not supported and the experience will not be ideal.
+
+
+## Install
+
+Install Readmore.js with npm:
+
+```
+$ npm install readmore-js
+```
+
+Then include it in your HTML:
+
+```html
+<script src="/node_modules/readmore-js/readmore.min.js"></script>
+```
+
+Or, using Webpack or Browserify
+
+```javascript
+require('readmore-js');
+```
+
+
+## Use
+
+```javascript
+$('article').readmore();
+```
+
+It's that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements.
+
+```javascript
+$('article').readmore({
+  speed: 75,
+  lessLink: '<a href="#">Read less</a>'
+});
+```
+
+### The options:
+
+* `speed: 100` in milliseconds
+* `collapsedHeight: 200` in pixels
+* `heightMargin: 16` in pixels, avoids collapsing blocks that are only slightly larger than `collapsedHeight`
+* `moreLink: '<a href="#">Read more</a>'`
+* `lessLink: '<a href="#">Close</a>'`
+* `embedCSS: true` insert required CSS dynamically, set this to `false` if you include the necessary CSS in a stylesheet
+* `blockCSS: 'display: block; width: 100%;'` sets the styling of the blocks, ignored if `embedCSS` is `false`
+* `startOpen: false` do not immediately truncate, start in the fully opened position
+* `beforeToggle: function() {}` called after a more or less link is clicked, but *before* the block is collapsed or expanded
+* `afterToggle: function() {}` called *after* the block is collapsed or expanded
+
+If the element has a `max-height` CSS property, Readmore.js will use that value rather than the value of the `collapsedHeight` option.
+
+### The callbacks:
+
+The callback functions, `beforeToggle` and `afterToggle`, both receive the same arguments: `trigger`, `element`, and `expanded`.
+
+* `trigger`: the "Read more" or "Close" element that was clicked
+* `element`: the block that is being collapsed or expanded
+* `expanded`: Boolean; `true` means the block is expanded
+
+#### Callback example:
+
+Here's an example of how you could use the `afterToggle` callback to scroll back to the top of a block when the "Close" link is clicked.
+
+```javascript
+$('article').readmore({
+  afterToggle: function(trigger, element, expanded) {
+    if(! expanded) { // The "Close" link was clicked
+      $('html, body').animate( { scrollTop: element.offset().top }, {duration: 100 } );
+    }
+  }
+});
+```
+
+### Removing Readmore:
+
+You can remove the Readmore.js functionality like so:
+
+```javascript
+$('article').readmore('destroy');
+```
+
+Or, you can be more surgical by specifying a particular element:
+
+```javascript
+$('article:first').readmore('destroy');
+```
+
+### Toggling blocks programmatically:
+
+You can toggle a block from code:
+
+```javascript
+$('article:nth-of-type(3)').readmore('toggle');
+```
+
+
+## CSS:
+
+Readmore.js is designed to use CSS for as much functionality as possible: collapsed height can be set in CSS with the `max-height` property; "collapsing" is achieved by setting `overflow: hidden` on the containing block and changing the `height` property; and, finally, the expanding/collapsing animation is done with CSS3 transitions.
+
+By default, Readmore.js inserts the following CSS, in addition to some transition-related rules:
+
+```css
+selector + [data-readmore-toggle], selector[data-readmore] {
+  display: block;
+  width: 100%;
+}
+```
+
+_`selector` would be the element you invoked `readmore()` on, e.g.: `$('selector').readmore()`_
+
+You can override the base rules when you set up Readmore.js like so:
+
+```javascript
+$('article').readmore({blockCSS: 'display: inline-block; width: 50%;'});
+```
+
+If you want to include the necessary styling in your site's stylesheet, you can disable the dynamic embedding by setting `embedCSS` to `false`:
+
+```javascript
+$('article').readmore({embedCSS: false});
+```
+
+### Media queries and other CSS tricks:
+
+If you wanted to set a `maxHeight` based on lines, you could do so in CSS with something like:
+
+```css
+body {
+  font: 16px/1.5 sans-serif;
+}
+
+/* Show only 4 lines in smaller screens */
+article {
+  max-height: 6em; /* (4 * 1.5 = 6) */
+}
+```
+
+Then, with a media query you could change the number of lines shown, like so:
+
+```css
+/* Show 8 lines on larger screens */
+@media screen and (min-width: 640px) {
+  article {
+    max-height: 12em;
+  }
+}
+```
+
+
+## Contributing
+
+Pull requests are always welcome, but not all suggested features will get merged. Feel free to contact me if you have an idea for a feature.
+
+Pull requests should include the minified script and this readme and the demo HTML should be updated with descriptions of your new feature. 
+
+You'll need NPM:
+
+```
+$ npm install
+```
+
+Which will install the necessary development dependencies. Then, to build the minified script:
+
+```
+$ gulp compress
+```
+

文件差異過大導致無法顯示
+ 25 - 0
app/Resources/public/assets/readmore-js/ajax-demo.html


+ 25 - 0
app/Resources/public/assets/readmore-js/bower.json

@@ -0,0 +1,25 @@
+{
+  "name": "Readmore.js",
+  "main": "readmore.min.js",
+  "version": "2.1.0",
+  "homepage": "http://jedfoster.com/Readmore.js/",
+  "authors": [
+    "Jed Foster <jed@jedfoster.com>"
+  ],
+  "description": "A lightweight jQuery plugin for collapsing and expanding long blocks of text with \"Read more\" and \"Close\" links.",
+  "keywords": [
+    "css",
+    "jquery",
+    "readmore",
+    "expand",
+    "collapse"
+  ],
+  "license": "MIT",
+  "ignore": [
+    "**/.*",
+    "node_modules",
+    "bower_components",
+    "test",
+    "tests"
+  ]
+}

+ 235 - 0
app/Resources/public/assets/readmore-js/demo.html

@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+
+<html lang="en" class="no-js">
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+
+  <title>Readmore.js: jQuery plugin for long blocks of text</title>
+  <meta name="description" content="A smooth, lightweight jQuery plugin for collapsing and expanding long blocks of text with &#8220;Read more&#8221; and &#8220;Close&#8221; links.">
+  <meta name="author" content="Jed Foster">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+  <!--[if lt IE 9]>
+    <script src="//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.min.js"></script>
+  <![endif]-->
+
+  <style media="screen">
+    body { font: 16px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; color: #444; }
+    code { color: #777; font-family: "Source Code Pro", "Menlo", "Courier New", monospace;}
+    a { color: #178DB1; }
+    .container { margin: 0 auto; max-width: 960px; }
+    #info + .readmore-js-toggle { padding-bottom: 1.5em; border-bottom: 1px solid #999; font-weight: bold;}
+    #demo { padding: 0 10%; }
+  </style>
+</head>
+
+<body>
+
+  <div class="container">
+    <header>
+      <h1>Readmore.js</h1>
+
+      <p>A smooth, responsive jQuery plugin for collapsing and expanding long blocks of text with &#8220;Read more&#8221; and &#8220;Close&#8221; links.</p>
+    </header>
+
+    <section id="info">
+      <p>The markup Readmore.js requires is so simple, you can probably use it with your existing HTML—there&#8217;s no need for complicated sets of <code>div</code>&#8217;s or hardcoded classes, just call <code>.readmore()</code> on the element containing your block of text and Readmore.js takes care of the rest. Readmore.js plays well in a responsive environment, too.</p>
+
+      <p>Readmore.js is tested with—and supported on—all versions of jQuery greater than 1.9.1. All the &#8220;good&#8221; browsers are supported, as well as IE10+; IE8 &amp; 9 <em>should</em> work, but are not supported and the experience will not be ideal.</p>
+
+      <h2 id="install">Install</h2>
+
+      <p>Install Readmore.js with npm:</p>
+
+      <pre><code>$ npm install readmore-js</code></pre>
+
+      <p>Then include it in your HTML:</p>
+
+      <pre><code class="html">&lt;script src=&quot;/node_modules/readmore-js/readmore.min.js&quot;&gt;&lt;/script&gt;</code></pre>
+
+      <p>Or, using Webpack or Browserify:</p>
+
+      <pre><code class="javascript">require('readmore-js');</code></pre>
+
+      <h2 id="use">Use</h2>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore();</code></pre>
+
+      <p>It&#8217;s that simple. You can change the speed of the animation, the height of the collapsed block, and the open and close elements.</p>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore({
+  speed: 75,
+  lessLink: &apos;&lt;a href=&quot;#&quot;&gt;Read less&lt;/a&gt;&apos;
+});</code></pre>
+
+      <h3 id="theoptions">The options:</h3>
+
+      <ul>
+        <li><code>speed: 100</code> in milliseconds</li>
+        <li><code>collapsedHeight: 200</code> in pixels</li>
+        <li><code>heightMargin: 16</code> in pixels, avoids collapsing blocks that are only slightly larger than <code>collapsedHeight</code></li>
+        <li><code>moreLink: '&lt;a href=&quot;#&quot;&gt;Read more&lt;/a&gt;'</code></li>
+        <li><code>lessLink: '&lt;a href=&quot;#&quot;&gt;Close&lt;/a&gt;'</code></li>
+        <li><code>embedCSS: true</code> insert required CSS dynamically, set this to <code>false</code> if you include the necessary CSS in a stylesheet</li>
+        <li><code>blockCSS: 'display: block; width: 100%;'</code> sets the styling of the blocks, ignored if <code>embedCSS</code> is <code>false</code></li>
+        <li><code>startOpen: false</code> do not immediately truncate, start in the fully opened position</li>
+        <li><code>beforeToggle: function() {}</code> called after a more or less link is clicked, but <em>before</em> the block is collapsed or expanded</li>
+        <li><code>afterToggle: function() {}</code> called <em>after</em> the block is collapsed or expanded</li>
+      </ul>
+
+      <p>If the element has a <code>max-height</code> CSS property, Readmore.js will use that value rather than the value of the <code>collapsedHeight</code> option.</p>
+
+      <h3 id="thecallbacks">The callbacks:</h3>
+
+      <p>The callback functions, <code>beforeToggle</code> and <code>afterToggle</code>, both receive the same arguments: <code>trigger</code>, <code>element</code>, and <code>expanded</code>.</p>
+
+      <ul>
+        <li><code>trigger</code>: the &#8220;Read more&#8221; or &#8220;Close&#8221; element that was clicked</li>
+        <li><code>element</code>: the block that is being collapsed or expanded</li>
+        <li><code>expanded</code>: Boolean; <code>true</code> means the block is expanded</li>
+      </ul>
+
+      <h4 id="callbackexample">Callback example:</h4>
+
+      <p>Here&#8217;s an example of how you could use the <code>afterToggle</code> callback to scroll back to the top of a block when the &#8220;Close&#8221; link is clicked.</p>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore({
+  afterToggle: function(trigger, element, expanded) {
+    if(! expanded) { // The &quot;Close&quot; link was clicked
+      $(&apos;html, body&apos;).animate( { scrollTop: element.offset().top }, {duration: 100 } );
+    }
+  }
+});</code></pre>
+
+      <h3 id="removingreadmore">Removing Readmore:</h3>
+
+      <p>You can remove the Readmore.js functionality like so:</p>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore(&apos;destroy&apos;);</code></pre>
+
+      <p>Or, you can be more surgical by specifying a particular element:</p>
+
+      <pre><code class="javascript">$(&apos;article:first&apos;).readmore(&apos;destroy&apos;);</code></pre>
+
+      <h3 id="togglingblocksprogrammatically">Toggling blocks programmatically:</h3>
+
+      <p>You can toggle a block from code:</p>
+
+      <pre><code class="javascript">$(&apos;article:nth-of-type(3)&apos;).readmore(&apos;toggle&apos;);</code></pre>
+
+      <h2 id="css">CSS:</h2>
+
+      <p>Readmore.js is designed to use CSS for as much functionality as possible: collapsed height can be set in CSS with the <code>max-height</code> property; &#8220;collapsing&#8221; is achieved by setting <code>overflow: hidden</code> on the containing block and changing the <code>height</code> property; and, finally, the expanding/collapsing animation is done with CSS3 transitions.</p>
+
+      <p>By default, Readmore.js inserts the following CSS, in addition to some transition-related rules:</p>
+
+      <pre><code class="css">selector + [data-readmore-toggle], selector[data-readmore] {
+  display: block;
+  width: 100%;
+}</code></pre>
+
+      <p><em><code>selector</code> would be the element you invoked <code>readmore()</code> on, e.g.: <code>$('selector').readmore()</code></em></p>
+
+      <p>You can override the base rules when you set up Readmore.js like so:</p>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore({blockCSS: &apos;display: inline-block; width: 50%;&apos;});</code></pre>
+
+      <p>If you want to include the necessary styling in your site&#8217;s stylesheet, you can disable the dynamic embedding by setting <code>embedCSS</code> to <code>false</code>:</p>
+
+      <pre><code class="javascript">$(&apos;article&apos;).readmore({embedCSS: false});</code></pre>
+
+      <h3 id="mediaqueriesandothercsstricks">Media queries and other CSS tricks:</h3>
+
+      <p>If you wanted to set a <code>maxHeight</code> based on lines, you could do so in CSS with something like:</p>
+
+      <pre><code class="css">body {
+  font: 16px/1.5 sans-serif;
+}
+
+/* Show only 4 lines in smaller screens */
+article {
+  max-height: 6em; /* (4 * 1.5 = 6) */
+}</code></pre>
+
+      <p>Then, with a media query you could change the number of lines shown, like so:</p>
+
+      <pre><code class="css">/* Show 8 lines on larger screens */
+@media screen and (min-width: 640px) {
+  article {
+    max-height: 12em;
+  }
+}</code></pre>
+
+      <h2 id="contributing">Contributing</h2>
+
+      <p>Pull requests are always welcome, but not all suggested features will get merged. Feel free to contact me if you have an idea for a feature.</p>
+
+      <p>Pull requests should include the minified script and this readme and the demo HTML should be updated with descriptions of your new feature. </p>
+
+      <p>You&#8217;ll need NPM:</p>
+
+      <pre><code>$ npm install</code></pre>
+
+      <p>Which will install the necessary development dependencies. Then, to build the minified script:</p>
+
+      <pre><code>$ gulp compress</code></pre>
+    </section>
+
+    <h1>Demo</h1>
+
+    <section id="demo">
+
+      <article>
+        <h2>Artisanal Narwahls</h2>
+
+        <p>From this distant vantage point, the Earth might not seem of any particular interest. But for us, it's different. Consider again that dot. That's here. That's home. That's us. On it everyone you love, everyone you know, everyone you ever heard of, every human being who ever was, lived out their lives. The aggregate of our joy and suffering, thousands of confident religions, ideologies, and economic doctrines, every hunter and forager, every hero and coward, every creator and destroyer of civilization, every king and peasant, every young couple in love, every mother and father, hopeful child, inventor and explorer, every teacher of morals, every corrupt politician, every "superstar," every "supreme leader," every saint and sinner in the history of our species lived there – on a mote of dust suspended in a sunbeam.</p>
+
+        <p>Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!</p>
+
+        <p>Here's how it is: Earth got used up, so we terraformed a whole new galaxy of Earths, some rich and flush with the new technologies, some not so much. Central Planets, them was formed the Alliance, waged war to bring everyone under their rule; a few idiots tried to fight it, among them myself. I'm Malcolm Reynolds, captain of Serenity. Got a good crew: fighters, pilot, mechanic. We even picked up a preacher, and a bona fide companion. There's a doctor, too, took his genius sister out of some Alliance camp, so they're keeping a low profile. You got a job, we can do it, don't much care what it is.</p>
+
+        <p>Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!</p>
+      </article>
+
+      <article>
+        <h2>Portland Leggings</h2>
+
+        <p>Here's how it is: Earth got used up, so we terraformed a whole new galaxy of Earths, some rich and flush with the new technologies, some not so much. Central Planets, them was formed the Alliance, waged war to bring everyone under their rule; a few idiots tried to fight it, among them myself. I'm Malcolm Reynolds, captain of Serenity. Got a good crew: fighters, pilot, mechanic. We even picked up a preacher, and a bona fide companion. There's a doctor, too, took his genius sister out of some Alliance camp, so they're keeping a low profile. You got a job, we can do it, don't much care what it is.</p>
+
+        <p>I am Duncan Macleod, born 400 years ago in the Highlands of Scotland. I am Immortal, and I am not alone. For centuries, we have waited for the time of the Gathering when the stroke of a sword and the fall of a head will release the power of the Quickening. In the end, there can be only one.</p>
+
+        <p>From this distant vantage point, the Earth might not seem of any particular interest. But for us, it's different. Consider again that dot. That's here. That's home. That's us. On it everyone you love, everyone you know, everyone you ever heard of, every human being who ever was, lived out their lives. The aggregate of our joy and suffering, thousands of confident religions, ideologies, and economic doctrines, every hunter and forager, every hero and coward, every creator and destroyer of civilization, every king and peasant, every young couple in love, every mother and father, hopeful child, inventor and explorer, every teacher of morals, every corrupt politician, every "superstar," every "supreme leader," every saint and sinner in the history of our species lived there – on a mote of dust suspended in a sunbeam.</p>
+
+        <p>Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!</p>
+      </article>
+
+      <article>
+        <h2>This section is shorter than the Readmore minimum</h2>
+
+        <p>Space, the final frontier. These are the voyages of the starship Enterprise. Its five year mission: to explore strange new worlds, to seek out new life and new civilizations, to boldly go where no man has gone before!</p>
+      </article>
+    </section>
+  </div>
+
+  <script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
+
+  <script src="readmore.js"></script>
+
+  <script>
+    $('#info').readmore({
+      moreLink: '<a href="#">Usage, examples, and options</a>',
+      collapsedHeight: 384,
+      afterToggle: function(trigger, element, expanded) {
+        if(! expanded) { // The "Close" link was clicked
+          $('html, body').animate({scrollTop: element.offset().top}, {duration: 100});
+        }
+      }
+    });
+
+    $('article').readmore({speed: 500});
+  </script>
+</body>
+</html>
+

+ 14 - 0
app/Resources/public/assets/readmore-js/gulpfile.js

@@ -0,0 +1,14 @@
+var gulp = require('gulp'),
+    uglify = require('gulp-uglify'),
+    rename = require('gulp-rename');
+
+gulp.task('compress', function() {
+  gulp.src('readmore.js')
+    .pipe(uglify({
+      mangle: true,
+      compress: true,
+      preserveComments: 'some'
+    }))
+    .pipe(rename('readmore.min.js'))
+    .pipe(gulp.dest('./'));
+});

+ 692 - 0
app/Resources/public/assets/readmore-js/jquery.mockjax.js

@@ -0,0 +1,692 @@
+/*!
+ * MockJax - jQuery Plugin to Mock Ajax requests
+ *
+ * Version:  1.6.1
+ * Released:
+ * Home:   https://github.com/jakerella/jquery-mockjax
+ * Author:   Jonathan Sharp (http://jdsharp.com)
+ * License:  MIT,GPL
+ *
+ * Copyright (c) 2014 appendTo, Jordan Kasper
+ * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014
+ * 
+ * Dual licensed under the MIT or GPL licenses.
+ * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html
+ */
+(function($) {
+	var _ajax = $.ajax,
+		mockHandlers = [],
+		mockedAjaxCalls = [],
+		unmockedAjaxCalls = [],
+		CALLBACK_REGEX = /=\?(&|$)/,
+		jsc = (new Date()).getTime();
+
+
+	// Parse the given XML string.
+	function parseXML(xml) {
+		if ( window.DOMParser == undefined && window.ActiveXObject ) {
+			DOMParser = function() { };
+			DOMParser.prototype.parseFromString = function( xmlString ) {
+				var doc = new ActiveXObject('Microsoft.XMLDOM');
+				doc.async = 'false';
+				doc.loadXML( xmlString );
+				return doc;
+			};
+		}
+
+		try {
+			var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
+			if ( $.isXMLDoc( xmlDoc ) ) {
+				var err = $('parsererror', xmlDoc);
+				if ( err.length == 1 ) {
+					throw new Error('Error: ' + $(xmlDoc).text() );
+				}
+			} else {
+				throw new Error('Unable to parse XML');
+			}
+			return xmlDoc;
+		} catch( e ) {
+			var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
+			$(document).trigger('xmlParseError', [ msg ]);
+			return undefined;
+		}
+	}
+
+	// Check if the data field on the mock handler and the request match. This
+	// can be used to restrict a mock handler to being used only when a certain
+	// set of data is passed to it.
+	function isMockDataEqual( mock, live ) {
+		var identical = true;
+		// Test for situations where the data is a querystring (not an object)
+		if (typeof live === 'string') {
+			// Querystring may be a regex
+			return $.isFunction( mock.test ) ? mock.test(live) : mock == live;
+		}
+		$.each(mock, function(k) {
+			if ( live[k] === undefined ) {
+				identical = false;
+				return identical;
+			} else {
+				if ( typeof live[k] === 'object' && live[k] !== null ) {
+					if ( identical && $.isArray( live[k] ) ) {
+						identical = $.isArray( mock[k] ) && live[k].length === mock[k].length;
+					}
+					identical = identical && isMockDataEqual(mock[k], live[k]);
+				} else {
+					if ( mock[k] && $.isFunction( mock[k].test ) ) {
+						identical = identical && mock[k].test(live[k]);
+					} else {
+						identical = identical && ( mock[k] == live[k] );
+					}
+				}
+			}
+		});
+
+		return identical;
+	}
+
+    // See if a mock handler property matches the default settings
+    function isDefaultSetting(handler, property) {
+        return handler[property] === $.mockjaxSettings[property];
+    }
+
+	// Check the given handler should mock the given request
+	function getMockForRequest( handler, requestSettings ) {
+		// If the mock was registered with a function, let the function decide if we
+		// want to mock this request
+		if ( $.isFunction(handler) ) {
+			return handler( requestSettings );
+		}
+
+		// Inspect the URL of the request and check if the mock handler's url
+		// matches the url for this ajax request
+		if ( $.isFunction(handler.url.test) ) {
+			// The user provided a regex for the url, test it
+			if ( !handler.url.test( requestSettings.url ) ) {
+				return null;
+			}
+		} else {
+			// Look for a simple wildcard '*' or a direct URL match
+			var star = handler.url.indexOf('*');
+			if (handler.url !== requestSettings.url && star === -1 ||
+					!new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace(/\*/g, '.+')).test(requestSettings.url)) {
+				return null;
+			}
+		}
+
+		// Inspect the data submitted in the request (either POST body or GET query string)
+		if ( handler.data ) {
+			if ( ! requestSettings.data || !isMockDataEqual(handler.data, requestSettings.data) ) {
+				// They're not identical, do not mock this request
+				return null;
+			}
+		}
+		// Inspect the request type
+		if ( handler && handler.type &&
+				handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) {
+			// The request type doesn't match (GET vs. POST)
+			return null;
+		}
+
+		return handler;
+	}
+
+	function parseResponseTimeOpt(responseTime) {
+		if ($.isArray(responseTime)) {
+			var min = responseTime[0];
+			var max = responseTime[1];
+			return (typeof min === 'number' && typeof max === 'number') ? Math.floor(Math.random() * (max - min)) + min : null;
+		} else {
+			return (typeof responseTime === 'number') ? responseTime: null;
+		}
+	}
+
+	// Process the xhr objects send operation
+	function _xhrSend(mockHandler, requestSettings, origSettings) {
+
+		// This is a substitute for < 1.4 which lacks $.proxy
+		var process = (function(that) {
+			return function() {
+				return (function() {
+					// The request has returned
+					this.status     = mockHandler.status;
+					this.statusText = mockHandler.statusText;
+					this.readyState	= 1;
+
+					var finishRequest = function () {
+						this.readyState	= 4;
+
+						var onReady;
+						// Copy over our mock to our xhr object before passing control back to
+						// jQuery's onreadystatechange callback
+						if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) {
+							this.responseText = JSON.stringify(mockHandler.responseText);
+						} else if ( requestSettings.dataType == 'xml' ) {
+							if ( typeof mockHandler.responseXML == 'string' ) {
+								this.responseXML = parseXML(mockHandler.responseXML);
+								//in jQuery 1.9.1+, responseXML is processed differently and relies on responseText
+								this.responseText = mockHandler.responseXML;
+							} else {
+								this.responseXML = mockHandler.responseXML;
+							}
+						} else if (typeof mockHandler.responseText === 'object' && mockHandler.responseText !== null) {
+							// since jQuery 1.9 responseText type has to match contentType
+							mockHandler.contentType = 'application/json';
+							this.responseText = JSON.stringify(mockHandler.responseText);
+						} else {
+							this.responseText = mockHandler.responseText;
+						}
+						if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) {
+							this.status = mockHandler.status;
+						}
+						if( typeof mockHandler.statusText === "string") {
+							this.statusText = mockHandler.statusText;
+						}
+						// jQuery 2.0 renamed onreadystatechange to onload
+						onReady = this.onreadystatechange || this.onload;
+
+						// jQuery < 1.4 doesn't have onreadystate change for xhr
+						if ( $.isFunction( onReady ) ) {
+							if( mockHandler.isTimeout) {
+								this.status = -1;
+							}
+							onReady.call( this, mockHandler.isTimeout ? 'timeout' : undefined );
+						} else if ( mockHandler.isTimeout ) {
+							// Fix for 1.3.2 timeout to keep success from firing.
+							this.status = -1;
+						}
+					};
+
+					// We have an executable function, call it to give
+					// the mock handler a chance to update it's data
+					if ( $.isFunction(mockHandler.response) ) {
+						// Wait for it to finish
+						if ( mockHandler.response.length === 2 ) {
+							mockHandler.response(origSettings, function () {
+								finishRequest.call(that);
+							});
+							return;
+						} else {
+							mockHandler.response(origSettings);
+						}
+					}
+
+					finishRequest.call(that);
+				}).apply(that);
+			};
+		})(this);
+
+		if ( mockHandler.proxy ) {
+			// We're proxying this request and loading in an external file instead
+			_ajax({
+				global: false,
+				url: mockHandler.proxy,
+				type: mockHandler.proxyType,
+				data: mockHandler.data,
+				dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType,
+				complete: function(xhr) {
+					mockHandler.responseXML = xhr.responseXML;
+					mockHandler.responseText = xhr.responseText;
+                    // Don't override the handler status/statusText if it's specified by the config
+                    if (isDefaultSetting(mockHandler, 'status')) {
+					    mockHandler.status = xhr.status;
+                    }
+                    if (isDefaultSetting(mockHandler, 'statusText')) {
+					    mockHandler.statusText = xhr.statusText;
+                    }
+					this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 0);
+				}
+			});
+		} else {
+			// type == 'POST' || 'GET' || 'DELETE'
+			if ( requestSettings.async === false ) {
+				// TODO: Blocking delay
+				process();
+			} else {
+				this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 50);
+			}
+		}
+	}
+
+	// Construct a mocked XHR Object
+	function xhr(mockHandler, requestSettings, origSettings, origHandler) {
+		// Extend with our default mockjax settings
+		mockHandler = $.extend(true, {}, $.mockjaxSettings, mockHandler);
+
+		if (typeof mockHandler.headers === 'undefined') {
+			mockHandler.headers = {};
+		}
+		if (typeof requestSettings.headers === 'undefined') {
+			requestSettings.headers = {};
+		}
+		if ( mockHandler.contentType ) {
+			mockHandler.headers['content-type'] = mockHandler.contentType;
+		}
+
+		return {
+			status: mockHandler.status,
+			statusText: mockHandler.statusText,
+			readyState: 1,
+			open: function() { },
+			send: function() {
+				origHandler.fired = true;
+				_xhrSend.call(this, mockHandler, requestSettings, origSettings);
+			},
+			abort: function() {
+				clearTimeout(this.responseTimer);
+			},
+			setRequestHeader: function(header, value) {
+				requestSettings.headers[header] = value;
+			},
+			getResponseHeader: function(header) {
+				// 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
+				if ( mockHandler.headers && mockHandler.headers[header] ) {
+					// Return arbitrary headers
+					return mockHandler.headers[header];
+				} else if ( header.toLowerCase() == 'last-modified' ) {
+					return mockHandler.lastModified || (new Date()).toString();
+				} else if ( header.toLowerCase() == 'etag' ) {
+					return mockHandler.etag || '';
+				} else if ( header.toLowerCase() == 'content-type' ) {
+					return mockHandler.contentType || 'text/plain';
+				}
+			},
+			getAllResponseHeaders: function() {
+				var headers = '';
+				// since jQuery 1.9 responseText type has to match contentType
+				if (mockHandler.contentType) {
+					mockHandler.headers['Content-Type'] = mockHandler.contentType;
+				}
+				$.each(mockHandler.headers, function(k, v) {
+					headers += k + ': ' + v + "\n";
+				});
+				return headers;
+			}
+		};
+	}
+
+	// Process a JSONP mock request.
+	function processJsonpMock( requestSettings, mockHandler, origSettings ) {
+		// Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
+		// because there isn't an easy hook for the cross domain script tag of jsonp
+
+		processJsonpUrl( requestSettings );
+
+		requestSettings.dataType = "json";
+		if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
+			createJsonpCallback(requestSettings, mockHandler, origSettings);
+
+			// We need to make sure
+			// that a JSONP style response is executed properly
+
+			var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
+				parts = rurl.exec( requestSettings.url ),
+				remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
+
+			requestSettings.dataType = "script";
+			if(requestSettings.type.toUpperCase() === "GET" && remote ) {
+				var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
+
+				// Check if we are supposed to return a Deferred back to the mock call, or just
+				// signal success
+				if(newMockReturn) {
+					return newMockReturn;
+				} else {
+					return true;
+				}
+			}
+		}
+		return null;
+	}
+
+	// Append the required callback parameter to the end of the request URL, for a JSONP request
+	function processJsonpUrl( requestSettings ) {
+		if ( requestSettings.type.toUpperCase() === "GET" ) {
+			if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
+				requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") +
+					(requestSettings.jsonp || "callback") + "=?";
+			}
+		} else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
+			requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?";
+		}
+	}
+
+	// Process a JSONP request by evaluating the mocked response text
+	function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
+		// Synthesize the mock request for adding a script tag
+		var callbackContext = origSettings && origSettings.context || requestSettings,
+			newMock = null;
+
+
+		// If the response handler on the moock is a function, call it
+		if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
+			mockHandler.response(origSettings);
+		} else {
+
+			// Evaluate the responseText javascript in a global context
+			if( typeof mockHandler.responseText === 'object' ) {
+				$.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
+			} else {
+				$.globalEval( '(' + mockHandler.responseText + ')');
+			}
+		}
+
+		// Successful response
+		setTimeout(function() {
+			jsonpSuccess( requestSettings, callbackContext, mockHandler );
+			jsonpComplete( requestSettings, callbackContext, mockHandler );
+		}, parseResponseTimeOpt(mockHandler.responseTime) || 0);
+
+		// If we are running under jQuery 1.5+, return a deferred object
+		if($.Deferred){
+			newMock = new $.Deferred();
+			if(typeof mockHandler.responseText == "object"){
+				newMock.resolveWith( callbackContext, [mockHandler.responseText] );
+			}
+			else{
+				newMock.resolveWith( callbackContext, [$.parseJSON( mockHandler.responseText )] );
+			}
+		}
+		return newMock;
+	}
+
+
+	// Create the required JSONP callback function for the request
+	function createJsonpCallback( requestSettings, mockHandler, origSettings ) {
+		var callbackContext = origSettings && origSettings.context || requestSettings;
+		var jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++);
+
+		// Replace the =? sequence both in the query string and the data
+		if ( requestSettings.data ) {
+			requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1");
+		}
+
+		requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1");
+
+
+		// Handle JSONP-style loading
+		window[ jsonp ] = window[ jsonp ] || function( tmp ) {
+			data = tmp;
+			jsonpSuccess( requestSettings, callbackContext, mockHandler );
+			jsonpComplete( requestSettings, callbackContext, mockHandler );
+			// Garbage collect
+			window[ jsonp ] = undefined;
+
+			try {
+				delete window[ jsonp ];
+			} catch(e) {}
+
+			if ( head ) {
+				head.removeChild( script );
+			}
+		};
+	}
+
+	// The JSONP request was successful
+	function jsonpSuccess(requestSettings, callbackContext, mockHandler) {
+		// If a local callback was specified, fire it and pass it the data
+		if ( requestSettings.success ) {
+			requestSettings.success.call( callbackContext, mockHandler.responseText || "", status, {} );
+		}
+
+		// Fire the global callback
+		if ( requestSettings.global ) {
+			(requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxSuccess", [{}, requestSettings]);
+		}
+	}
+
+	// The JSONP request was completed
+	function jsonpComplete(requestSettings, callbackContext) {
+		// Process result
+		if ( requestSettings.complete ) {
+			requestSettings.complete.call( callbackContext, {} , status );
+		}
+
+		// The request was completed
+		if ( requestSettings.global ) {
+			(requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxComplete", [{}, requestSettings]);
+		}
+
+		// Handle the global AJAX counter
+		if ( requestSettings.global && ! --$.active ) {
+			$.event.trigger( "ajaxStop" );
+		}
+	}
+
+
+	// The core $.ajax replacement.
+	function handleAjax( url, origSettings ) {
+		var mockRequest, requestSettings, mockHandler, overrideCallback;
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			origSettings = url;
+			url = undefined;
+		} else {
+			// work around to support 1.5 signature
+			origSettings = origSettings || {};
+			origSettings.url = url;
+		}
+
+		// Extend the original settings for the request
+		requestSettings = $.extend(true, {}, $.ajaxSettings, origSettings);
+
+		// Generic function to override callback methods for use with
+		// callback options (onAfterSuccess, onAfterError, onAfterComplete)
+		overrideCallback = function(action, mockHandler) {
+			var origHandler = origSettings[action.toLowerCase()];
+			return function() {
+				if ( $.isFunction(origHandler) ) {
+					origHandler.apply(this, [].slice.call(arguments));
+				}
+				mockHandler['onAfter' + action]();
+			};
+		};
+
+		// Iterate over our mock handlers (in registration order) until we find
+		// one that is willing to intercept the request
+		for(var k = 0; k < mockHandlers.length; k++) {
+			if ( !mockHandlers[k] ) {
+				continue;
+			}
+
+			mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
+			if(!mockHandler) {
+				// No valid mock found for this request
+				continue;
+			}
+
+			mockedAjaxCalls.push(requestSettings);
+
+			// If logging is enabled, log the mock to the console
+			$.mockjaxSettings.log( mockHandler, requestSettings );
+
+
+			if ( requestSettings.dataType && requestSettings.dataType.toUpperCase() === 'JSONP' ) {
+				if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
+					// This mock will handle the JSONP request
+					return mockRequest;
+				}
+			}
+
+
+			// Removed to fix #54 - keep the mocking data object intact
+			//mockHandler.data = requestSettings.data;
+
+			mockHandler.cache = requestSettings.cache;
+			mockHandler.timeout = requestSettings.timeout;
+			mockHandler.global = requestSettings.global;
+
+			// In the case of a timeout, we just need to ensure
+			// an actual jQuery timeout (That is, our reponse won't)
+			// return faster than the timeout setting.
+			if ( mockHandler.isTimeout ) {
+				if ( mockHandler.responseTime > 1 ) {
+					origSettings.timeout = mockHandler.responseTime - 1;
+				} else {
+					mockHandler.responseTime = 2;
+					origSettings.timeout = 1;
+				}
+				mockHandler.isTimeout = false;
+			}
+
+			// Set up onAfter[X] callback functions
+			if ( $.isFunction( mockHandler.onAfterSuccess ) ) {
+				origSettings.success = overrideCallback('Success', mockHandler);
+			}
+			if ( $.isFunction( mockHandler.onAfterError ) ) {
+				origSettings.error = overrideCallback('Error', mockHandler);
+			}
+			if ( $.isFunction( mockHandler.onAfterComplete ) ) {
+				origSettings.complete = overrideCallback('Complete', mockHandler);
+			}
+
+			copyUrlParameters(mockHandler, origSettings);
+
+			(function(mockHandler, requestSettings, origSettings, origHandler) {
+
+				mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
+					// Mock the XHR object
+					xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ); }
+				}));
+			})(mockHandler, requestSettings, origSettings, mockHandlers[k]);
+
+			return mockRequest;
+		}
+
+		// We don't have a mock request
+		unmockedAjaxCalls.push(origSettings);
+		if($.mockjaxSettings.throwUnmocked === true) {
+			throw new Error('AJAX not mocked: ' + origSettings.url);
+		}
+		else { // trigger a normal request
+			return _ajax.apply($, [origSettings]);
+		}
+	}
+
+	/**
+	* Copies URL parameter values if they were captured by a regular expression
+	* @param {Object} mockHandler
+	* @param {Object} origSettings
+	*/
+	function copyUrlParameters(mockHandler, origSettings) {
+		//parameters aren't captured if the URL isn't a RegExp
+		if (!(mockHandler.url instanceof RegExp)) {
+			return;
+		}
+		//if no URL params were defined on the handler, don't attempt a capture
+		if (!mockHandler.hasOwnProperty('urlParams')) {
+			return;
+		}
+		var captures = mockHandler.url.exec(origSettings.url);
+		//the whole RegExp match is always the first value in the capture results
+		if (captures.length === 1) {
+			return;
+		}
+		captures.shift();
+		//use handler params as keys and capture resuts as values
+		var i = 0,
+		capturesLength = captures.length,
+		paramsLength = mockHandler.urlParams.length,
+		//in case the number of params specified is less than actual captures
+		maxIterations = Math.min(capturesLength, paramsLength),
+		paramValues = {};
+		for (i; i < maxIterations; i++) {
+			var key = mockHandler.urlParams[i];
+			paramValues[key] = captures[i];
+		}
+		origSettings.urlParams = paramValues;
+	}
+
+
+	// Public
+
+	$.extend({
+		ajax: handleAjax
+	});
+
+	$.mockjaxSettings = {
+		//url:        null,
+		//type:       'GET',
+		log:          function( mockHandler, requestSettings ) {
+			if ( mockHandler.logging === false ||
+				 ( typeof mockHandler.logging === 'undefined' && $.mockjaxSettings.logging === false ) ) {
+				return;
+			}
+			if ( window.console && console.log ) {
+				var message = 'MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url;
+				var request = $.extend({}, requestSettings);
+
+				if (typeof console.log === 'function') {
+					console.log(message, request);
+				} else {
+					try {
+						console.log( message + ' ' + JSON.stringify(request) );
+					} catch (e) {
+						console.log(message);
+					}
+				}
+			}
+		},
+		logging:       true,
+		status:        200,
+		statusText:    "OK",
+		responseTime:  500,
+		isTimeout:     false,
+		throwUnmocked: false,
+		contentType:   'text/plain',
+		response:      '',
+		responseText:  '',
+		responseXML:   '',
+		proxy:         '',
+		proxyType:     'GET',
+
+		lastModified:  null,
+		etag:          '',
+		headers: {
+			etag: 'IJF@H#@923uf8023hFO@I#H#',
+			'content-type' : 'text/plain'
+		}
+	};
+
+	$.mockjax = function(settings) {
+		var i = mockHandlers.length;
+		mockHandlers[i] = settings;
+		return i;
+	};
+	$.mockjax.clear = function(i) {
+		if ( arguments.length == 1 ) {
+			mockHandlers[i] = null;
+		} else {
+			mockHandlers = [];
+		}
+		mockedAjaxCalls = [];
+		unmockedAjaxCalls = [];
+	};
+	// support older, deprecated version
+	$.mockjaxClear = function(i) {
+		window.console && window.console.warn && window.console.warn( 'DEPRECATED: The $.mockjaxClear() method has been deprecated in 1.6.0. Please use $.mockjax.clear() as the older function will be removed soon!' );
+		$.mockjax.clear();
+	};
+	$.mockjax.handler = function(i) {
+		if ( arguments.length == 1 ) {
+			return mockHandlers[i];
+		}
+	};
+	$.mockjax.mockedAjaxCalls = function() {
+		return mockedAjaxCalls;
+	};
+	$.mockjax.unfiredHandlers = function() {
+		var results = [];
+		for (var i=0, len=mockHandlers.length; i<len; i++) {
+			var handler = mockHandlers[i];
+            if (handler !== null && !handler.fired) {
+				results.push(handler);
+			}
+		}
+		return results;
+	};
+	$.mockjax.unmockedAjaxCalls = function() {
+		return unmockedAjaxCalls;
+	};
+})(jQuery);

+ 34 - 0
app/Resources/public/assets/readmore-js/package.json

@@ -0,0 +1,34 @@
+{
+  "name": "readmore-js",
+  "version": "2.1.0",
+  "description": "A lightweight jQuery plugin for collapsing and expanding long blocks of text with \"Read more\" and \"Close\" links.",
+  "main": "readmore.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/jedfoster/Readmore.js.git"
+  },
+  "keywords": [
+    "css",
+    "jquery",
+    "readmore",
+    "expand",
+    "collapse"
+  ],
+  "author": "Jed Foster <jed@jedfoster.com>",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/jedfoster/Readmore.js/issues"
+  },
+  "homepage": "https://github.com/jedfoster/Readmore.js",
+  "dependencies": {
+    "jquery": "~2.1.4"
+  },
+  "devDependencies": {
+    "gulp": "^3.9.0",
+    "gulp-rename": "^1.2.0",
+    "gulp-uglify": "^1.0.2"
+  }
+}

+ 330 - 0
app/Resources/public/assets/readmore-js/readmore.js

@@ -0,0 +1,330 @@
+/*!
+ * @preserve
+ *
+ * Readmore.js jQuery plugin
+ * Author: @jed_foster
+ * Project home: http://jedfoster.github.io/Readmore.js
+ * Licensed under the MIT license
+ *
+ * Debounce function from http://davidwalsh.name/javascript-debounce-function
+ */
+
+/* global jQuery */
+
+(function(factory) {
+  if (typeof define === 'function' && define.amd) {
+    // AMD
+    define(['jquery'], factory);
+  } else if (typeof exports === 'object') {
+    // CommonJS
+    module.exports = factory(require('jquery'));
+  } else {
+    // Browser globals
+    factory(jQuery);
+  }
+}(function($) {
+  'use strict';
+
+  var readmore = 'readmore',
+      defaults = {
+        speed: 100,
+        collapsedHeight: 200,
+        heightMargin: 16,
+        moreLink: '<a href="#">Read More</a>',
+        lessLink: '<a href="#">Close</a>',
+        embedCSS: true,
+        blockCSS: 'display: block; width: 100%;',
+        startOpen: false,
+
+        // callbacks
+        beforeToggle: function(){},
+        afterToggle: function(){}
+      },
+      cssEmbedded = {},
+      uniqueIdCounter = 0;
+
+  function debounce(func, wait, immediate) {
+    var timeout;
+
+    return function() {
+      var context = this, args = arguments;
+      var later = function() {
+        timeout = null;
+        if (! immediate) {
+          func.apply(context, args);
+        }
+      };
+      var callNow = immediate && !timeout;
+
+      clearTimeout(timeout);
+      timeout = setTimeout(later, wait);
+
+      if (callNow) {
+        func.apply(context, args);
+      }
+    };
+  }
+
+  function uniqueId(prefix) {
+    var id = ++uniqueIdCounter;
+
+    return String(prefix == null ? 'rmjs-' : prefix) + id;
+  }
+
+  function setBoxHeights(element) {
+    var el = element.clone().css({
+          height: 'auto',
+          width: element.width(),
+          maxHeight: 'none',
+          overflow: 'hidden'
+        }).insertAfter(element),
+        expandedHeight = el.outerHeight(),
+        cssMaxHeight = parseInt(el.css({maxHeight: ''}).css('max-height').replace(/[^-\d\.]/g, ''), 10),
+        defaultHeight = element.data('defaultHeight');
+
+    el.remove();
+
+    var collapsedHeight = cssMaxHeight || element.data('collapsedHeight') || defaultHeight;
+
+    // Store our measurements.
+    element.data({
+      expandedHeight: expandedHeight,
+      maxHeight: cssMaxHeight,
+      collapsedHeight: collapsedHeight
+    })
+    // and disable any `max-height` property set in CSS
+    .css({
+      maxHeight: 'none'
+    });
+  }
+
+  var resizeBoxes = debounce(function() {
+    $('[data-readmore]').each(function() {
+      var current = $(this),
+          isExpanded = (current.attr('aria-expanded') === 'true');
+
+      setBoxHeights(current);
+
+      current.css({
+        height: current.data( (isExpanded ? 'expandedHeight' : 'collapsedHeight') )
+      });
+    });
+  }, 100);
+
+  function embedCSS(options) {
+    if (! cssEmbedded[options.selector]) {
+      var styles = ' ';
+
+      if (options.embedCSS && options.blockCSS !== '') {
+        styles += options.selector + ' + [data-readmore-toggle], ' +
+          options.selector + '[data-readmore]{' +
+            options.blockCSS +
+          '}';
+      }
+
+      // Include the transition CSS even if embedCSS is false
+      styles += options.selector + '[data-readmore]{' +
+        'transition: height ' + options.speed + 'ms;' +
+        'overflow: hidden;' +
+      '}';
+
+      (function(d, u) {
+        var css = d.createElement('style');
+        css.type = 'text/css';
+
+        if (css.styleSheet) {
+          css.styleSheet.cssText = u;
+        }
+        else {
+          css.appendChild(d.createTextNode(u));
+        }
+
+        d.getElementsByTagName('head')[0].appendChild(css);
+      }(document, styles));
+
+      cssEmbedded[options.selector] = true;
+    }
+  }
+
+  function Readmore(element, options) {
+    this.element = element;
+
+    this.options = $.extend({}, defaults, options);
+
+    embedCSS(this.options);
+
+    this._defaults = defaults;
+    this._name = readmore;
+
+    this.init();
+
+    // IE8 chokes on `window.addEventListener`, so need to test for support.
+    if (window.addEventListener) {
+      // Need to resize boxes when the page has fully loaded.
+      window.addEventListener('load', resizeBoxes);
+      window.addEventListener('resize', resizeBoxes);
+    }
+    else {
+      window.attachEvent('load', resizeBoxes);
+      window.attachEvent('resize', resizeBoxes);
+    }
+  }
+
+
+  Readmore.prototype = {
+    init: function() {
+      var current = $(this.element);
+
+      current.data({
+        defaultHeight: this.options.collapsedHeight,
+        heightMargin: this.options.heightMargin
+      });
+
+      setBoxHeights(current);
+
+      var collapsedHeight = current.data('collapsedHeight'),
+          heightMargin = current.data('heightMargin');
+
+      if (current.outerHeight(true) <= collapsedHeight + heightMargin) {
+        // The block is shorter than the limit, so there's no need to truncate it.
+        return true;
+      }
+      else {
+        var id = current.attr('id') || uniqueId(),
+            useLink = this.options.startOpen ? this.options.lessLink : this.options.moreLink;
+
+        current.attr({
+          'data-readmore': '',
+          'aria-expanded': this.options.startOpen,
+          'id': id
+        });
+
+        current.after($(useLink)
+          .on('click', (function(_this) {
+            return function(event) {
+              _this.toggle(this, current[0], event);
+            };
+          })(this))
+          .attr({
+            'data-readmore-toggle': '',
+            'aria-controls': id
+          }));
+
+        if (! this.options.startOpen) {
+          current.css({
+            height: collapsedHeight
+          });
+        }
+      }
+    },
+
+    toggle: function(trigger, element, event) {
+      if (event) {
+        event.preventDefault();
+      }
+
+      if (! trigger) {
+        trigger = $('[aria-controls="' + _this.element.id + '"]')[0];
+      }
+
+      if (! element) {
+        element = _this.element;
+      }
+
+      var $element = $(element),
+          newHeight = '',
+          newLink = '',
+          expanded = false,
+          collapsedHeight = $element.data('collapsedHeight');
+
+      if ($element.height() <= collapsedHeight) {
+        newHeight = $element.data('expandedHeight') + 'px';
+        newLink = 'lessLink';
+        expanded = true;
+      }
+      else {
+        newHeight = collapsedHeight;
+        newLink = 'moreLink';
+      }
+
+      // Fire beforeToggle callback
+      // Since we determined the new "expanded" state above we're now out of sync
+      // with our true current state, so we need to flip the value of `expanded`
+      this.options.beforeToggle(trigger, $element, ! expanded);
+
+      $element.css({'height': newHeight});
+
+      // Fire afterToggle callback
+      $element.on('transitionend', (function(_this) {
+        return function() {
+          _this.options.afterToggle(trigger, $element, expanded);
+
+          $(this).attr({
+            'aria-expanded': expanded
+          }).off('transitionend');
+        }
+      })(this));
+
+      $(trigger).replaceWith($(this.options[newLink])
+        .on('click', (function(_this) {
+            return function(event) {
+              _this.toggle(this, element, event);
+            };
+          })(this))
+        .attr({
+          'data-readmore-toggle': '',
+          'aria-controls': $element.attr('id')
+        }));
+    },
+
+    destroy: function() {
+      $(this.element).each(function() {
+        var current = $(this);
+
+        current.attr({
+          'data-readmore': null,
+          'aria-expanded': null
+        })
+          .css({
+            maxHeight: '',
+            height: ''
+          })
+          .next('[data-readmore-toggle]')
+          .remove();
+
+        current.removeData();
+      });
+    }
+  };
+
+
+  $.fn.readmore = function(options) {
+    var args = arguments,
+        selector = this.selector;
+
+    options = options || {};
+
+    if (typeof options === 'object') {
+      return this.each(function() {
+        if ($.data(this, 'plugin_' + readmore)) {
+          var instance = $.data(this, 'plugin_' + readmore);
+          instance.destroy.apply(instance);
+        }
+
+        options.selector = selector;
+
+        $.data(this, 'plugin_' + readmore, new Readmore(this, options));
+      });
+    }
+    else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') {
+      return this.each(function () {
+        var instance = $.data(this, 'plugin_' + readmore);
+        if (instance instanceof Readmore && typeof instance[options] === 'function') {
+          instance[options].apply(instance, Array.prototype.slice.call(args, 1));
+        }
+      });
+    }
+  };
+
+}));
+

文件差異過大導致無法顯示
+ 10 - 0
app/Resources/public/assets/readmore-js/readmore.min.js


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

@@ -1,6 +1,6 @@
 {
   "name": "webcamjs",
-  "version": "1.0.12",
+  "version": "1.0.15",
   "homepage": "https://github.com/jhuckaby/webcamjs",
   "authors": [
     "Joseph Huckaby <jhuckaby@gmail.com>"
@@ -21,11 +21,11 @@
     "demos",
     "flash"
   ],
-  "_release": "1.0.12",
+  "_release": "1.0.15",
   "_resolution": {
     "type": "version",
-    "tag": "v1.0.12",
-    "commit": "1d84cba577c025414cea79ca04412c9599f5b6e6"
+    "tag": "v1.0.15",
+    "commit": "15b3fedfc1bd5e6cb8b0ed4460ee0f56a8adbfa8"
   },
   "_source": "https://github.com/jhuckaby/webcamjs.git",
   "_target": "1.0.*",

+ 10 - 1
app/Resources/public/assets/webcamjs/README.md

@@ -104,13 +104,22 @@ If you want to override the default settings, just call `Webcam.set()` and pass
 | `crop_height` | (Disabled) | Height of the final cropped image in pixels, defaults to `dest_height`. |
 | `image_format` | jpeg | Desired image format of captured image, may be "jpeg" or "png". |
 | `jpeg_quality` | 90 | For JPEG images, this is the desired quality, from 0 (worst) to 100 (best). |
+| `enable_flash` | true | Enable or disable Flash fallback, if there is no native webcam access. |
 | `force_flash` | false | Setting this to true will always run in Adobe Flash fallback mode. |
 | `flip_horiz` | false | Setting this to true will flip the image horizontally (mirror mode). |
 | `fps` | 30 | Set the desired fps (frames per second) capture rate. |
 | `swfURL` | "./webcam.swf" | Set an alternate location for the Adobe Flash fallback SWF file
 | `flashNotDetectedText` | "ERROR: No Adobe Flash Player detected.  Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours)." | text/html for flash player not detected.
 | `unfreeze_snap` | true | Whether to unfreeze the camera after snap (defaults to true)
-Here is an example of overriding some parameters.  Remember to call this *before* you attach the viewer.
+| `upload_name` | "webcam" | Which HTTP POST parameter name to use when uploading the webcam image file.
+
+Here is an example of overriding a single parameter.  Remember to call this *before* you attach the viewer.
+
+```js
+	Webcam.set('flip_horiz', true);
+```
+
+You can also set multiple parameters at once by passing in an object, like this:
 
 ```javascript
 	Webcam.set({

+ 1 - 1
app/Resources/public/assets/webcamjs/bower.json

@@ -1,6 +1,6 @@
 {
   "name": "webcamjs",
-  "version": "1.0.12",
+  "version": "1.0.15",
   "homepage": "https://github.com/jhuckaby/webcamjs",
   "authors": [
     "Joseph Huckaby <jhuckaby@gmail.com>"

+ 1 - 1
app/Resources/public/assets/webcamjs/package.json

@@ -1,6 +1,6 @@
 {
 	"name": "webcamjs",
-	"version": "1.0.12",
+	"version": "1.0.15",
 	"description": "HTML5 Webcam Image Capture Library with Flash Fallback",
 	"author": "Joseph Huckaby <jhuckaby@gmail.com>",
 	"homepage": "https://github.com/jhuckaby/webcamjs",

+ 9 - 4
app/Resources/public/assets/webcamjs/webcam.js

@@ -1,4 +1,4 @@
-// WebcamJS v1.0.12
+// WebcamJS v1.0.15
 // Webcam library for capturing JPEG/PNG images in JavaScript
 // Attempts getUserMedia, falls back to Flash
 // Author: Joseph Huckaby: http://github.com/jhuckaby
@@ -34,7 +34,7 @@ FlashError.prototype = new IntermediateInheritor();
 WebcamError.prototype = new IntermediateInheritor();
 
 var Webcam = {
-	version: '1.0.12',
+	version: '1.0.15',
 	
 	// globals
 	protocol: location.protocol.match(/https/i) ? 'https' : 'http',
@@ -49,6 +49,7 @@ var Webcam = {
 		dest_height: 0,        // these default to width/height
 		image_format: 'jpeg',  // image format (may be jpeg or png)
 		jpeg_quality: 90,      // jpeg image quality from 0 (worst) to 100 (best)
+		enable_flash: true,    // enable flash fallback,
 		force_flash: false,    // force flash mode,
 		flip_horiz: false,     // flip image horiz (mirror mode)
 		fps: 30,               // camera frames per second
@@ -56,6 +57,7 @@ var Webcam = {
 		constraints: null,     // custom user media constraints,
 		swfURL: '',            // URI to webcam.swf movie (defaults to the js location)
 		flashNotDetectedText: 'ERROR: No Adobe Flash Player detected.  Webcam.js relies on Flash for browsers that do not support getUserMedia (like yours).',
+		noInterfaceFoundText: 'No supported webcam interface found.',
 		unfreeze_snap: true    // Whether to unfreeze the camera after snap (defaults to true)
 	},
 
@@ -193,7 +195,7 @@ var Webcam = {
 			.catch( function(err) {
 				// JH 2016-07-31 Instead of dispatching error, now falling back to Flash if userMedia fails (thx @john2014)
 				// JH 2016-08-07 But only if flash is actually installed -- if not, dispatch error here and now.
-				if (self.detectFlash()) {
+				if (self.params.enable_flash && self.detectFlash()) {
 					setTimeout( function() { self.params.force_flash = 1; self.attach(elem); }, 1 );
 				}
 				else {
@@ -201,13 +203,16 @@ var Webcam = {
 				}
 			});
 		}
-		else {
+		else if (this.params.enable_flash && this.detectFlash()) {
 			// flash fallback
 			window.Webcam = Webcam; // needed for flash-to-js interface
 			var div = document.createElement('div');
 			div.innerHTML = this.getSWFHTML();
 			elem.appendChild( div );
 		}
+		else {
+			this.dispatch('error', new WebcamError( this.params.noInterfaceFoundText ));
+		}
 		
 		// setup final crop for live preview
 		if (this.params.crop_width && this.params.crop_height) {

文件差異過大導致無法顯示
+ 0 - 1
app/Resources/public/assets/webcamjs/webcam.min.js


文件差異過大導致無法顯示
+ 408 - 224
app/Resources/public/css/base.css


+ 2 - 1
bower.json

@@ -27,7 +27,8 @@
     "cropper": "1.0.*",
     "jquery.scrollbar": "0.2.*",
     "jquery-file-upload": "*",
-    "pwstrength-bootstrap": "*"
+    "pwstrength-bootstrap": "*",
+    "readmore-js": "2.1.0"
   },
   "resolutions": {
     "jquery": "2.1.4"

+ 5 - 2
documentation/installation_guide.html

@@ -701,6 +701,7 @@ If you have issues with files taking a long time to download, make sure you reco
 
   location @rewrite{
     rewrite ^/courses/([^/]+)/scorm/(.*)$ /main/document/download_scorm.php?doc_url=/$2&cDir=$1 last;
+    rewrite "^/courses/([^/]+)/document/certificates/(.*)$" /app/courses/$1/document/certificates/$2 last;
     rewrite ^/courses/([^/]+)/document/(.*)$ /main/document/download.php?doc_url=/$2&cDir=$1 last;
     rewrite ^/courses/([^/]+)/work/(.*)$ /main/work/download.php?file=work/$2&cDir=$1 last;
     rewrite ^/courses/([^/]+)/upload/(.*)$ /app/courses/$1/upload/$2 last;
@@ -711,7 +712,8 @@ If you have issues with files taking a long time to download, make sure you reco
   location / {
     rewrite ^/courses/([^/]+)/$ /main/course_home/course_home.php?cDir=$1 last;
     rewrite ^/courses/([^/]+)/index.php$ /main/course_home/course_home.php?cDir=$1 last;
-    rewrite ^/badge/(\d+)/user/(\d+)$ /main/badge/issued.php?skill=$1&user=$2 last;
+    rewrite ^/skill/(\d+)/user/(\d+)$ /main/badge/issued_all.php?skill=$1&user=$2 last;
+    rewrite ^/badge/(\d+)/user/(\d+)$ /main/badge/issued_all.php?skill=$1&user=$2 last;
     try_files $uri @rewrite;
   }
 
@@ -727,6 +729,7 @@ If you have issues with files taking a long time to download, make sure you reco
     rewrite ^/courses/([^/]+)/$ /main/course_home/course_home.php?cDir=$1 last;
     rewrite ^/courses/([^/]+)/index.php$ /main/course_home/course_home.php?cDir=$1 last;
     rewrite ^/session/([^/]+)/about/?$ /main/session/about.php?session_id=$1 last;
+    rewrite ^/badge/([^/]+) /main/badge/issued.php?issue=$1 last;
     rewrite ^/main/exercice/(.+)$ /main/exercise/$1 last;
     rewrite ^/main/newscorm/(.+)$ /main/lp/$1 last;
 
@@ -738,7 +741,7 @@ If you have issues with files taking a long time to download, make sure you reco
   }
 
   # Serve static files directly
-  location ~* \.(png|jpe?g|gif|ico)$ {
+  location ~* \.(png|jpe?g|gif|ico|js)$ {
     expires 1y;
     access_log off;
     rewrite ^/courses/([^/]+)/course-pic85x85.png$ /app/courses/$1/course-pic85x85.png last;

+ 0 - 1
main/admin/course_intro_pdf_import.php

@@ -25,7 +25,6 @@ if ($_POST['formSent']) {
         Display :: display_error_message($error_message, false);
     } else {
         $allowed_file_mimetype = array('zip');
-
         $ext_import_file = substr($_FILES['import_file']['name'], (strrpos($_FILES['import_file']['name'], '.') + 1));
 
         if (!in_array($ext_import_file, $allowed_file_mimetype)) {

+ 12 - 11
main/admin/course_list.php

@@ -275,7 +275,7 @@ function get_course_visibility_icon($v) {
     }
 }
 
-if (isset ($_POST['action'])) {
+if (isset($_POST['action'])) {
     switch ($_POST['action']) {
         // Delete selected courses
         case 'delete_courses':
@@ -294,7 +294,7 @@ $content = '';
 $message = '';
 $actions = '';
 
-if (isset ($_GET['search']) && $_GET['search'] == 'advanced') {
+if (isset ($_GET['search']) && $_GET['search'] === 'advanced') {
     // Get all course categories
     $interbreadcrumb[] = array('url' => 'index.php', 'name' => get_lang('PlatformAdmin'));
     $interbreadcrumb[] = array('url' => 'course_list.php', 'name' => get_lang('CourseList'));
@@ -375,8 +375,9 @@ if (isset ($_GET['search']) && $_GET['search'] == 'advanced') {
     $actions .= '<div class="row">';
     $actions .= '<div class="col-md-2">';
     $actions .= '<a href="course_add.php">'.Display::return_icon('new_course.png', get_lang('AddCourse'),'',ICON_SIZE_MEDIUM).'</a> ';
-    if (api_get_setting('course_validation') == 'true') {
-        $actions .= '<a href="course_request_review.php">'.Display::return_icon('course_request_pending.png', get_lang('ReviewCourseRequests'),'',ICON_SIZE_MEDIUM).'</a>';
+    if (api_get_setting('course_validation') === 'true') {
+        $actions .= '<a href="course_request_review.php">'.
+            Display::return_icon('course_request_pending.png', get_lang('ReviewCourseRequests'),'',ICON_SIZE_MEDIUM).'</a>';
     }
     $actions .= '</div>';
     $actions .= '<div class="col-md-4">';
@@ -419,13 +420,13 @@ if (isset ($_GET['search']) && $_GET['search'] == 'advanced') {
     if (isset ($_GET['keyword'])) {
         $parameters = array ('keyword' => Security::remove_XSS($_GET['keyword']));
     } elseif (isset ($_GET['keyword_code'])) {
-        $parameters['keyword_code'] 		= Security::remove_XSS($_GET['keyword_code']);
-        $parameters['keyword_title'] 		= Security::remove_XSS($_GET['keyword_title']);
-        $parameters['keyword_category'] 	= Security::remove_XSS($_GET['keyword_category']);
-        $parameters['keyword_language'] 	= Security::remove_XSS($_GET['keyword_language']);
-        $parameters['keyword_visibility'] 	= Security::remove_XSS($_GET['keyword_visibility']);
-        $parameters['keyword_subscribe'] 	= Security::remove_XSS($_GET['keyword_subscribe']);
-        $parameters['keyword_unsubscribe'] 	= Security::remove_XSS($_GET['keyword_unsubscribe']);
+        $parameters['keyword_code'] = Security::remove_XSS($_GET['keyword_code']);
+        $parameters['keyword_title'] = Security::remove_XSS($_GET['keyword_title']);
+        $parameters['keyword_category'] = Security::remove_XSS($_GET['keyword_category']);
+        $parameters['keyword_language'] = Security::remove_XSS($_GET['keyword_language']);
+        $parameters['keyword_visibility'] = Security::remove_XSS($_GET['keyword_visibility']);
+        $parameters['keyword_subscribe'] = Security::remove_XSS($_GET['keyword_subscribe']);
+        $parameters['keyword_unsubscribe'] = Security::remove_XSS($_GET['keyword_unsubscribe']);
     }
 
     $table->set_additional_parameters($parameters);

+ 8 - 9
main/admin/course_user_import_by_email.php

@@ -25,7 +25,7 @@ function validate_data($users_courses)
         }
 
         // 2. Check whether coursecode exists.
-        if (isset ($user_course['CourseCode']) && strlen($user_course['CourseCode']) != 0) {
+        if (isset($user_course['CourseCode']) && strlen($user_course['CourseCode']) != 0) {
             // 2.1 Check whethher code has been allready used by this CVS-file.
             if (!isset($coursecodes[$user_course['CourseCode']])) {
                 // 2.1.1 Check whether course with this code exists in the system.
@@ -43,8 +43,7 @@ function validate_data($users_courses)
         }
 
         // 3. Check whether Email exists.
-        if (isset ($user_course['Email']) && strlen($user_course['Email']) !=
-            0) {
+        if (isset($user_course['Email']) && strlen($user_course['Email']) != 0) {
             $user = api_get_user_info_from_email($user_course['Email']);
             if (empty($user)) {
                 $user_course['error'] = get_lang('UnknownUser');
@@ -53,7 +52,7 @@ function validate_data($users_courses)
         }
 
         // 4. Check whether status is valid.
-        if (isset ($user_course['Status']) && strlen($user_course['Status']) != 0) {
+        if (isset($user_course['Status']) && strlen($user_course['Status']) != 0) {
             if ($user_course['Status'] != COURSEMANAGER && $user_course['Status'] != STUDENT) {
                 $user_course['error'] = get_lang('UnknownStatus');
                 $errors[]             = $user_course;
@@ -75,13 +74,13 @@ function save_data($users_courses)
     $inserted_in_course = array();
 
     foreach ($users_courses as $user_course) {
-        $csv_data[$user_course['Email']][$user_course['CourseCode']] =
-            $user_course['Status'];
+        $csv_data[$user_course['Email']][$user_course['CourseCode']] = $user_course['Status'];
     }
 
     foreach ($csv_data as $email => $csv_subscriptions) {
         $sql = "SELECT * FROM $user_table u
-                WHERE u.email = '".Database::escape_string($email)."' LIMIT 1";
+                WHERE u.email = '".Database::escape_string($email)."' 
+                LIMIT 1";
         $res = Database::query($sql);
         $obj = Database::fetch_object($res);
         $user_id = $obj->user_id;
@@ -96,7 +95,7 @@ function save_data($users_courses)
         $to_subscribe   = array_diff(array_keys($csv_subscriptions), array_keys($db_subscriptions));
         $to_unsubscribe = array_diff(array_keys($db_subscriptions), array_keys($csv_subscriptions));
 
-        if ($_POST['subscribe']) {
+        if (isset($_POST['subscribe']) && $_POST['subscribe']) {
             foreach ($to_subscribe as $courseId) {
                 $courseInfo = api_get_course_info_by_id($courseId);
                 $course_code = $courseInfo['code'];
@@ -114,7 +113,7 @@ function save_data($users_courses)
             }
         }
 
-        if ($_POST['unsubscribe']) {
+        if (isset($_POST['unsubscribe']) && $_POST['unsubscribe']) {
             foreach ($to_unsubscribe as $courseId) {
                 $courseInfo = api_get_course_info_by_id($courseId);
                 $course_code = $courseInfo['code'];

+ 3 - 9
main/admin/dashboard_add_courses_to_user.php

@@ -91,7 +91,7 @@ function search_courses($needle, $type)
 		$rs	= Database::query($sql);
 
 		$return .= '<select id="origin" name="NoAssignedCoursesList[]" multiple="multiple" size="20" >';
-		while($course = Database :: fetch_array($rs)) {
+		while ($course = Database :: fetch_array($rs)) {
 			$return .= '<option value="'.$course['code'].'" title="'.htmlspecialchars($course['title'],ENT_QUOTES).'">'.$course['title'].' ('.$course['code'].')</option>';
 		}
 		$return .= '</select>';
@@ -102,8 +102,7 @@ function search_courses($needle, $type)
 
 $xajax->processRequests();
 $htmlHeadXtra[] = $xajax->getJavascript('../inc/lib/xajax/');
-$htmlHeadXtra[] = '
-<script type="text/javascript">
+$htmlHeadXtra[] = '<script>
 function moveItem(origin , destination) {
 	for(var i = 0 ; i<origin.options.length ; i++) {
 		if(origin.options[i].selected) {
@@ -170,7 +169,6 @@ if (isset($_POST['formSent']) && intval($_POST['formSent']) == 1) {
 Display::display_header($tool_name);
 
 // actions
-
 $actionsLeft = '<a href="dashboard_add_users_to_user.php?user='.$user_id.'">'.
     Display::return_icon('add-user.png', get_lang('AssignUsers'), null, ICON_SIZE_MEDIUM).'</a>';
 $actionsLeft .= '<a href="dashboard_add_sessions_to_user.php?user='.$user_id.'">'.
@@ -192,7 +190,7 @@ foreach ($assigned_courses_code as &$value) {
 
 $without_assigned_courses = '';
 if (count($assigned_courses_code) > 0) {
-	$without_assigned_courses = " AND c.code NOT IN(".implode(',',$assigned_courses_code).")";
+	$without_assigned_courses = " AND c.code NOT IN(".implode(',', $assigned_courses_code).")";
 }
 
 $needle = '%';
@@ -219,7 +217,6 @@ if (api_is_multiple_url_enabled()) {
             ORDER BY c.title";
 }
 
-
 $result	= Database::query($sql);
 
 ?>
@@ -269,9 +266,6 @@ if(!empty($msg)) {
                 <?php echo '<button class="btn btn-success" type="button" value="" onclick="valide()" >'.$tool_name.'</button>'; ?>
             </div>
         </div>
-
-
-
     </div>
     <div class="col-md-4">
         <h5><?php

+ 84 - 89
main/admin/dashboard_add_sessions_to_user.php

@@ -22,11 +22,11 @@ api_protect_admin_script(true);
 
 // setting breadcrumbs
 $interbreadcrumb[] = array('url' => 'index.php', 'name' => get_lang('PlatformAdmin'));
-$interbreadcrumb[] = array('url' => 'user_list.php','name' => get_lang('UserList'));
+$interbreadcrumb[] = array('url' => 'user_list.php', 'name' => get_lang('UserList'));
 
 // Database Table Definitions
-$tbl_session 			= Database::get_main_table(TABLE_MAIN_SESSION);
-$tbl_session_rel_user 	= Database::get_main_table(TABLE_MAIN_SESSION_USER);
+$tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
+$tbl_session_rel_user = Database::get_main_table(TABLE_MAIN_SESSION_USER);
 $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
 
 // Initializing variables
@@ -56,8 +56,9 @@ if (!api_is_platform_admin() && !api_is_session_admin()) {
 
 function search_sessions($needle, $type)
 {
-    global $tbl_session_rel_access_url, $tbl_session, $user_id;
-
+    global $user_id;
+    $tbl_session = Database::get_main_table(TABLE_MAIN_SESSION);
+    $tbl_session_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_SESSION);
     $xajax_response = new xajaxResponse();
     $return = '';
     if (!empty($needle) && !empty($type)) {
@@ -71,9 +72,12 @@ function search_sessions($needle, $type)
         }
 
         if (api_is_multiple_url_enabled()) {
-            $sql 	= " SELECT s.id, s.name FROM $tbl_session s
-                        LEFT JOIN $tbl_session_rel_access_url a ON (s.id = a.session_id)
-                        WHERE  s.name LIKE '$needle%' $without_assigned_sessions AND access_url_id = ".api_get_current_access_url_id()."";
+            $sql = " SELECT s.id, s.name FROM $tbl_session s
+                     LEFT JOIN $tbl_session_rel_access_url a 
+                     ON (s.id = a.session_id)
+                     WHERE  
+                        s.name LIKE '$needle%' $without_assigned_sessions AND 
+                        access_url_id = ".api_get_current_access_url_id();
         } else {
             $sql = "SELECT s.id, s.name FROM $tbl_session s
                     WHERE  s.name LIKE '$needle%' $without_assigned_sessions ";
@@ -208,95 +212,86 @@ if (api_is_multiple_url_enabled()) {
 }
 $result	= Database::query($sql);
 ?>
-    <form name="formulaire" method="post" action="<?php echo api_get_self(); ?>?user=<?php echo $user_id ?>" style="margin:0px;" <?php if($ajax_search){ echo ' onsubmit="valide();"';}?>>
-        <input type="hidden" name="formSent" value="1" />
-
-        <div class="row">
-            <div class="col-md-4">
-                <h5><?php echo get_lang('SessionsListInPlatform') ?> :</h5>
-
-                <div id="ajax_list_sessions_multiple">
-                    <select id="origin" name="NoAssignedSessionsList[]" multiple="multiple" size="20" style="width:340px;">
-                        <?php
-                        while ($enreg = Database::fetch_array($result)) {
-                        ?>
-                            <option value="<?php echo $enreg['id']; ?>" <?php echo 'title="'.htmlspecialchars($enreg['name'],ENT_QUOTES).'"';?>>
-                                <?php echo $enreg['name']; ?>
-                            </option>
-                        <?php } ?>
-                    </select>
-                </div>
-            </div>
-            <div class="col-md-4">
-                <div class="code-course">
-                    <?php if ($add_type == 'multiple') { ?>
-                    <p><?php echo get_lang('FirstLetterSession');?> :</p>
-                    <select class="selectpicker form-control" name="firstLetterSession" onchange = "xajax_search_sessions(this.value, 'multiple')">
-                        <option value="%">--</option>
-                            <?php  echo Display :: get_alphabet_options($firstLetterSession); ?>
-                    </select>
-                    <?php } ?>
-                </div>
-                <div class="control-course">
-                <?php
-                    if ($ajax_search) {
-                        ?>
-                        <div class="separate-action">
-                            <button class="btn btn-primary" type="button" onclick="remove_item(document.getElementById('destination'))">
-                                <em class="fa fa-arrow-left"></em>
-                            </button>
-                        </div>
+<form name="formulaire" method="post" action="<?php echo api_get_self(); ?>?user=<?php echo $user_id ?>" style="margin:0px;" <?php if($ajax_search){ echo ' onsubmit="valide();"';}?>>
+    <input type="hidden" name="formSent" value="1" />
+
+    <div class="row">
+        <div class="col-md-4">
+            <h5><?php echo get_lang('SessionsListInPlatform') ?> :</h5>
+            <div id="ajax_list_sessions_multiple">
+                <select id="origin" name="NoAssignedSessionsList[]" multiple="multiple" size="20" style="width:340px;">
                     <?php
-                    }
-                    else
-                    {
-                        ?>
-                    <div class="separate-action">
-                        <button class="btn btn-primary" type="button" onclick="moveItem(document.getElementById('origin'), document.getElementById('destination'))" onclick="moveItem(document.getElementById('origin'), document.getElementById('destination'))">
-                            <em class="fa fa-arrow-right"></em>
-                        </button>
-                    </div>
+                    while ($enreg = Database::fetch_array($result)) {
+                    ?>
+                        <option value="<?php echo $enreg['id']; ?>" <?php echo 'title="'.htmlspecialchars($enreg['name'],ENT_QUOTES).'"';?>>
+                            <?php echo $enreg['name']; ?>
+                        </option>
+                    <?php } ?>
+                </select>
+            </div>
+        </div>
+        <div class="col-md-4">
+            <div class="code-course">
+                <?php if ($add_type == 'multiple') { ?>
+                <p><?php echo get_lang('FirstLetterSession');?> :</p>
+                <select class="selectpicker form-control" name="firstLetterSession" onchange = "xajax_search_sessions(this.value, 'multiple')">
+                    <option value="%">--</option>
+                        <?php  echo Display :: get_alphabet_options($firstLetterSession); ?>
+                </select>
+                <?php } ?>
+            </div>
+            <div class="control-course">
+            <?php
+                if ($ajax_search) {
+                    ?>
                     <div class="separate-action">
-                        <button class="btn btn-primary" type="button" onclick="moveItem(document.getElementById('destination'), document.getElementById('origin'))" onclick="moveItem(document.getElementById('destination'), document.getElementById('origin'))">
+                        <button class="btn btn-primary" type="button" onclick="remove_item(document.getElementById('destination'))">
                             <em class="fa fa-arrow-left"></em>
                         </button>
                     </div>
-
-                    <?php
-                    }
-                    ?>
-
-                    <?php
-                    echo '<button class="btn btn-success" type="button" value="" onclick="valide()" >'.$tool_name.'</button>';
-                    ?>
+                <?php } else { ?>
+                <div class="separate-action">
+                    <button class="btn btn-primary" type="button" onclick="moveItem(document.getElementById('origin'), document.getElementById('destination'))" onclick="moveItem(document.getElementById('origin'), document.getElementById('destination'))">
+                        <em class="fa fa-arrow-right"></em>
+                    </button>
+                </div>
+                <div class="separate-action">
+                    <button class="btn btn-primary" type="button" onclick="moveItem(document.getElementById('destination'), document.getElementById('origin'))" onclick="moveItem(document.getElementById('destination'), document.getElementById('origin'))">
+                        <em class="fa fa-arrow-left"></em>
+                    </button>
                 </div>
+
+                <?php
+                }
+                echo '<button class="btn btn-success" type="button" value="" onclick="valide()" >'.$tool_name.'</button>';
+                ?>
             </div>
-            <div class="col-md-4">
-                <h5><?php
-                        if (UserManager::is_admin($user_id)) {
-                            echo get_lang('AssignedSessionsListToPlatformAdministrator');
-                        } else if ($user_info['status'] == SESSIONADMIN) {
-                            echo get_lang('AssignedSessionsListToSessionsAdministrator');
-                        } else {
-                            echo get_lang('AssignedSessionsListToHumanResourcesManager');
-                        }
+        </div>
+        <div class="col-md-4">
+            <h5>
+                <?php
+                if (UserManager::is_admin($user_id)) {
+                    echo get_lang('AssignedSessionsListToPlatformAdministrator');
+                } else if ($user_info['status'] == SESSIONADMIN) {
+                    echo get_lang('AssignedSessionsListToSessionsAdministrator');
+                } else {
+                    echo get_lang('AssignedSessionsListToHumanResourcesManager');
+                }
+                ?>
+            :</h5>
+             <select id='destination' name="SessionsList[]" multiple="multiple" size="20" style="width:320px;">
+                <?php
+                if (is_array($assigned_sessions_to_hrm)) {
+                    foreach($assigned_sessions_to_hrm as $enreg) {
                         ?>
-                :</h5>
-                 <select id='destination' name="SessionsList[]" multiple="multiple" size="20" style="width:320px;">
-                        <?php
-                        if (is_array($assigned_sessions_to_hrm)) {
-                            foreach($assigned_sessions_to_hrm as $enreg) {
-                                ?>
-                                <option value="<?php echo $enreg['id']; ?>" <?php echo 'title="'.htmlspecialchars($enreg['name'],ENT_QUOTES).'"'; ?>>
-                                    <?php echo $enreg['name'] ?>
-                                </option>
-                            <?php }
-                        }?>
-                    </select>
-            </div>
+                        <option value="<?php echo $enreg['id']; ?>" <?php echo 'title="'.htmlspecialchars($enreg['name'],ENT_QUOTES).'"'; ?>>
+                            <?php echo $enreg['name'] ?>
+                        </option>
+                <?php }
+                }?>
+            </select>
         </div>
-
-    </form>
-
+    </div>
+</form>
 <?php
 Display::display_footer();

+ 20 - 20
main/admin/dashboard_add_users_to_user.php

@@ -120,9 +120,8 @@ function search_users($needle, $type)
                     $order_clause
             ";
         }
-
-		$rs	= Database::query($sql);
-		$xajax_response->addAssign('ajax_list_users_multiple','innerHTML',api_utf8_encode($return));
+        $rs	= Database::query($sql);
+        $xajax_response->addAssign('ajax_list_users_multiple', 'innerHTML', api_utf8_encode($return));
 
         if ($type == 'single') {
             $tbl_user_rel_access_url = Database::get_main_table(TABLE_MAIN_ACCESS_URL_REL_USER);
@@ -171,14 +170,14 @@ function search_users($needle, $type)
             $return .= '</select>';
             $xajax_response->addAssign('ajax_list_users_multiple', 'innerHTML', api_utf8_encode($return));
         }
-	}
+    }
+
 	return $xajax_response;
 }
 
 $xajax->processRequests();
 $htmlHeadXtra[] = $xajax->getJavascript('../inc/lib/xajax/');
-$htmlHeadXtra[] = '
-<script type="text/javascript">
+$htmlHeadXtra[] = '<script>
 function add_user_to_user (code, content) {
 	document.getElementById("user_to_add").value = "";
 	document.getElementById("ajax_list_users_single").innerHTML = "";
@@ -285,7 +284,7 @@ if (!empty($filters) && !empty($filterData)) {
 
 $msg = '';
 if (isset($_POST['formSent']) && intval($_POST['formSent']) == 1) {
-	$user_list = $_POST['UsersList'];
+    $user_list = $_POST['UsersList'];
 
     switch ($userStatus) {
         case DRH:
@@ -312,12 +311,12 @@ Display::display_header($tool_name);
 $actionsLeft = '';
 if ($userStatus != STUDENT_BOSS) {
     $actionsLeft = Display::url(
-        Display::return_icon('course-add.png', get_lang('AssignCourses'), null, ICON_SIZE_MEDIUM ),
+        Display::return_icon('course-add.png', get_lang('AssignCourses'), null, ICON_SIZE_MEDIUM),
         "dashboard_add_courses_to_user.php?user=$user_id"
     );
 
     $actionsLeft .= Display::url(
-        Display::return_icon('session-add.png', get_lang('AssignSessions'), null, ICON_SIZE_MEDIUM ) ,
+        Display::return_icon('session-add.png', get_lang('AssignSessions'), null, ICON_SIZE_MEDIUM),
         "dashboard_add_sessions_to_user.php?user=$user_id"
     );
 }
@@ -387,7 +386,8 @@ if (!empty($conditions)) {
 
 if (api_is_multiple_url_enabled()) {
 	$sql = "SELECT user.user_id, username, lastname, firstname
-	        FROM $tbl_user user  LEFT JOIN $tbl_access_url_rel_user au ON (au.user_id = user.user_id)
+	        FROM $tbl_user user  LEFT JOIN $tbl_access_url_rel_user au 
+	        ON (au.user_id = user.user_id)
 			WHERE
                 $without_assigned_users
                 user.user_id NOT IN ($user_anonymous, $current_user_id, $user_id) AND
@@ -424,11 +424,12 @@ if(!empty($msg)) {
             <div class="col-sm-12">
                 <div id="ajax_list_users_multiple">
                     <select id="origin" class="form-control" name="NoAssignedUsersList[]" multiple="multiple" size="15">
-                        <?php   while ($enreg = Database::fetch_array($result)) {
+                        <?php
+                            while ($enreg = Database::fetch_array($result)) {
                                 $person_name = api_get_person_name($enreg['firstname'], $enreg['lastname']); ?>
-                                  <option value="<?php echo $enreg['user_id']; ?>" <?php echo 'title="'.htmlspecialchars($person_name,ENT_QUOTES).'"';?>>
-                            <?php echo $person_name.' ('.$enreg['username'].')'; ?>
-                        </option>
+                                <option value="<?php echo $enreg['user_id']; ?>" <?php echo 'title="'.htmlspecialchars($person_name,ENT_QUOTES).'"';?>>
+                                <?php echo $person_name.' ('.$enreg['username'].')'; ?>
+                                </option>
                         <?php } ?>
                     </select>
                 </div>
@@ -463,7 +464,6 @@ if(!empty($msg)) {
                 <em class="fa fa-chevron-left"></em>
                 </button>
             </div>
-
           <?php
           }
           ?>
@@ -492,13 +492,13 @@ if(!empty($msg)) {
                 <select id='destination' class="form-control" name="UsersList[]" multiple="multiple" size="15" >
                     <?php
                     if (is_array($assigned_users_to_hrm)) {
-                            foreach($assigned_users_to_hrm as $enreg) {
-                                    $person_name = api_get_person_name($enreg['firstname'], $enreg['lastname']);
+                        foreach($assigned_users_to_hrm as $enreg) {
+                            $person_name = api_get_person_name($enreg['firstname'], $enreg['lastname']);
                     ?>
                             <option value="<?php echo $enreg['user_id']; ?>" <?php echo 'title="'.htmlspecialchars($person_name,ENT_QUOTES).'"'; ?>>
-                        <?php echo $person_name.' ('.$enreg['username'].')'; ?>
-                    </option>
-                    <?php }
+                            <?php echo $person_name.' ('.$enreg['username'].')'; ?>
+                            </option>
+                        <?php }
                     }?>
                 </select>
             </div>

+ 7 - 1
main/admin/email_tester.php

@@ -21,7 +21,13 @@ $form->addText('smtp_host', get_lang('Host'), false, ['cols-size' => [2, 8, 2]])
 $form->addText('smtp_port', get_lang('Port'), false, ['cols-size' => [2, 8, 2]]);
 $form->addText('destination', get_lang('Destination'), true, ['cols-size' => [2, 8, 2]]);
 $form->addText('subject', get_lang('Subject'), true, ['cols-size' => [2, 8, 2]]);
-$form->addHtmlEditor('content', get_lang('Message'), true, false, ['ToolbarSet' => 'Minimal', 'cols-size' => [2, 8, 2]]);
+$form->addHtmlEditor(
+    'content',
+    get_lang('Message'),
+    true,
+    false,
+    ['ToolbarSet' => 'Minimal', 'cols-size' => [2, 8, 2]]
+);
 $form->addButtonSend(get_lang('SendMessage'), 'submit', false, ['cols-size' => [2, 8, 2]]);
 $form->setDefaults([
     'smtp_host' => $platform_email['SMTP_HOST'],

+ 21 - 41
main/admin/event_controller.php

@@ -12,68 +12,48 @@ if (api_get_setting('activate_email_template') != 'true') {
     api_not_allowed();
 }
 
-class eventController { // extends Controller {
-    public function showAction() {
-
-    }
-
-    public function newAction() {
-
-    }
-
-    public function addAction() {
-
-    }
-
-    public function listingAction() {
-        $event_email_template = new EventEmailTemplate();
-        return $event_email_template->display();
-    }
-
-    public function deleteAction($id) {
-         $event_email_template = new EventEmailTemplate();
-        return $event_email_template->delete($id);
-    }
-}
-
-$event_controller = new eventController();
 $action = isset($_GET['action']) ? $_GET['action'] : null;
 
 switch ($action) {
     case 'show':
-        $event_controller->showAction();
         break;
     case 'add':
-        $event_controller->addAction();
         break;
     case 'new':
-        $event_controller->newAction();
         break;
-    case 'delete' :
-        $event_controller->deleteAction($_GET['id']);
-        $content = $event_controller->listingAction();
+    case 'delete':
+        $event_email_template = new EventEmailTemplate();
+        $event_email_template->delete($_GET['id']);
+        $content = $event_email_template->display();
         break;
     default:
     case 'listing':
-        $content = $event_controller->listingAction();
+        $event_email_template = new EventEmailTemplate();
+        $content = $event_email_template->display();
         break;
 }
 
 //jqgrid will use this URL to do the selects
-$url            = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_event_email_template';
+$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_event_email_template';
 
 //The order is important you need to check the the $column variable in the model.ajax.php file
-$columns        = array(get_lang('Subject'), get_lang('EventTypeName'), get_lang('Language'), get_lang('Status'), get_lang('Actions'));
+$columns = array(
+    get_lang('Subject'),
+    get_lang('EventTypeName'),
+    get_lang('Language'),
+    get_lang('Status'),
+    get_lang('Actions'),
+);
 
 //Column config
-$column_model   = array(
-                        array('name'=>'subject',        'index'=>'subject',        'width'=>'80',   'align'=>'left'),
+$column_model = array(
+    array('name' => 'subject', 'index' => 'subject', 'width' => '80', 'align' => 'left'),
 //                        array('name'=>'message',        'index'=>'message', 'width'=>'500',  'align'=>'left','sortable'=>'false'),
-                        array('name'=>'event_type_name',        'index'=>'event_type_name',        'width'=>'80',   'align'=>'left'),
-                        array('name'=>'language_id',        'index'=>'language_id',        'width'=>'80',   'align'=>'left'),
-                        array('name'=>'activated',        'index'=>'activated',        'width'=>'80',   'align'=>'left'),
-                        array('name'=>'actions',        'index'=>'actions',     'width'=>'100')
-                       );
+    array('name' => 'event_type_name', 'index' => 'event_type_name', 'width' => '80', 'align' => 'left'),
+    array('name' => 'language_id', 'index' => 'language_id', 'width' => '80', 'align' => 'left'),
+    array('name' => 'activated', 'index' => 'activated', 'width' => '80', 'align' => 'left'),
+    array('name' => 'actions', 'index' => 'actions', 'width' => '100'),
+);
 //Autowidth
 $extra_params['autowidth'] = 'true';
 //height auto

+ 22 - 5
main/admin/event_type.php

@@ -36,7 +36,14 @@ if ($action == 'modEventType') {
     if (!empty($event_name)) {
         $eventName = $event_name;
     }
-    Event::save_event_type_message($eventName, $users, $eventMessage, $eventSubject, $eventMessageLanguage, $activated);
+    Event::save_event_type_message(
+        $eventName,
+        $users,
+        $eventMessage,
+        $eventSubject,
+        $eventMessageLanguage,
+        $activated
+    );
     header('location: event_controller.php');
     exit;
 }
@@ -47,7 +54,17 @@ $languages = api_get_languages();
 
 $ajaxPath = api_get_path(WEB_CODE_PATH) . 'inc/ajax/events.ajax.php';
 
-$action_array = array(array('url' =>'event_controller.php?action=listing' , 'content' => Display::return_icon('view_text.png', get_lang('ListView'), array(), ICON_SIZE_MEDIUM)));
+$action_array = array(
+    array(
+        'url' => 'event_controller.php?action=listing',
+        'content' => Display::return_icon(
+            'view_text.png',
+            get_lang('ListView'),
+            array(),
+            ICON_SIZE_MEDIUM
+        ),
+    )
+);
 
 $key_lang = get_lang('YouHaveSomeUnsavedChanges');
 $users = UserManager::get_user_list(array(), array('firstname'));
@@ -345,7 +362,9 @@ echo Display::actions($action_array);
     <select id="languages" name="languages" style="margin-top:20px;" onclick='confirmMessage(this.name); return false;'>
 <?php foreach ($languages["name"] as $key => $value) {
     $english_name = $languages['folder'][$key]; ?>
-            <option value="<?php echo $english_name; ?>" <?php echo ($english_name == api_get_interface_language()) ? "selected=selected" : ""; ?>><?php echo $value; ?></option>
+    <option value="<?php echo $english_name; ?>" <?php echo ($english_name == api_get_interface_language()) ? "selected=selected" : ""; ?>>
+        <?php echo $value; ?>
+    </option>
 <?php } ?>
     </select>
 
@@ -355,8 +374,6 @@ echo Display::actions($action_array);
     <input type="hidden" id="eventName" value="<?php echo $event_name ?>"/>
 
     <br />
-    <!--	<div id="descLangVar">
-        </div>-->
     <br />
 
     <label for="eventSubject">

+ 0 - 3
main/admin/export_certificates.php

@@ -1,5 +1,4 @@
 <?php
-
 /* For licensing terms, see /license.txt */
 
 /**
@@ -28,7 +27,6 @@ if ($form->validate()) {
     if (isset($_FILES['file']['tmp_name']) &&
         !empty($_FILES['file']['tmp_name'])
     ) {
-
         $users = Import::csv_reader($_FILES['file']['tmp_name']);
         $courseId = $values['course'];
         $courseInfo = api_get_course_info_by_id($courseId);
@@ -62,7 +60,6 @@ if ($form->validate()) {
             );
         }
     }
-
 }
 
 Display :: display_footer();

+ 18 - 9
main/admin/extra_field_workflow.php

@@ -47,24 +47,33 @@ if ($action == 'add') {
     );
     $interbreadcrumb[]=array('url' => '#','name' => get_lang('Add'));
 } elseif ($action == 'edit') {
-    $interbreadcrumb[]=array('url' => 'extra_fields.php?type='.$extraField->type,'name' => $extraField->pageName);
-    $interbreadcrumb[]=array(
+    $interbreadcrumb[] = array(
+        'url' => 'extra_fields.php?type='.$extraField->type,
+        'name' => $extraField->pageName
+    );
+    $interbreadcrumb[] = array(
         'url' => 'extra_fields.php?type='.$extraField->type.'&action=edit&id='.$extraFieldInfo['id'],
         'name' => $extraFieldInfo['display_text']
     );
-    $interbreadcrumb[]=array(
+    $interbreadcrumb[] = array(
         'url' => 'extra_field_options.php?type='.$extraField->type.'&field_id='.$extraFieldInfo['id'],
         'name' => get_lang('EditExtraFieldOptions')
     );
 
-    $interbreadcrumb[]=array('url' => '#','name' => get_lang('Edit'));
+    $interbreadcrumb[] = array('url' => '#', 'name' => get_lang('Edit'));
 } else {
-    $interbreadcrumb[]=array('url' => 'extra_fields.php?type='.$extraField->type,'name' => $extraField->pageName);
-    $interbreadcrumb[]=array(
-        'url' =>  'extra_fields.php?type='.$extraField->type.'&action=edit&id='.$extraFieldInfo['id'],
+    $interbreadcrumb[] = array(
+        'url' => 'extra_fields.php?type='.$extraField->type,
+        'name' => $extraField->pageName
+    );
+    $interbreadcrumb[] = array(
+        'url' => 'extra_fields.php?type='.$extraField->type.'&action=edit&id='.$extraFieldInfo['id'],
         'name' => $extraFieldInfo['display_text']
     );
-    $interbreadcrumb[]=array('url' => '#','name' => get_lang('EditExtraFieldOptions'));
+    $interbreadcrumb[] = array(
+        'url' => '#',
+        'name' => get_lang('EditExtraFieldOptions')
+    );
 }
 
 $roleId = isset($_REQUEST['roleId']) ? $_REQUEST['roleId'] : null;
@@ -120,7 +129,7 @@ $result = Database::select(
     $obj->table,
     array(
         'where' => array("field_id = ? " => $field_id),
-        'order' => "option_order ASC",
+        'order' => "option_order ASC"
     )
 );
 

+ 0 - 5
main/admin/extra_fields.php

@@ -111,7 +111,6 @@ $(function() {
 Display::display_header($tool_name);
 
 // Action handling: Add
-
 switch ($action) {
     case 'add':
         if (api_get_session_id() != 0 &&
@@ -124,14 +123,12 @@ switch ($action) {
 
         // The validation or display
         if ($form->validate()) {
-            //if ($check) {
             $values = $form->exportValues();
             unset($values['id']);
             $res = $obj->save($values);
             if ($res) {
                 Display::display_confirmation_message(get_lang('ItemAdded'));
             }
-            //}
             $obj->display();
         } else {
             echo '<div class="actions">';
@@ -169,12 +166,10 @@ switch ($action) {
         break;
     case 'delete':
         // Action handling: delete
-        //if ($check) {
         $res = $obj->delete($_GET['id']);
         if ($res) {
             Display::display_confirmation_message(get_lang('ItemDeleted'));
         }
-        //}
         $obj->display();
         break;
     default:

+ 6 - 6
main/admin/filler.php

@@ -22,7 +22,7 @@ api_protect_admin_script(true);
 $nameTools = get_lang('PlatformAdmin');
 
 // setting breadcrumbs
-$interbreadcrumb[] = array ("url" => 'index.php', "name" => $nameTools);
+$interbreadcrumb[] = array ('url' => 'index.php', 'name' => $nameTools);
 
 // setting the name of the tool
 $nameTools = get_lang('DataFiller');
@@ -30,8 +30,8 @@ $nameTools = get_lang('DataFiller');
 $output = array();
 if (!empty($_GET['fill'])) {
     switch ($_GET['fill']) {
-    	case 'users':
-        	require api_get_path(SYS_TEST_PATH).'datafiller/fill_users.php';
+        case 'users':
+            require api_get_path(SYS_TEST_PATH).'datafiller/fill_users.php';
             $output = fill_users();
             break;
         case 'courses':
@@ -63,11 +63,11 @@ if (count($output)>0) {
 ?>
 <div id="datafiller" class="panel panel-default">
     <div class="panel-body">
-    <h4><?php echo Display::return_icon('bug.png',get_lang('DataFiller'),null,ICON_SIZE_MEDIUM).' '.get_lang('DataFiller')?></h4>
+    <h4><?php echo Display::return_icon('bug.png', get_lang('DataFiller'), null, ICON_SIZE_MEDIUM).' '.get_lang('DataFiller')?></h4>
     <div class="description"><?php echo get_lang('ThisSectionIsOnlyVisibleOnSourceInstalls');?></div>
     <ul class="fillers">
-      <li><a href="filler.php?fill=users"><?php echo Display::return_icon('user.png',get_lang('FillUsers'),null,ICON_SIZE_SMALL).' '.get_lang('FillUsers');?></a></li>
-      <li><a href="filler.php?fill=courses"><?php echo Display::return_icon('new-course.png',get_lang('FillCourses'),null,ICON_SIZE_SMALL).' '.get_lang('FillCourses');?></a></li>
+      <li><a href="filler.php?fill=users"><?php echo Display::return_icon('user.png', get_lang('FillUsers'), null, ICON_SIZE_SMALL).' '.get_lang('FillUsers');?></a></li>
+      <li><a href="filler.php?fill=courses"><?php echo Display::return_icon('new-course.png', get_lang('FillCourses'), null, ICON_SIZE_SMALL).' '.get_lang('FillCourses');?></a></li>
     </ul>
     </div>
 </div>

+ 50 - 19
main/admin/grade_models.php

@@ -27,7 +27,7 @@ $action = isset($_GET['action']) ? $_GET['action'] : null;
 $check = Security::check_token('request');
 $token = Security::get_token();
 
-if ($action == 'add') {
+if ($action === 'add') {
     $interbreadcrumb[]=array('url' => 'grade_models.php','name' => get_lang('GradeModel'));
     $interbreadcrumb[]=array('url' => '#','name' => get_lang('Add'));
 } elseif ($action == 'edit') {
@@ -40,7 +40,7 @@ if ($action == 'add') {
 $htmlHeadXtra[]= '<script>
 
 function plusItem(item) {
-        if (item != 1) {
+    if (item != 1) {
 		document.getElementById(item).style.display = "inline";
     	document.getElementById("plus-"+item).style.display = "none";
    	 	document.getElementById("min-"+(item-1)).style.display = "none";
@@ -49,18 +49,18 @@ function plusItem(item) {
 	 	//document.getElementById("txta-"+(item)).value = "100";
 	 	//document.getElementById("txta-"+(item-1)).value = "";
 	 	$("input").removeClass("form-control");
-        }
-  }
+    }
+}
 
 function minItem(item) {
     if (item != 1) {
-     document.getElementById(item).style.display = "none";
-	 //document.getElementById("txta-"+item).value = "";
-	 //document.getElementById("txtb-"+item).value = "";
-     document.getElementById("plus-"+item).style.display = "inline";
-     document.getElementById("min-"+(item-1)).style.display = "inline";
-	 //document.getElementById("txta-"+(item-1)).value = "100";
-	 $("input").removeClass("form-control");
+        document.getElementById(item).style.display = "none";
+        //document.getElementById("txta-"+item).value = "";
+        //document.getElementById("txtb-"+item).value = "";
+        document.getElementById("plus-"+item).style.display = "inline";
+        document.getElementById("min-"+(item-1)).style.display = "inline";
+        //document.getElementById("txta-"+(item-1)).value = "100";
+        $("input").removeClass("form-control");
 	}
 	if (item = 1) {
 		document.getElementById("min-"+(item)).style.display = "none";
@@ -73,17 +73,39 @@ function minItem(item) {
 Display::display_header();
 
 //jqgrid will use this URL to do the selects
-$url            = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_grade_models';
+$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_grade_models';
 
 //The order is important you need to check the the $column variable in the model.ajax.php file
-$columns        = array(get_lang('Name'), get_lang('Description'), get_lang('Actions'));
+$columns = array(
+    get_lang('Name'),
+    get_lang('Description'),
+    get_lang('Actions'),
+);
 
 //Column config
-$column_model   = array(
-                        array('name'=>'name',           'index'=>'name',        'width'=>'80',   'align'=>'left'),
-                        array('name'=>'description',    'index'=>'description', 'width'=>'500',  'align'=>'left','sortable'=>'false'),
-                        array('name'=>'actions',        'index'=>'actions',     'width'=>'100',  'align'=>'left','formatter'=>'action_formatter','sortable'=>'false')
-                       );
+$column_model = array(
+    array(
+        'name' => 'name',
+        'index' => 'name',
+        'width' => '80',
+        'align' => 'left'
+    ),
+    array(
+        'name' => 'description',
+        'index' => 'description',
+        'width' => '500',
+        'align' => 'left',
+        'sortable' => 'false'
+    ),
+    array(
+        'name' => 'actions',
+        'index' => 'actions',
+        'width' => '100',
+        'align' => 'left',
+        'formatter' => 'action_formatter',
+        'sortable' => 'false'
+    )
+);
 //Autowidth
 $extra_params['autowidth'] = 'true';
 //height auto
@@ -100,7 +122,16 @@ $action_links = 'function action_formatter(cellvalue, options, rowObject) {
 $(function() {
 <?php
     // grid definition see the $obj->display() function
-    echo Display::grid_js('grade_model',  $url, $columns, $column_model, $extra_params, array(), $action_links,true);
+    echo Display::grid_js(
+        'grade_model',
+        $url,
+        $columns,
+        $column_model,
+        $extra_params,
+        array(),
+        $action_links,
+        true
+    );
 ?>
 });
 </script>

+ 4 - 5
main/admin/inactive_user_list.php

@@ -3,7 +3,8 @@
 /**
     @author Andre Boivin base code
 	@author Julio Montoya fixing lot of little details
-	@todo this script is not ready for a production use that's why I'm commenting the function delete_inactive_student
+	@todo this script is not ready for a production use that's why
+   I'm commenting the function delete_inactive_student
 
 *	@package chamilo.admin
 *	script pour effacer les user inactif depuis x temps
@@ -19,15 +20,13 @@ api_protect_admin_script(true);
 *
 */
 
-/**		INIT SECTION
-*/
-Display :: display_header($tool_name, "");
+Display :: display_header($tool_name);
 
 //On sélectionne les user élèves
 $sql = "SELECT user_id FROM ".$table_user." user WHERE user.status= '5' ORDER by lastname " ;
 $result = Database::query($sql);
 
-while($row = Database::fetch_array($result)) {
+while ($row = Database::fetch_array($result)) {
     $user_id = $row['user_id'];
     //  pour chaque élève, on trouve la dernière connexion
     //$last_connection_date = UserManager:: delete_inactive_student($user_id, 2, true);

+ 14 - 20
main/admin/ldap_form_add_users_group.php

@@ -11,21 +11,19 @@
  */
 $nbre=0;
 echo '<form name="form" method="post" action="'.api_get_self().'?annee='.Security::remove_XSS($annee).'">';
-	if($statut==1)
-	{
-		echo get_lang('EmailNotifySubscription').': <input type="checkbox" name="mailling" value="1" checked="checked"><i>'.get_lang('DontUnchek').'</i>';
-	}
-	else
-	{
-		echo '<input type="hidden" name="mailling" value="1">';
-	}
-if(!empty($course))
-{
-	echo '<input type="hidden" name="course" value="'.Security::remove_XSS($course).'">';
+if ($statut == 1) {
+    echo get_lang(
+            'EmailNotifySubscription'
+        ).': <input type="checkbox" name="mailling" value="1" checked="checked"><i>'.get_lang(
+            'DontUnchek'
+        ).'</i>';
+} else {
+    echo '<input type="hidden" name="mailling" value="1">';
 }
-elseif(!empty($id_session))
-{
-	echo '<input type="hidden" name="id_session" value="'.Security::remove_XSS($id_session).'">';
+if (!empty($course)) {
+    echo '<input type="hidden" name="course" value="'.Security::remove_XSS($course).'">';
+} elseif (!empty($id_session)) {
+    echo '<input type="hidden" name="id_session" value="'.Security::remove_XSS($id_session).'">';
 }
 $is_western_name_order = api_is_western_name_order();
 echo '<input type="hidden" name="confirmed" value="yes">';
@@ -46,13 +44,10 @@ while (list ($key, $val) = each($nom_form)) {
 	echo '<tr align="center" id="header'.$ndiv.'">';
 	echo '<td><input type="checkbox" name="checkboxes[]" value="'.$key.'" checked="checked"></td>';
 	echo '<td>'.$email_form[$key].'<input type="hidden" name="email_form['.$key.']" size="40" value="'.$email_form[$key].'"></td>';
-	if ($is_western_name_order)
-	{
+	if ($is_western_name_order)	{
 		echo '<td>'.$prenom_form[$key].'<input type="hidden" name="prenom_form['.$key.']" size="20" value="'.$prenom_form[$key].'"></td>';
 		echo '<td>'.$nom_form[$key].'<input type="hidden" name="nom_form['.$key.']" size="20" value="'.$nom_form[$key].'"></td>';
-	}
-	else
-	{
+	} else {
 		echo '<td>'.$nom_form[$key].'<input type="hidden" name="nom_form['.$key.']" size="20" value="'.$nom_form[$key].'"></td>';
 		echo '<td>'.$prenom_form[$key].'<input type="hidden" name="prenom_form['.$key.']" size="20" value="'.$prenom_form[$key].'"></td>';
 	}
@@ -69,4 +64,3 @@ echo '<br />';
 echo '<br />';
 echo '<input type="submit"  name="submit" value="'.get_lang('Submit').'">';
 echo '</form>';
-?>

+ 0 - 3
main/admin/ldap_import_students.php

@@ -5,9 +5,6 @@
  * @package chamilo.admin
  * Copyright (c) 2007 Mustapha Alouani (supervised by Michel Moreau-Belliard)
  */
-/**
- * Code
- */
 // resetting the course id
 $cidReset=true;
 require_once('../inc/global.inc.php');

+ 2 - 2
main/admin/legal_add.php

@@ -82,8 +82,8 @@ if ($form->validate()) {
                         $defaults = $term_preview;
                         if (!$term_preview) {
                             // there are not terms and conditions
-                            $term_preview['type']=-1;
-                            $defaults['type']=0;
+                            $term_preview['type'] = -1;
+                            $defaults['type'] = 0;
                         }
                     }
                 }

+ 2 - 0
main/admin/legal_list.php

@@ -44,8 +44,10 @@ function get_legal_data_mask($id, $params = null, $row = null)
 {
 	return LegalManager::get_legal_data($id, $params, $row);
 }
+
 function count_mask()
 {
 	return LegalManager::count();
 }
+
 Display :: display_footer();

+ 9 - 1
main/admin/resource_sequence.php

@@ -76,7 +76,15 @@ $form->addHtml("</div>");
 
 $formSave = new FormValidator('');
 $formSave->addHidden('sequence_type', 'session');
-$formSave->addButton('save_resource', get_lang('SaveSettings'), 'floppy-o', 'success', null, null, ['cols-size' => [1, 10, 1]]);
+$formSave->addButton(
+    'save_resource',
+    get_lang('SaveSettings'),
+    'floppy-o',
+    'success',
+    null,
+    null,
+    ['cols-size' => [1, 10, 1]]
+);
 
 $tpl->assign('create_sequence', $formSequence->returnForm());
 $tpl->assign('select_sequence', $selectSequence->returnForm());

+ 3 - 2
main/admin/skill_badge_create.php

@@ -1,11 +1,13 @@
 <?php
 /* For licensing terms, see /license.txt */
+
+use ChamiloSession as Session;
+
 /**
  * Show information about Mozilla OpenBadges
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
  * @package chamilo.admin.openbadges
  */
-use ChamiloSession as Session;
 
 $cidReset = true;
 
@@ -57,7 +59,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
         }
 
         if ($existsBadgesDirectory) {
-
             if (!empty($skill['icon'])) {
                 $iconFileAbsolutePath = $badgePath . $skill['icon'];
 

+ 2 - 1
main/admin/skill_badge_list.php

@@ -1,12 +1,13 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use ChamiloSession as Session;
+
 /**
  * Show information about Mozilla OpenBadges
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
  * @package chamilo.admin.openbadges
  */
-use ChamiloSession as Session;
 
 $cidReset = true;
 

+ 3 - 1
main/admin/skill_create.php

@@ -1,11 +1,13 @@
 <?php
 /* For licensing terms, see /license.txt */
+
+use ChamiloSession as Session;
+
 /**
  * Create skill form
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
  * @package chamilo.admin
  */
-use ChamiloSession as Session;
 
 $cidReset = true;
 

+ 3 - 1
main/admin/skill_edit.php

@@ -1,11 +1,13 @@
 <?php
 /* For licensing terms, see /license.txt */
+
+use ChamiloSession as Session;
+
 /**
  * Skill edit form
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
  * @package chamilo.admin
  */
-use ChamiloSession as Session;
 
 $cidReset = true;
 

+ 0 - 1
main/admin/skill_level.php

@@ -92,7 +92,6 @@ switch ($action) {
             header('Location: '.$listAction);
             exit;
         }
-
         break;
     case 'delete':
         $tpl->assign('actions', Display::url(get_lang('List'), $listAction));

+ 0 - 2
main/admin/skill_list.php

@@ -1,8 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
 
-use ChamiloSession as Session;
-
 /**
  * Skill list for management
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>

+ 4 - 5
main/admin/skills.php

@@ -21,13 +21,13 @@ $htmlHeadXtra[] = api_get_js('jquery.jsPlumb.all.js');
 $htmlHeadXtra[] = api_get_js('jqueryui-touch-punch/jquery.ui.touch-punch.min.js');
 $htmlHeadXtra[] = api_get_js('skills.js');
 
-$skill  = new Skill();
-$type   = 'edit'; //edit
-$tree   = $skill->get_skills_tree(null, null, true);
+$skill = new Skill();
+$type = 'edit'; //edit
+$tree = $skill->get_skills_tree(null, null, true);
 $skill_visualizer = new SkillVisualizer($tree, $type);
 
 $html = $skill_visualizer->return_html();
-$url  = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1';
+$url = api_get_path(WEB_AJAX_PATH).'skill.ajax.php?1=1';
 
 $tpl = new Template(null, false, false);
 
@@ -36,7 +36,6 @@ $tpl->assign('html', $html);
 $tpl->assign('skill_visualizer', $skill_visualizer);
 $tpl->assign('js', $skill_visualizer->return_js());
 
-//
 $content = $tpl->fetch('default/skill/skill_tree.tpl');
 $tpl->assign('content', $content);
 $tpl->display_no_layout_template();

+ 38 - 12
main/admin/skills_gradebook.php

@@ -34,23 +34,49 @@ if ($action == 'add_skill') {
 
 Display::display_header($tool_name);
 
-
-
-
 //jqgrid will use this URL to do the selects
 
-$url            = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_gradebooks';
+$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_gradebooks';
 
 //The order is important you need to check the the $column variable in the model.ajax.php file
-$columns        = array(get_lang('Name'), get_lang('CertificatesFiles'), get_lang('Skills'), get_lang('Actions'));
+$columns = array(
+    get_lang('Name'),
+    get_lang('CertificatesFiles'),
+    get_lang('Skills'),
+    get_lang('Actions')
+);
 
 //Column config
-$column_model   = array(
-                        array('name'=>'name',           'index'=>'name',        'width'=>'150', 'align'=>'left'),
-                        array('name'=>'certificate',    'index'=>'certificate', 'width'=>'25', 'align'=>'left', 'sortable'=>'false'),
-                        array('name'=>'skills',         'index'=>'skills',      'width'=>'300', 'align'=>'left', 'sortable'=>'false'),
-                        array('name'=>'actions',        'index'=>'actions',     'width'=>'30', 'align'=>'left','formatter'=>'action_formatter','sortable'=>'false')
-                       );
+$column_model = array(
+    array(
+        'name' => 'name',
+        'index' => 'name',
+        'width' => '150',
+        'align' => 'left'
+    ),
+    array(
+        'name' => 'certificate',
+        'index' => 'certificate',
+        'width' => '25',
+        'align' => 'left',
+        'sortable' => 'false'
+    ),
+    array(
+        'name' => 'skills',
+        'index' => 'skills',
+        'width' => '300',
+        'align' => 'left',
+        'sortable' => 'false'
+    ),
+    array(
+        'name' => 'actions',
+        'index' => 'actions',
+        'width' => '30',
+        'align' => 'left',
+        'formatter' => 'action_formatter',
+        'sortable' => 'false'
+    )
+);
 //Autowidth
 $extra_params['autowidth'] = 'true';
 //height auto
@@ -77,7 +103,7 @@ $(function() {
 <?php
 $gradebook = new Gradebook();
 
-switch($action) {
+switch ($action) {
     case 'display':
         $gradebook->display();
         break;

+ 123 - 117
main/admin/skills_import.php

@@ -95,15 +95,15 @@ function parse_csv_data($file)
 function element_start($parser, $data)
 {
 	$data = api_utf8_decode($data);
-	global $skill;
-	global $current_tag;
-	switch ($data) {
-		case 'Skill' :
-			$skill = array ();
-			break;
-		default :
-			$current_tag = $data;
-	}
+    global $skill;
+    global $current_tag;
+    switch ($data) {
+        case 'Skill':
+            $skill = array();
+            break;
+        default:
+            $current_tag = $data;
+    }
 }
 
 /**
@@ -112,17 +112,17 @@ function element_start($parser, $data)
 function element_end($parser, $data)
 {
 	$data = api_utf8_decode($data);
-	global $skill;
-	global $skills;
-	global $current_value;
-	switch ($data) {
-		case 'Skill' :
-			$skills[] = $skill;
-			break;
-		default :
-			$skill[$data] = $current_value;
-			break;
-	}
+    global $skill;
+    global $skills;
+    global $current_value;
+    switch ($data) {
+        case 'Skill':
+            $skills[] = $skill;
+            break;
+        default:
+            $skill[$data] = $current_value;
+            break;
+    }
 }
 
 /**
@@ -142,17 +142,17 @@ function character_data($parser, $data)
  */
 function parse_xml_data($file)
 {
-	global $current_tag;
-	global $current_value;
-	global $skill;
-	global $skills;
-	$skills = array();
-	$parser = xml_parser_create('UTF-8');
-	xml_set_element_handler($parser, 'element_start', 'element_end');
-	xml_set_character_data_handler($parser, 'character_data');
-	xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
-	xml_parse($parser, api_utf8_encode_xml(file_get_contents($file)));
-	xml_parser_free($parser);
+    global $current_tag;
+    global $current_value;
+    global $skill;
+    global $skills;
+    $skills = array();
+    $parser = xml_parser_create('UTF-8');
+    xml_set_element_handler($parser, 'element_start', 'element_end');
+    xml_set_character_data_handler($parser, 'character_data');
+    xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, false);
+    xml_parse($parser, api_utf8_encode_xml(file_get_contents($file)));
+    xml_parser_free($parser);
 
 	return $skills;
 }
@@ -169,73 +169,73 @@ $user_id_error = array();
 $error_message = '';
 
 if (!empty($_POST['formSent']) && $_FILES['import_file']['size'] !== 0) {
-	$file_type = $_POST['file_type'];
-	Security::clear_token();
-	$tok = Security::get_token();
-	$allowed_file_mimetype = array('csv','xml');
-	$error_kind_file = false;
+    $file_type = $_POST['file_type'];
+    Security::clear_token();
+    $tok = Security::get_token();
+    $allowed_file_mimetype = array('csv','xml');
+    $error_kind_file = false;
     $error_message = '';
 
-	$ext_import_file = substr($_FILES['import_file']['name'], (strrpos($_FILES['import_file']['name'],'.')+1));
+    $ext_import_file = substr($_FILES['import_file']['name'], (strrpos($_FILES['import_file']['name'],'.')+1));
 
-	if (in_array($ext_import_file,$allowed_file_mimetype)) {
-		if (strcmp($file_type, 'csv') === 0 && $ext_import_file == $allowed_file_mimetype[0]) {
-			$skills	= parse_csv_data($_FILES['import_file']['tmp_name']);
-			$errors = validate_data($skills);
-			$error_kind_file = false;
-		} elseif (strcmp($file_type, 'xml') === 0 && $ext_import_file == $allowed_file_mimetype[1]) {
-			$skills = parse_xml_data($_FILES['import_file']['tmp_name']);
-			$errors = validate_data($skills);
-			$error_kind_file = false;
-		} else {
-			$error_kind_file = true;
-		}
-	} else {
-		$error_kind_file = true;
-	}
+    if (in_array($ext_import_file,$allowed_file_mimetype)) {
+        if (strcmp($file_type, 'csv') === 0 && $ext_import_file == $allowed_file_mimetype[0]) {
+            $skills	= parse_csv_data($_FILES['import_file']['tmp_name']);
+            $errors = validate_data($skills);
+            $error_kind_file = false;
+        } elseif (strcmp($file_type, 'xml') === 0 && $ext_import_file == $allowed_file_mimetype[1]) {
+            $skills = parse_xml_data($_FILES['import_file']['tmp_name']);
+            $errors = validate_data($skills);
+            $error_kind_file = false;
+        } else {
+            $error_kind_file = true;
+        }
+    } else {
+        $error_kind_file = true;
+    }
 
-	// List skill id with error.
-	$skills_to_insert = $skill_id_error = array();
-	if (is_array($errors)) {
-		foreach ($errors as $my_errors) {
-			$skill_id_error[] = $my_errors['SkillName'];
-		}
-	}
-	if (is_array($skills)) {
-		foreach ($skills as $my_skill) {
-			if (isset($my_skill['name']) && !in_array($my_skill['name'], $skill_id_error)) {
-				$skills_to_insert[] = $my_skill;
-			}
-		}
-	}
+    // List skill id with error.
+    $skills_to_insert = $skill_id_error = array();
+    if (is_array($errors)) {
+        foreach ($errors as $my_errors) {
+            $skill_id_error[] = $my_errors['SkillName'];
+        }
+    }
+    if (is_array($skills)) {
+        foreach ($skills as $my_skill) {
+            if (isset($my_skill['name']) && !in_array($my_skill['name'], $skill_id_error)) {
+                $skills_to_insert[] = $my_skill;
+            }
+        }
+    }
 
-	if (strcmp($file_type, 'csv') === 0) {
-		save_data($skills_to_insert);
-	} elseif (strcmp($file_type, 'xml') === 0) {
-		save_data($skills_to_insert);
-	} else {
-		$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
-	}
+    if (strcmp($file_type, 'csv') === 0) {
+        save_data($skills_to_insert);
+    } elseif (strcmp($file_type, 'xml') === 0) {
+        save_data($skills_to_insert);
+    } else {
+        $error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
+    }
 
-	if (count($errors) > 0) {
-		$see_message_import = get_lang('FileImportedJustSkillsThatAreNotRegistered');
-	} else {
-		$see_message_import = get_lang('FileImported');
-	}
+    if (count($errors) > 0) {
+        $see_message_import = get_lang('FileImportedJustSkillsThatAreNotRegistered');
+    } else {
+        $see_message_import = get_lang('FileImported');
+    }
 
-	if (count($errors) != 0) {
-		$warning_message = '<ul>';
-		foreach ($errors as $index => $error_skill) {
-			$warning_message .= '<li><b>'.$error_skill['error'].'</b>: ';
-			$warning_message .= '<strong>'.$error_skill['SkillName'].'</strong>&nbsp;('.$error_skill['SkillName'].')';
-			$warning_message .= '</li>';
-		}
-		$warning_message .= '</ul>';
-	}
+    if (count($errors) != 0) {
+        $warning_message = '<ul>';
+        foreach ($errors as $index => $error_skill) {
+            $warning_message .= '<li><b>'.$error_skill['error'].'</b>: ';
+            $warning_message .= '<strong>'.$error_skill['SkillName'].'</strong>&nbsp;('.$error_skill['SkillName'].')';
+            $warning_message .= '</li>';
+        }
+        $warning_message .= '</ul>';
+    }
 
     if ($error_kind_file) {
-		$error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
-	}
+        $error_message = get_lang('YouMustImportAFileAccordingToSelectedOption');
+    }
 }
 
 $interbreadcrumb[] = array ("url" => 'skill_list.php', "name" => get_lang('ManageSkills'));
@@ -243,34 +243,34 @@ $interbreadcrumb[] = array ("url" => 'skill_list.php', "name" => get_lang('Manag
 Display :: display_header($tool_name);
 
 if (!empty($error_message)) {
-	Display::display_error_message($error_message);
+    Display::display_error_message($error_message);
 }
 if (!empty($see_message_import)) {
-	Display::display_normal_message($see_message_import);
+    Display::display_normal_message($see_message_import);
 }
 
 $toolbar = Display::toolbarButton(
-	get_lang('ManageSkills'),
-	api_get_path(WEB_CODE_PATH) . 'admin/skill_list.php',
-	'list',
-	'success',
-	['title' => get_lang('CreateSkill')]
+    get_lang('ManageSkills'),
+    api_get_path(WEB_CODE_PATH) . 'admin/skill_list.php',
+    'list',
+    'success',
+    ['title' => get_lang('CreateSkill')]
 );
 $toolbar .= '&nbsp;&nbsp;';
 $toolbar .= Display::toolbarButton(
-	get_lang('SkillsWheel'),
-	api_get_path(WEB_CODE_PATH) . 'admin/skills_wheel.php',
-	'bullseye',
-	'primary',
-	['title' => get_lang('CreateSkill')]
+    get_lang('SkillsWheel'),
+    api_get_path(WEB_CODE_PATH) . 'admin/skills_wheel.php',
+    'bullseye',
+    'primary',
+    ['title' => get_lang('CreateSkill')]
 );
 $toolbar .= '&nbsp;&nbsp;';
 $toolbar .= Display::toolbarButton(
-	get_lang('BadgesManagement'),
-	api_get_path(WEB_CODE_PATH) . 'admin/skill_badge_list.php',
-	'shield',
-	'warning',
-	['title' => get_lang('BadgesManagement')]
+    get_lang('BadgesManagement'),
+    api_get_path(WEB_CODE_PATH) . 'admin/skill_badge_list.php',
+    'shield',
+    'warning',
+    ['title' => get_lang('BadgesManagement')]
 );
 $toolbar .= '<br /><br />';
 
@@ -281,7 +281,13 @@ $form->addElement('header', '', $tool_name);
 $form->addElement('hidden', 'formSent');
 $form->addElement('file', 'import_file', get_lang('ImportFileLocation'));
 $group = array();
-$group[] = $form->createElement('radio', 'file_type', '', 'CSV (<a href="skill_example.csv" target="_blank">'.get_lang('ExampleCSVFile').'</a>)', 'csv');
+$group[] = $form->createElement(
+    'radio',
+    'file_type',
+    '',
+    'CSV (<a href="skill_example.csv" target="_blank">'.get_lang('ExampleCSVFile').'</a>)',
+    'csv'
+);
 $form->addGroup($group, '', get_lang('FileType'));
 $form->addButtonImport(get_lang('Import'));
 $defaults['formSent'] = 1;
@@ -296,16 +302,16 @@ $result_xml = '';
 $i = 0;
 $count_fields = count($extra_fields);
 if ($count_fields > 0) {
-	foreach ($extra_fields as $extra) {
-		$list[] = $extra[1];
-		$list_reponse[] = 'xxx';
-		$spaces = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
-		$result_xml .= $spaces.'&lt;'.$extra[1].'&gt;xxx&lt;/'.$extra[1].'&gt;';
-		if ($i != $count_fields - 1) {
-			$result_xml .= '<br/>';
-		}
-		$i++;
-	}
+    foreach ($extra_fields as $extra) {
+        $list[] = $extra[1];
+        $list_reponse[] = 'xxx';
+        $spaces = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;';
+        $result_xml .= $spaces.'&lt;'.$extra[1].'&gt;xxx&lt;/'.$extra[1].'&gt;';
+        if ($i != $count_fields - 1) {
+            $result_xml .= '<br/>';
+        }
+        $i++;
+    }
 }
 ?>
 <p><?php echo get_lang('CSVMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>

+ 4 - 8
main/admin/skills_profile.php

@@ -47,10 +47,8 @@ if ($form->validate()) {
 
     $skills = $values['skills'];
     if (!empty($skills)) {
-
         $hidden_skills = isset($values['hidden_skills']) ? $values['hidden_skills'] : array();
         $skills = array_merge($skills, $hidden_skills);
-
         $skills = array_filter($skills);
         $skills = array_unique($skills);
         Session::write('skills', $skills);
@@ -90,7 +88,7 @@ if (!empty($users)) {
         $user_list[$user['user_id']]['total_found_skills'] = $found_counts;
     }
     $ordered_user_list = array();
-    foreach($user_list as $user_id => $user_data) {
+    foreach ($user_list as $user_id => $user_data) {
         $ordered_user_list[$user_data['total_found_skills']][] = $user_data;
     }
     if (!empty($ordered_user_list)) {
@@ -104,23 +102,21 @@ $tpl->assign('total_search_skills', $count_skills);
 
 if (!empty($skills)) {
     $counter = 0;
-    foreach($skills as $hidden_skill_id) {
+    foreach ($skills as $hidden_skill_id) {
         $form->addElement('hidden', 'hidden_skills[]', $hidden_skill_id);
         $counter++;
     }
 }
 
 if (!empty($skills)) {
-    foreach($skills as $my_skill) {
+    foreach ($skills as $my_skill) {
         $total_skills_to_search[$my_skill] = $my_skill;
     }
 }
 
 $total_skills_to_search = $skill->get_skills_info($total_skills_to_search);
-
-
 $action = isset($_REQUEST['a']) ? $_REQUEST['a'] : null;
-$id     = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null;
+$id = isset($_REQUEST['id']) ? intval($_REQUEST['id']) : null;
 
 switch ($action) {
     case 'remove_skill':

+ 5 - 6
main/admin/special_exports.php

@@ -1,6 +1,9 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
+use Chamilo\CourseBundle\Component\CourseCopy\CourseSelectForm;
+
 /**
  * Special exports
  *
@@ -13,9 +16,6 @@
 $cidReset = true;
 require_once  '../inc/global.inc.php';
 
-use Chamilo\CourseBundle\Component\CourseCopy\CourseBuilder;
-use Chamilo\CourseBundle\Component\CourseCopy\CourseSelectForm;
-
 // setting the section (for the tabs)
 $this_section = SECTION_PLATFORM_ADMIN;
 // setting breadcrumbs
@@ -217,12 +217,11 @@ function rename_zip($FileZip) {
 
 }
 
-function fullexportspecial(){
+function fullexportspecial()
+{
     global $tbl_session, $tbl_session_course, $export;
     $FileZip = create_zip();
     $to_group_id = 0;
-    $code_course = '';
-    $list_course = array();
     $zip_folder = new PclZip($FileZip['TEMP_FILE_ZIP']);
     $list_course = CourseManager::get_course_list();
 

+ 8 - 3
main/admin/system_announcements.php

@@ -81,7 +81,7 @@ switch($action) {
         Display :: display_confirmation_message(get_lang('AnnouncementDeleted'));
         break;
     case 'delete_selected':
-        foreach($_POST['id'] as $index => $id) {
+        foreach ($_POST['id'] as $index => $id) {
             SystemAnnouncementManager :: delete_announcement($id);
         }
         Display :: display_confirmation_message(get_lang('AnnouncementDeleted'));
@@ -137,11 +137,16 @@ if ($action_todo) {
     $language_list = api_get_languages();
     $language_list_with_keys = array();
     $language_list_with_keys['all'] = get_lang('All');
-    for ($i=0; $i<count($language_list['name']) ; $i++) {
+    for ($i = 0; $i < count($language_list['name']); $i++) {
         $language_list_with_keys[$language_list['folder'][$i]] = $language_list['name'][$i];
     }
 
-    $form->addElement('select', 'lang',get_lang('Language'), $language_list_with_keys);
+    $form->addElement(
+        'select',
+        'lang',
+        get_lang('Language'),
+        $language_list_with_keys
+    );
     $form->addHtmlEditor(
         'content',
         get_lang('Content'),

+ 1 - 2
main/admin/teacher_time_report.php

@@ -7,7 +7,7 @@
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>
  * @package chamilo.admin
  */
-/* INIT SECTION */
+
 // Resetting the course id.
 $cidReset = true;
 
@@ -163,7 +163,6 @@ if (!empty($selectedSession)) {
     );
 
     $reportTitle = sprintf(get_lang('TimeReportForSessionX'), $session['name']);
-
     $courses = SessionManager::get_course_list_by_session_id($selectedSession);
 
     foreach ($courses as $course) {

+ 3 - 3
main/admin/teachers_time_by_session_report.php

@@ -1,14 +1,14 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use \Chamilo\CoreBundle\Entity\Session;
+use \Doctrine\Common\Collections\Criteria;
+
 /**
  * Generate a teacher time report in platform by session only
  * @package chamilo.admin
  */
 
-use \Chamilo\CoreBundle\Entity\Session;
-use \Doctrine\Common\Collections\Criteria;
-
 $cidReset = true;
 
 require_once '../inc/global.inc.php';

+ 29 - 11
main/admin/user_import.php

@@ -148,6 +148,22 @@ function complete_missing_data($user)
         $user['ExpiryDate'] = '';
     }
 
+    if (!isset($user['OfficialCode'])) {
+        $user['OfficialCode'] = '';
+    }
+
+    if (!isset($user['language'])) {
+        $user['language'] = '';
+    }
+
+    if (!isset($user['PhoneNumber'])) {
+        $user['PhoneNumber'] = '';
+    }
+
+    if (!isset($user['OfficialCode'])) {
+        $user['OfficialCode'] = '';
+    }
+
     return $user;
 }
 
@@ -190,10 +206,8 @@ function save_data($users)
                 null,
                 $send_mail
             );
-            if (!is_array($user['Courses']) && !empty($user['Courses'])) {
-                $user['Courses'] = array($user['Courses']);
-            }
-            if (is_array($user['Courses'])) {
+
+            if (isset($user['Courses']) && is_array($user['Courses'])) {
                 foreach ($user['Courses'] as $course) {
                     if (CourseManager::course_exists($course)) {
                         CourseManager::subscribe_user($user_id, $course, $user['Status']);
@@ -215,8 +229,8 @@ function save_data($users)
             // We are sure that the extra field exists.
             foreach ($extra_fields as $extras) {
                 if (isset($user[$extras[1]])) {
-                    $key 	= $extras[1];
-                    $value 	= $user[$extras[1]];
+                    $key = $extras[1];
+                    $value = $user[$extras[1]];
                     UserManager::update_extra_field_value($user_id, $key, $value);
                 }
             }
@@ -429,8 +443,8 @@ if (isset($_POST['formSent']) && $_POST['formSent'] AND
                 '<strong>'.$error_user['UserName'].'</strong> - '.
                 api_get_person_name(
                     $error_user['FirstName'],
-                    $error_user['LastName']).'
-                '.$email;
+                    $error_user['LastName']
+                ).' '.$email;
             $warning_message .= '</li>';
         }
         $warning_message .= '</ul>';
@@ -438,11 +452,16 @@ if (isset($_POST['formSent']) && $_POST['formSent'] AND
 
     // if the warning message is too long then we display the warning message trough a session
     Display::addFlash(Display::return_message($warning_message, 'warning', false));
-
     Display::addFlash(Display::return_message($see_message_import, 'confirmation', false));
 
     if ($error_kind_file) {
-        Display::addFlash(Display::return_message(get_lang('YouMustImportAFileAccordingToSelectedOption'), 'error', false));
+        Display::addFlash(
+            Display::return_message(
+                get_lang('YouMustImportAFileAccordingToSelectedOption'),
+                'error',
+                false
+            )
+        );
     } else {
         header('Location: '.api_get_path(WEB_CODE_PATH).'admin/user_list.php?sec_token='.$tok);
         exit;
@@ -511,7 +530,6 @@ if ($count_fields > 0) {
         $i++;
     }
 }
-
 ?>
     <p><?php echo get_lang('CSVMustLookLike').' ('.get_lang('MandatoryFields').')'; ?> :</p>
     <blockquote>

+ 7 - 1
main/admin/usergroup_import.php

@@ -98,7 +98,13 @@ set_time_limit(0);
 $form = new FormValidator('import_classes');
 $form->addElement('file', 'import_file', get_lang('ImportCSVFileLocation'));
 $group = array();
-$group[] = $form->createElement('radio', 'file_type', '', 'CSV (<a href="example_class.csv" target="_blank">' . get_lang('ExampleCSVFile') . '</a>)', 'csv');
+$group[] = $form->createElement(
+    'radio',
+    'file_type',
+    '',
+    'CSV (<a href="example_class.csv" target="_blank">' . get_lang('ExampleCSVFile') . '</a>)',
+    'csv'
+);
 $form->addGroup($group, '', get_lang('FileType'), null);
 $form->addButtonImport(get_lang('Import'));
 

+ 15 - 17
main/announcements/announcements.php

@@ -59,7 +59,6 @@ api_protect_course_group(GroupManager::GROUP_TOOL_ANNOUNCEMENT);
 Event::event_access_tool(TOOL_ANNOUNCEMENT);
 
 $announcement_id = isset($_GET['id']) ? intval($_GET['id']) : null;
-$origin = isset($_GET['origin']) ? Security::remove_XSS($_GET['origin']) : null;
 $action = isset($_GET['action']) ? Security::remove_XSS($_GET['action']) : 'list';
 
 $announcement_number = AnnouncementManager::getNumberAnnouncements();
@@ -164,8 +163,6 @@ switch ($action) {
             $userIdToSearch = $filterData['user_id'];
         }
 
-        //$searchFormToString = $searchForm->returnForm();
-
         // jqgrid will use this URL to do the selects
         $url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_course_announcements&'.api_get_cidreq().'&title_to_search='.$keyword.'&user_id_to_search='.$userIdToSearch;
         $deleteUrl = api_get_path(WEB_AJAX_PATH).'announcement.ajax.php?a=delete_item&'.api_get_cidreq();
@@ -312,7 +309,6 @@ switch ($action) {
                 if (!api_is_course_coach() ||
                     api_is_element_in_the_session(TOOL_ANNOUNCEMENT, $_GET['id'])
                 ) {
-
                     AnnouncementManager::change_visibility_announcement(
                         $_course,
                         $_GET['id']
@@ -320,7 +316,6 @@ switch ($action) {
                     Display::addFlash(Display::return_message(get_lang('VisibilityChanged')));
                     header('Location: '.$homeUrl);
                     exit;
-
                 }
             }
         }
@@ -364,7 +359,7 @@ switch ($action) {
                     get_lang('RemindInactiveLearnersMailSubject'),
                     api_get_setting('siteName')
                 );
-            } elseif (isset($_GET['remindallinactives']) && $_GET['remindallinactives'] == 'true') {
+            } elseif (isset($_GET['remindallinactives']) && $_GET['remindallinactives'] === 'true') {
                 // we want to remind inactive users. The $_GET['since'] parameter
                 // determines which users have to be warned (i.e the users who have been inactive for x days or more
                 $since = isset($_GET['since']) ? intval($_GET['since']) : 6;
@@ -419,11 +414,11 @@ switch ($action) {
             );
         } else {
             if (!isset($announcement_to_modify)) {
-                $announcement_to_modify = "";
+                $announcement_to_modify = '';
             }
-            $element = CourseManager::addGroupMultiSelect($form, $group_id, array());
-            $form->setRequired($element);
 
+            $element = CourseManager::addGroupMultiSelect($form, $group_properties['iid'], array());
+            $form->setRequired($element);
             $form->addElement(
                 'checkbox',
                 'email_ann',
@@ -459,9 +454,7 @@ switch ($action) {
             $htmlTags .= "<b>".$tag."</b><br />";
         }
 
-        $form->addHtml(
-            "<div class='form-group'><div class='col-sm-2'></div><div class='col-sm-8'><div class='alert alert-info'>".$htmlTags."</div></div></div>"
-        );
+        $form->addLabel('', "<div class='alert alert-info'>".$htmlTags."</div>");
         $form->addHtmlEditor(
             'content',
             get_lang('Description'),
@@ -478,7 +471,12 @@ switch ($action) {
             $form->addCheckBox('send_to_users_in_session', null, get_lang('SendToUsersInSessions'));
         }
 
-        $form->addCheckBox('send_to_hrm_users', null, get_lang('SendAnnouncementCopyToDRH'));
+        $config = api_get_configuration_value('announcements_hide_send_to_hrm_users');
+
+        if ($config === false) {
+            $form->addCheckBox('send_to_hrm_users', null, get_lang('SendAnnouncementCopyToDRH'));
+        }
+
         $form->addButtonSave(get_lang('ButtonPublishAnnouncement'));
         $form->setDefaults($defaults);
 
@@ -606,16 +604,16 @@ if ((api_is_allowed_to_edit(false, true) ||
     (empty($_GET['origin']) || $_GET['origin'] !== 'learnpath')
 ) {
     if (in_array($action, array('add', 'modify', 'view'))) {
-        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."&origin=".$origin."'>".
+        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."'>".
             Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM)."</a>";
     } else {
-        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."&action=add&origin=".$origin."'>".
+        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."&action=add'>".
             Display::return_icon('new_announce.png', get_lang('AddAnnouncement'), '', ICON_SIZE_MEDIUM)."</a>";
     }
     $show_actions = true;
 } else {
     if (in_array($action, array('view'))) {
-        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."&origin=".$origin."'>".
+        $actionsLeft .= "<a href='".api_get_self()."?".api_get_cidreq()."'>".
             Display::return_icon('back.png', get_lang('Back'), '', ICON_SIZE_MEDIUM)."</a>";
     }
 }
@@ -640,7 +638,7 @@ if ($show_actions) {
 
 echo $content;
 
-if (empty($_GET['origin']) or $_GET['origin'] !== 'learnpath') {
+if (empty($_GET['origin']) || $_GET['origin'] !== 'learnpath') {
     //we are not in learnpath tool
     Display::display_footer();
 }

+ 1 - 0
main/auth/courses.php

@@ -203,6 +203,7 @@ switch ($action) {
             );
         } else {
             header('Location: ' . api_get_self());
+            exit;
         }
         break;
     case 'display_random_courses':

+ 6 - 11
main/auth/courses_controller.php

@@ -43,8 +43,7 @@ class CoursesController
         $data['user_courses'] = $this->model->get_courses_of_user($user_id);
         $data['user_course_categories'] = $this->model->get_user_course_categories();
         $data['courses_in_category'] = $this->model->get_courses_in_category();
-        $data['all_user_categories'] = $this->model->get_user_course_categories(
-        );
+        $data['all_user_categories'] = $this->model->get_user_course_categories();
         $data['action'] = $action;
         $data['message'] = $message;
 
@@ -116,7 +115,6 @@ class CoursesController
         $data['browse_course_categories'] = $browse_course_categories;
         $data['code'] = Security::remove_XSS($category_code);
 
-
         // getting all the courses to which the user is subscribed to
         $curr_user_id = api_get_user_id();
         $user_courses = $this->model->get_courses_of_user($curr_user_id);
@@ -353,7 +351,9 @@ class CoursesController
     /**
      * Unsubscribe user from a course
      * render to listing view
-     * @param string    Course code
+     * @param string $course_code
+     * @param string $search_term
+     * @param string $category_code
      */
     public function unsubscribe_user_from_course($course_code, $search_term = null, $category_code = null)
     {
@@ -383,9 +383,7 @@ class CoursesController
     public function getCoursesCategoriesBlock($code = null, $hiddenLinks = false, $limit = null)
     {
         $categories = $this->model->browse_course_categories();
-
         $html = '';
-
         if (!empty($categories)) {
             $action = 'display_courses';
             foreach ($categories[0] as $category) {
@@ -517,8 +515,7 @@ class CoursesController
         $sessionId,
         $sessionName,
         $checkRequirements = false
-    )
-    {
+    ) {
         if ($checkRequirements) {
             $url = api_get_path(WEB_AJAX_PATH);
             $url .= 'sequence.ajax.php?';
@@ -544,9 +541,7 @@ class CoursesController
 
         $catalogSessionAutoSubscriptionAllowed = false;
 
-        if (
-            api_get_setting('catalog_allow_session_auto_subscription') === 'true'
-        ) {
+        if (api_get_setting('catalog_allow_session_auto_subscription') === 'true') {
             $catalogSessionAutoSubscriptionAllowed = true;
         }
 

+ 74 - 25
main/auth/external_login/functions.inc.php

@@ -16,7 +16,8 @@
  *       password
  *   or false if no data
  * */
-function external_get_user_info($login, $password) {
+function external_get_user_info($login, $password)
+{
     //Those are the mandatory fields for user creation.
     //See external_add_user function for all the fields you can have.
     $table = USERINFO_TABLE;
@@ -43,22 +44,22 @@ function external_get_user_info($login, $password) {
         default:
             $status = STUDENT;
     }
+
     // Language
     switch ($user_info['language']) {
-        case 'FR' :
+        case 'FR':
             $language = 'french';
             break;
-        case 'EN' :
+        case 'EN':
             $language = 'english';
             break;
-        default :
+        default:
             $language = 'english';
             break;
     }
     //Can Send Message ?
     $can_send_message = ($user_info['can_send_message'] == 1) ? 'yes' : 'no';
 
-
     $u = array(
         'firstname' => $user_info['firstname'],
         'lastname' => $user_info['lastname'],
@@ -93,34 +94,64 @@ function external_get_user_info($login, $password) {
   firstname, lastname, status, email, login, password
  * @return mixed   new user id - if the new user creation succeeds, false otherwise
  * */
-function external_add_user($u) {
+function external_add_user($u)
+{
     //Setting default
-    if (empty($u['password']))
+    if (empty($u['password'])) {
         $u['password'] = null;
-    if (empty($u['status']))
+    }
+    if (empty($u['status'])) {
         $u['status'] = 5;
-    if (!isset($u['official_code']))
+    }
+    if (!isset($u['official_code'])) {
         $u['official_code'] = '';
-    if (!isset($u['language']))
+    }
+    if (!isset($u['language'])) {
         $u['language'] = '';
-    if (!isset($u['phone']))
+    }
+    if (!isset($u['phone'])) {
         $u['phone'] = '';
-    if (!isset($u['picture_uri']))
+    }
+    if (!isset($u['picture_uri'])) {
         $u['picture_uri'] = '';
-    if (!isset($u['auth_source']))
+    }
+    if (!isset($u['auth_source'])) {
         $u['auth_source'] = PLATFORM_AUTH_SOURCE;
-    if (!isset($u['expiration_date']))
+    }
+    if (!isset($u['expiration_date'])) {
         $u['expiration_date'] = '';
-    if (!isset($u['active']))
+    }
+    if (!isset($u['active'])) {
         $u['active'] = 1;
-    if (!isset($u['hr_dept_id']))
-        $u['hr_dept_id'] = 0; //id of responsible HR
-    if (!isset($u['extra']))
+    }
+    if (!isset($u['hr_dept_id'])) {
+        $u['hr_dept_id'] = 0;
+    } //id of responsible HR
+    if (!isset($u['extra'])) {
         $u['extra'] = null;
-    if (!isset($u['encrypt_method']))
+    }
+    if (!isset($u['encrypt_method'])) {
         $u['encrypt_method'] = '';
+    }
 
-    $chamilo_uid = UserManager::create_user($u['firstname'], $u['lastname'], $u['status'], $u['email'], $u['username'], $u['password'], $u['official_code'], $u['language'], $u['phone'], $u['picture_uri'], $u['auth_source'], $u['expiration_date'], $u['active'], $u['hr_dept_id'], $u['extra'], $u['encrypt_method']);
+    $chamilo_uid = UserManager::create_user(
+        $u['firstname'],
+        $u['lastname'],
+        $u['status'],
+        $u['email'],
+        $u['username'],
+        $u['password'],
+        $u['official_code'],
+        $u['language'],
+        $u['phone'],
+        $u['picture_uri'],
+        $u['auth_source'],
+        $u['expiration_date'],
+        $u['active'],
+        $u['hr_dept_id'],
+        $u['extra'],
+        $u['encrypt_method']
+    );
     return $chamilo_uid;
 }
 
@@ -151,10 +182,30 @@ function external_add_user($u) {
  * @return boolean|null
  * @author ndiechburg <noel@cblue.be>
  * */
-function external_update_user($new_user) {
+function external_update_user($new_user)
+{
     $old_user = api_get_user_info($new_user['user_id']);
     $u = array_merge($old_user, $new_user);
-    $updated = UserManager::update_user($u['user_id'], $u['firstname'], $u['lastname'], $u['username'], null, $u['auth_source'], $u['email'], $u['status'], $u['official_code'], $u['phone'], $u['picture_uri'], $u['expiration_date'], $u['active'], $u['creator_id'], $u['hr_dept_id'], $u['extra'], $u['language'], '');
+    $updated = UserManager::update_user(
+        $u['user_id'],
+        $u['firstname'],
+        $u['lastname'],
+        $u['username'],
+        null,
+        $u['auth_source'],
+        $u['email'],
+        $u['status'],
+        $u['official_code'],
+        $u['phone'],
+        $u['picture_uri'],
+        $u['expiration_date'],
+        $u['active'],
+        $u['creator_id'],
+        $u['hr_dept_id'],
+        $u['extra'],
+        $u['language'],
+        ''
+    );
     if (isset($u['courses']) && !empty($u['courses'])) {
         $autoSubscribe = explode('|', $u['courses']);
         foreach ($autoSubscribe as $code) {
@@ -171,6 +222,4 @@ function external_update_user($new_user) {
       $table = Database::get_main_table(TABLE_MAIN_ADMIN);
       $res = Database::query("SELECT * from $table WHERE user_id = ".$u['user_id']);
       } */
-}
-
-?>
+}

+ 14 - 7
main/auth/external_login/login.ws.php

@@ -1,6 +1,8 @@
 <?php
 /* For licensing terms, see /license.txt */
 
+use ChamiloSession as Session;
+
 // External login module : WS (for Web Services)
 /**
  *
@@ -8,8 +10,6 @@
  * have 'ws' in his auth_source field instead of 'platform'.
  */
 
-use ChamiloSession as Session;
-
 // Configure the web service URL here. e.g. http://174.1.1.19:8020/login.asmx?WSDL
 $wsUrl = '';
 
@@ -21,7 +21,7 @@ $isValid = loginWSAuthenticate($login, $password, $wsUrl);
 // if the authentication was successful, proceed
 if ($isValid === 1) {
     //error_log('WS authentication worked');
-    $chamiloUser = api_get_user_info($login);
+    $chamiloUser = api_get_user_info_from_username($login);
     $loginFailed = false;
     $_user['user_id'] = $chamiloUser['user_id'];
     $_user['status'] = (isset($chamiloUser['status']) ? $chamiloUser['status'] : 5);
@@ -45,9 +45,10 @@ if ($isValid === 1) {
  * @param string The cleartext password, as provided in form
  * @param string The WS URL, as provided at the beginning of this script
  */
-function loginWSAuthenticate($username, $password, $wsUrl) {
+function loginWSAuthenticate($username, $password, $wsUrl)
+{
     // check params
-    if (empty($username) or empty($password) or empty($wsUrl)) {
+    if (empty($username) || empty($password) || empty($wsUrl)) {
         return false;
     }
     // Create new SOAP client instance
@@ -62,7 +63,7 @@ function loginWSAuthenticate($username, $password, $wsUrl) {
     // Complete password con PKCS7-specific padding
     $blockSize = 16;
     $padding = $blockSize - (strlen($password)%$blockSize);
-    $password .= str_repeat(chr($padding),$padding);
+    $password .= str_repeat(chr($padding), $padding);
     $cipher = new Crypt_AES(CRYPT_AES_MODE_CFB);
     $cipher->setKeyLength(128);
     $cipher->setKey($key);
@@ -83,7 +84,13 @@ function loginWSAuthenticate($username, $password, $wsUrl) {
     $passCrypted = base64_encode($cipheredPass);
     // The call to the webservice will change depending on your definition
     try {
-        $response = $client->validateUser(array('user' => $username, 'pass' => $passCrypted, 'system' => 'chamilo'));
+        $response = $client->validateUser(
+            array(
+                'user' => $username,
+                'pass' => $passCrypted,
+                'system' => 'chamilo',
+            )
+        );
     } catch (SoapFault $fault) {
         error_log('Caught something');
         if ($fault->faultstring != 'Could not connect to host') {

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

@@ -3,7 +3,7 @@
   Template to automatically create a new user with information from anywhere.
   This file is loaded by main/inc/local.inc.php
   To use it please add this line to main/inc/conf/configuration.php :
-  $extAuthSource["external_logininfo"]["newUser"] = $_configuration['root_sys'].$_configuration['code_append']."auth/external_logininfo/newUser.php";
+  $extAuthSource["external_logininfo"]["newUser"] = $_configuration['root_sys']."main/auth/external_logininfo/newUser.php";
 
   You also have to implements the external_get_user_info function in functions.inc.php
  */

+ 2 - 2
main/auth/gotocourse.php

@@ -11,8 +11,8 @@
  *
  */
 
-require '../inc/global.inc.php';
-require_once(api_get_path(SYS_PATH).'main/auth/cas/authcas.php');
+require_once '../inc/global.inc.php';
+require_once api_get_path(SYS_PATH).'main/auth/cas/authcas.php';
 $msg = null;
 if (isset($_GET['firstpage'])) {
     $firstpage = $_GET['firstpage'];

+ 8 - 21
main/auth/profile.php

@@ -22,8 +22,6 @@ if (api_get_setting('allow_social_tool') == 'true') {
     $this_section = SECTION_MYPROFILE;
 }
 
-//$htmlHeadXtra[] = api_get_password_checker_js('#username', '#password1');
-
 $_SESSION['this_section'] = $this_section;
 
 if (!(isset($_user['user_id']) && $_user['user_id']) || api_is_anonymous($_user['user_id'], true)) {
@@ -78,7 +76,7 @@ function show_icon_edit(element_html) {
 </script>';
 
 $jquery_ready_content = '';
-if (api_get_setting('allow_message_tool') == 'true') {
+if (api_get_setting('allow_message_tool') === 'true') {
     $jquery_ready_content = <<<EOF
     $(".message-content .message-delete").click(function(){
         $(this).parents(".message-content").animate({ opacity: "hide" }, "slow");
@@ -259,8 +257,8 @@ if (defined('CONFVAL_ASK_FOR_OFFICIAL_CODE') && CONFVAL_ASK_FOR_OFFICIAL_CODE ==
     $form->applyFilter('official_code', 'stripslashes');
     $form->applyFilter('official_code', 'trim');
     $form->applyFilter('official_code', 'html_filter');
-    if (api_get_setting('registration', 'officialcode') == 'true' && api_get_setting('profile',
-            'officialcode') == 'true'
+    if (api_get_setting('registration', 'officialcode') === 'true' &&
+        api_get_setting('profile', 'officialcode') === 'true'
     ) {
         $form->addRule('official_code', get_lang('ThisFieldIsRequired'), 'required');
     }
@@ -566,6 +564,7 @@ if ($form->validate()) {
     // set password if a new one was provided
     $validPassword = false;
     $passwordWasChecked = false;
+
     if ($user &&
         (!empty($user_data['password0']) &&
         !empty($user_data['password1'])) ||
@@ -574,8 +573,9 @@ if ($form->validate()) {
     ) {
         $passwordWasChecked = true;
         $validPassword = UserManager::isPasswordValid(
+            $user->getPassword(),
             $user_data['password0'],
-            $user
+            $user->getSalt()
         );
 
         if ($validPassword) {
@@ -610,9 +610,7 @@ if ($form->validate()) {
                 $changeemail = $user_data['email'];
             }
 
-            if (!check_user_email($user_data['email']) &&
-                empty($user_data['password0'])
-            ){
+            if (!check_user_email($user_data['email']) && empty($user_data['password0'])) {
                 Display::addFlash(
                     Display:: return_message(
                         get_lang('ToChangeYourEmailMustTypeYourPassword'),
@@ -721,7 +719,7 @@ if ($form->validate()) {
     $available_values_to_modify = array();
     foreach ($profile_list as $key => $status) {
         if ($status == 'true') {
-            switch($key) {
+            switch ($key) {
                 case 'login':
                     $available_values_to_modify[] = 'username';
                     break;
@@ -768,11 +766,6 @@ if ($form->validate()) {
         }
         if (isset($password) && in_array('password', $available_values_to_modify)) {
             $changePassword = true;
-            /*$password = api_get_encrypted_password($password);
-            $sql .= " password = '".Database::escape_string($password)."'";*/
-        } else {
-            // remove trailing , from the query we have so far
-            //$sql = rtrim($sql, ',');
         }
     } else {
         if (isset($changeemail) && !isset($password) && in_array('email', $available_values_to_modify)) {
@@ -783,17 +776,11 @@ if ($form->validate()) {
                     $sql .= " email = '".Database::escape_string($changeemail)."' ";
                 }
                 $changePassword = true;
-                /*$password = api_get_encrypted_password($password);
-                $sql .= " password = '".Database::escape_string($password)."'";*/
-            } else {
-                // remove trailing , from the query we have so far
-                //$sql = rtrim($sql, ',');
             }
         }
     }
 
     $sql = rtrim($sql, ',');
-
     if ($changePassword && !empty($password)) {
         UserManager::updatePassword(api_get_user_id(), $password);
     }

+ 4 - 5
main/auth/set_temp_password.php

@@ -1,13 +1,14 @@
 <?php
 /* For license terms, see /license.txt */
+
+use ChamiloSession as Session;
+
 /**
  * This page aims at requesting a password from a user to access a course
  * protected by password. If the password matches the course password, we
  * store the fact that user can access it during its session
  */
 
-use ChamiloSession as Session;
-
 $cidReset = true;
 require_once '../inc/global.inc.php';
 $this_section = SECTION_COURSES;
@@ -21,9 +22,7 @@ $user_id = api_get_user_id();
 if (empty($course_id)) {
     api_not_allowed();
 }
-/**
- * Code
- */
+
 $course_info = api_get_course_info_by_id($course_id);
 
 $tpl = new Template(null);

+ 1 - 0
main/badge/assertion.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  * Show information about a new assertion
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>

+ 1 - 0
main/badge/class.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  * Show information about the OpenBadge class
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>

+ 1 - 0
main/badge/criteria.php

@@ -1,5 +1,6 @@
 <?php
 /* For licensing terms, see /license.txt */
+
 /**
  * Show information about OpenBadge citeria
  * @author Angel Fernando Quiroz Campos <angel.quiroz@beeznest.com>

部分文件因文件數量過多而無法顯示