Merge branch 'MDL-41506-master' of git://github.com/FMCorz/moodle
authorDan Poltawski <dan@moodle.com>
Tue, 21 Jan 2014 15:29:20 +0000 (23:29 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 21 Jan 2014 15:29:20 +0000 (23:29 +0800)
Conflicts:
theme/bootstrapbase/style/moodle.css

356 files changed:
admin/settings/plugins.php
admin/tests/behat/behat_admin.php
admin/tests/behat/filter_users.feature
admin/tests/behat/upload_users.feature
admin/tool/behat/cli/util.php
admin/tool/behat/tests/behat/data_generators.feature
admin/tool/behat/upgrade.txt [new file with mode: 0644]
admin/tool/qeupgradehelper/locallib.php
admin/tool/uploadcourse/tests/behat/create.feature
admin/tool/uploadcourse/tests/behat/update.feature
auth/tests/behat/behat_auth.php
backup/converter/moodle1/lib.php
backup/moodle2/restore_course_task.class.php
backup/moodle2/restore_stepslib.php
backup/util/ui/tests/behat/behat_backup.php
badges/backpack_form.php
badges/backpackconnect.php
badges/lib/backpacklib.php
badges/recipients.php
badges/renderer.php
badges/tests/behat/add_badge.feature
badges/tests/behat/award_badge.feature
badges/upgrade.txt
blocks/activity_modules/tests/behat/block_activity_modules.feature
blocks/comments/tests/behat/behat_block_comments.php
blocks/html/styles.css [deleted file]
blocks/private_files/edit.php
blocks/private_files/lang/en/block_private_files.php
blocks/private_files/styles.css [new file with mode: 0644]
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/styles.css
blocks/social_activities/block_social_activities.php
blocks/social_activities/styles.css [new file with mode: 0644]
blocks/tests/behat/behat_blocks.php
calendar/lib.php
cohort/tests/behat/add_cohort.feature
cohort/tests/behat/behat_cohort.php
cohort/tests/behat/upload_cohort_users.feature
comment/locallib.php
completion/tests/behat/restrict_activity_by_date.feature
config-dist.php
course/dnduploadlib.php
course/lib.php
course/tests/behat/course_controls.feature
course/tests/externallib_test.php
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js
course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js
course/yui/src/modchooser/js/modchooser.js
course/yui/src/toolboxes/js/toolbox.js
files/tests/behat/course_files.feature
filter/upgrade.txt
grade/grading/form/guide/styles.css
grade/report/grader/styles.css
grade/report/user/lib.php
grade/tests/behat/behat_grade.php [new file with mode: 0644]
grade/tests/behat/grade_view.feature [new file with mode: 0644]
group/autogroup.php
group/delete.php
group/group.php
group/grouping.php
group/index.php
group/tests/behat/behat_groups.php
group/tests/behat/create_groups.feature
group/tests/behat/groups_import.feature
install/lang/es/error.php
install/lang/fr/error.php
lang/en/admin.php
lang/en/block.php
lang/en/mnet.php
lang/en/moodle.php
lang/en/question.php
lib/accesslib.php
lib/adminlib.php
lib/ajax/blocks.php
lib/ajax/getnavbranch.php
lib/badgeslib.php
lib/behat/classes/behat_command.php
lib/behat/classes/behat_config_manager.php
lib/classes/event/course_category_created.php [new file with mode: 0644]
lib/classes/event/course_category_updated.php [new file with mode: 0644]
lib/classes/event/email_failed.php [new file with mode: 0644]
lib/classes/event/mnet_access_control_created.php [new file with mode: 0644]
lib/classes/event/mnet_access_control_updated.php [new file with mode: 0644]
lib/coursecatlib.php
lib/cronlib.php
lib/customcheckslib.php
lib/db/install.xml
lib/db/upgrade.php
lib/dml/moodle_database.php
lib/dml/mssql_native_moodle_database.php
lib/dml/mysqli_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/environmentlib.php
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js [new file with mode: 0644]
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js [new file with mode: 0644]
lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js [new file with mode: 0644]
lib/form/yui/passwordunmask/passwordunmask.js [deleted file]
lib/form/yui/src/passwordunmask/build.json [new file with mode: 0644]
lib/form/yui/src/passwordunmask/js/passwordunmask.js [new file with mode: 0644]
lib/form/yui/src/passwordunmask/meta/passwordunmask.json [new file with mode: 0644]
lib/google/curlio.php
lib/grade/grade_item.php
lib/grade/tests/fixtures/lib.php
lib/grade/tests/grade_item_test.php
lib/htaccess
lib/javascript-static.js
lib/moodlelib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/phpunit/bootstrap.php
lib/phpunit/classes/hint_resultprinter.php
lib/setuplib.php
lib/testing/lib.php
lib/tests/accesslib_test.php
lib/tests/behat/behat_deprecated.php
lib/tests/behat/behat_hooks.php
lib/tests/events_test.php [new file with mode: 0644]
lib/tests/upgradelib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-debug.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue-min.js
lib/yui/build/moodle-core-chooserdialogue/moodle-core-chooserdialogue.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/src/chooserdialogue/js/chooserdialogue.js
lib/yui/src/notification/js/dialogue.js
login/signup_form.php
message/index.php
message/lib.php
message/tests/behat/behat_message.php
message/tests/behat/display_history.feature
message/tests/behat/manage_contacts.feature
message/tests/behat/search_history.feature
mnet/lib.php
mnet/tests/events_test.php [new file with mode: 0644]
mod/assign/backup/moodle2/backup_assign_stepslib.php
mod/assign/db/access.php
mod/assign/db/install.xml
mod/assign/db/upgrade.php
mod/assign/externallib.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/locallib.php
mod/assign/mod_form.php
mod/assign/module.js
mod/assign/quickgradingform.php
mod/assign/settings.php
mod/assign/tests/behat/file_submission.feature
mod/assign/tests/behat/group_submission.feature
mod/assign/tests/generator/lib.php
mod/assign/tests/locallib_test.php
mod/assign/tests/upgradelib_test.php
mod/assign/upgrade.txt
mod/assign/upgradelib.php
mod/assign/version.php
mod/assignment/assignment.js [deleted file]
mod/assignment/backup/moodle2/restore_assignment_activity_task.class.php
mod/assignment/backup/moodle2/restore_assignment_stepslib.php
mod/assignment/classes/plugininfo/assignment.php
mod/assignment/db/install.xml
mod/assignment/db/upgrade.php
mod/assignment/db/upgradelib.php [new file with mode: 0644]
mod/assignment/delete.php [deleted file]
mod/assignment/grade.php [deleted file]
mod/assignment/lang/en/assignment.php
mod/assignment/lib.php
mod/assignment/locallib.php [deleted file]
mod/assignment/mod_form.php
mod/assignment/renderer.php [deleted file]
mod/assignment/settings.php [deleted file]
mod/assignment/styles.css [deleted file]
mod/assignment/submissions.php [deleted file]
mod/assignment/tests/generator_test.php [deleted file]
mod/assignment/type/offline/assignment.class.php [deleted file]
mod/assignment/type/online/all.php [deleted file]
mod/assignment/type/online/assignment.class.php [deleted file]
mod/assignment/type/online/classes/event/assessable_uploaded.php [deleted file]
mod/assignment/type/online/file.php [deleted file]
mod/assignment/type/upgrade.txt
mod/assignment/type/upload/assignment.class.php [deleted file]
mod/assignment/type/upload/classes/event/assessable_submitted.php [deleted file]
mod/assignment/type/upload/classes/event/assessable_uploaded.php [deleted file]
mod/assignment/type/upload/notes.php [deleted file]
mod/assignment/type/upload/upload.php [deleted file]
mod/assignment/type/upload/upload_form.php [deleted file]
mod/assignment/type/uploadsingle/assignment.class.php [deleted file]
mod/assignment/type/uploadsingle/upload.php [deleted file]
mod/assignment/type/uploadsingle/upload_form.php [deleted file]
mod/assignment/upload.php [deleted file]
mod/assignment/version.php
mod/assignment/view.php
mod/feedback/item/captcha/lib.php
mod/forum/lib.php
mod/forum/maildigest.php
mod/imscp/backup/moodle1/lib.php
mod/imscp/locallib.php
mod/lesson/classes/event/essay_assessed.php [new file with mode: 0644]
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/tests/events_test.php
mod/lti/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/lti/classes/event/course_module_viewed.php [moved from blocks/private_files/edit_form.php with 54% similarity]
mod/lti/index.php
mod/lti/launch.php
mod/lti/view.php
mod/quiz/attemptlib.php
mod/quiz/backup/moodle1/lib.php
mod/quiz/backup/moodle2/backup_quiz_stepslib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/db/install.xml
mod/quiz/db/upgrade.php
mod/quiz/editlib.php
mod/quiz/lib.php
mod/quiz/locallib.php
mod/quiz/report/reportlib.php
mod/quiz/startattempt.php
mod/quiz/styles.css
mod/quiz/summary.php
mod/quiz/tests/editlib_test.php
mod/quiz/upgrade.txt
mod/quiz/version.php
mod/resource/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/resource/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/resource/index.php
mod/resource/tests/events_test.php [new file with mode: 0644]
mod/resource/view.php
mod/scorm/classes/event/attempt_deleted.php [new file with mode: 0644]
mod/scorm/classes/event/course_module_instance_list_viewed.php [new file with mode: 0644]
mod/scorm/classes/event/course_module_viewed.php [new file with mode: 0644]
mod/scorm/classes/event/interactions_viewed.php [new file with mode: 0644]
mod/scorm/classes/event/report_viewed.php [new file with mode: 0644]
mod/scorm/classes/event/sco_launched.php [new file with mode: 0644]
mod/scorm/classes/event/tracks_viewed.php [new file with mode: 0644]
mod/scorm/classes/event/user_report_viewed.php [new file with mode: 0644]
mod/scorm/index.php
mod/scorm/lang/en/scorm.php
mod/scorm/loadSCO.php
mod/scorm/locallib.php
mod/scorm/player.php
mod/scorm/report.php
mod/scorm/report/basic/report.php
mod/scorm/report/interactions/report.php
mod/scorm/report/objectives/report.php
mod/scorm/report/userreport.php
mod/scorm/report/userreportinteractions.php
mod/scorm/report/userreporttracks.php
mod/scorm/request.js
mod/scorm/tests/behat/add_scorm.feature
mod/scorm/tests/event_test.php [new file with mode: 0644]
mod/scorm/tests/packages/readme_moodle.txt [new file with mode: 0644]
mod/scorm/tests/packages/singlesco_scorm12.zip [new file with mode: 0644]
mod/scorm/view.js
mod/scorm/view.php
mod/wiki/edit_form.php
mod/wiki/pagelib.php
phpunit.xml.dist
question/classes/bank/search/category_condition.php [new file with mode: 0644]
question/classes/bank/search/condition.php [new file with mode: 0644]
question/classes/bank/search/hidden_condition.php [new file with mode: 0644]
question/editlib.php
question/engine/bank.php
question/engine/questionattempt.php
question/engine/tests/helpers.php
question/engine/tests/questionusage_autosave_test.php
question/format/blackboard_six/formatpool.php
question/format/blackboard_six/formatqti.php
question/format/examview/format.php
question/format/gift/format.php
question/format/webct/format.php
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
question/question.php
question/tests/behat/copy_questions.feature [new file with mode: 0644]
question/tests/behat/delete_questions.feature
question/tests/behat/edit_questions.feature
question/tests/behat/preview_question.feature
question/type/calculated/questiontype.php
question/type/edit_question_form.php
question/type/essay/backup/moodle1/lib.php
question/type/essay/backup/moodle2/backup_qtype_essay_plugin.class.php
question/type/essay/backup/moodle2/restore_qtype_essay_plugin.class.php
question/type/essay/db/install.xml
question/type/essay/db/upgrade.php
question/type/essay/edit_essay_form.php
question/type/essay/lang/en/qtype_essay.php
question/type/essay/question.php
question/type/essay/questiontype.php
question/type/essay/renderer.php
question/type/essay/tests/helper.php
question/type/essay/tests/question_test.php
question/type/essay/version.php
question/type/multianswer/edit_multianswer_form.php
question/upgrade.txt
question/yui/build/moodle-question-searchform/moodle-question-searchform-debug.js [new file with mode: 0644]
question/yui/build/moodle-question-searchform/moodle-question-searchform-min.js [new file with mode: 0644]
question/yui/build/moodle-question-searchform/moodle-question-searchform.js [new file with mode: 0644]
question/yui/src/searchform/build.json [new file with mode: 0644]
question/yui/src/searchform/js/searchform.js [new file with mode: 0644]
question/yui/src/searchform/meta/searchform.json [new file with mode: 0644]
report/loglive/index.php
report/loglive/lang/en/report_loglive.php
report/security/lang/en/report_security.php
report/security/locallib.php
repository/recent/tests/behat/add_recent.feature
repository/tests/behat/behat_filepicker.php
repository/tests/behat/cancel_add_file.feature
repository/tests/behat/create_shortcut.feature
repository/tests/behat/delete_files.feature
repository/tests/behat/overwrite_file.feature
repository/upload/tests/behat/upload_file.feature
theme/arialist/style/pagelayout.css
theme/base/style/blocks.css
theme/base/style/core.css
theme/base/style/filemanager.css
theme/base/style/message.css
theme/base/style/pagelayout.css
theme/binarius/style/pagelayout.css
theme/bootstrapbase/config.php
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/buttons.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/filemanager.less
theme/bootstrapbase/less/moodle/grade.less [new file with mode: 0644]
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/less/moodle/user.less
theme/bootstrapbase/renderers/core_renderer.php
theme/bootstrapbase/style/moodle.css
theme/boxxie/style/core.css
theme/brick/style/pagelayout.css
theme/canvas/style/mods.css
theme/clean/config.php
theme/formfactor/style/mods.css
theme/fusion/style/core.css
theme/fusion/style/pagelayout.css
theme/magazine/style/layout.css
theme/nimble/style/pagelayout.css
theme/nonzero/style/pagelayout.css
theme/overlay/style/pagelayout.css
theme/sky_high/style/pagelayout.css
theme/standard/style/modules.css
version.php

index 283d02a..2fa4e27 100644 (file)
@@ -25,6 +25,9 @@
 */
 
 if ($hassiteconfig) {
+    /* @var admin_root $ADMIN */
+    $ADMIN->locate('modules')->set_sorting(true);
+
     $ADMIN->add('modules', new admin_page_pluginsoverview());
 
     // activity modules
@@ -142,31 +145,7 @@ if ($hassiteconfig) {
     // "filtersettings" settingpage
     $temp = new admin_settingpage('commonfiltersettings', new lang_string('commonfiltersettings', 'admin'));
     if ($ADMIN->fulltree) {
-        $cachetimes = array(
-            604800 => new lang_string('numdays','',7),
-            86400 => new lang_string('numdays','',1),
-            43200 => new lang_string('numhours','',12),
-            10800 => new lang_string('numhours','',3),
-            7200 => new lang_string('numhours','',2),
-            3600 => new lang_string('numhours','',1),
-            2700 => new lang_string('numminutes','',45),
-            1800 => new lang_string('numminutes','',30),
-            900 => new lang_string('numminutes','',15),
-            600 => new lang_string('numminutes','',10),
-            540 => new lang_string('numminutes','',9),
-            480 => new lang_string('numminutes','',8),
-            420 => new lang_string('numminutes','',7),
-            360 => new lang_string('numminutes','',6),
-            300 => new lang_string('numminutes','',5),
-            240 => new lang_string('numminutes','',4),
-            180 => new lang_string('numminutes','',3),
-            120 => new lang_string('numminutes','',2),
-            60 => new lang_string('numminutes','',1),
-            30 => new lang_string('numseconds','',30),
-            0 => new lang_string('no')
-        );
         $items = array();
-        $items[] = new admin_setting_configselect('cachetext', new lang_string('cachetext', 'admin'), new lang_string('configcachetext', 'admin'), 60, $cachetimes);
         $items[] = new admin_setting_configselect('filteruploadedfiles', new lang_string('filteruploadedfiles', 'admin'), new lang_string('configfilteruploadedfiles', 'admin'), 0,
                 array('0' => new lang_string('none'), '1' => new lang_string('allfiles'), '2' => new lang_string('htmlfilesonly')));
         $items[] = new admin_setting_configcheckbox('filtermatchoneperpage', new lang_string('filtermatchoneperpage', 'admin'), new lang_string('configfiltermatchoneperpage', 'admin'), 0);
index 58b7775..2a02183 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Steps definitions related with administration.
  *
- * @package   core
+ * @package   core_admin
  * @category  test
  * @copyright 2013 David Monllaó
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -35,7 +35,7 @@ use Behat\Behat\Context\Step\Given as Given,
 /**
  * Site administration level steps definitions.
  *
- * @package    core
+ * @package    core_admin
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index bb5e657..4b7f6ef 100644 (file)
@@ -8,9 +8,9 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
     Given the following "users" exists:
       | username | firstname | lastname | email | auth | confirmed |
       | user1 | User | One | one@asd.com | manual | 0 |
-      | user2 | User | Two | one@asd.com | ldap | 1 |
-      | user3 | User | Three | one@asd.com | manual | 1 |
-      | user4 | User | Four | one@asd.com | ldap | 0 |
+      | user2 | User | Two | two@asd.com | ldap | 1 |
+      | user3 | User | Three | three@asd.com | manual | 1 |
+      | user4 | User | Four | four@asd.com | ldap | 0 |
     And the following "cohorts" exists:
       | name | idnumber |
       | Cohort 1 | CH1 |
@@ -23,8 +23,8 @@ Feature: An administrator can filter user accounts by role, cohort and other pro
       | user2 | C1 | student |
       | user3 | C1 | student |
     And I log in as "admin"
-    And I add "user2" user to "CH1" cohort
-    And I add "user3" user to "CH1" cohort
+    And I add "User Two (two@asd.com)" user to "CH1" cohort members
+    And I add "User Three (three@asd.com)" user to "CH1" cohort members
     And I follow "Browse list of users"
 
   @javascript
index b276f44..5cde0e8 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_admin @_only_local
+@core @core_admin @_file_upload
 Feature: Upload users
   In order to add users to the system
   As an admin
index 790406f..d843bf2 100644 (file)
@@ -127,8 +127,8 @@ if ($options['install']) {
     mtrace("Acceptance tests site dropped");
 } else if ($options['enable']) {
     behat_util::start_test_mode();
-    $runtestscommand = behat_command::get_behat_command() . ' --config '
-        . $CFG->behat_dataroot . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
+    $runtestscommand = behat_command::get_behat_command(true) .
+        ' --config ' . behat_config_manager::get_behat_cli_config_filepath();
     mtrace("Acceptance tests environment enabled on $CFG->behat_wwwroot, to run the tests use:\n " . $runtestscommand);
 } else if ($options['disable']) {
     behat_util::stop_test_mode();
index 8f21a72..558c848 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_behat @_only_local
+@tool @tool_behat
 Feature: Set up contextual data for tests
   In order to write tests quickly
   As a developer
diff --git a/admin/tool/behat/upgrade.txt b/admin/tool/behat/upgrade.txt
new file mode 100644 (file)
index 0000000..e677b90
--- /dev/null
@@ -0,0 +1,6 @@
+This files describes API changes in the assign code.
+
+=== 2.7 ===
+
+* @_only_local tag used in .feature files replaced by @_file_upload tag
+* @_alerts tag used in .feature files replaced by @_alert tag
index f471b23..2c1d933 100644 (file)
@@ -638,10 +638,10 @@ function tool_qeupgradehelper_load_question($questionid, $quizid) {
     global $CFG, $DB;
 
     $question = $DB->get_record_sql('
-            SELECT q.*, qqi.grade AS maxmark
+            SELECT q.*, qqi.maxmark
             FROM {question} q
-            JOIN {quiz_question_instances} qqi ON qqi.question = q.id
-            WHERE q.id = :questionid AND qqi.quiz = :quizid',
+            JOIN {quiz_question_instances} qqi ON qqi.questionid = q.id
+            WHERE q.id = :questionid AND qqi.quizid = :quizid',
             array('questionid' => $questionid, 'quizid' => $quizid));
 
     if (tool_qeupgradehelper_is_upgraded()) {
index ce2c98b..c1bf56f 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_uploadcourse @_only_local
+@tool @tool_uploadcourse @_file_upload
 Feature: An admin can create courses using a CSV file
   In order to create courses using a CSV file
   As an admin
index 457f07d..4ac4f4e 100644 (file)
@@ -1,4 +1,4 @@
-@tool @tool_uploadcourse @_only_local
+@tool @tool_uploadcourse @_file_upload
 Feature: An admin can update courses using a CSV file
   In order to update courses using a CSV file
   As an admin
index 4ced154..d8ada6b 100644 (file)
@@ -18,7 +18,7 @@
 /**
  * Basic authentication steps definitions.
  *
- * @package    core
+ * @package    core_auth
  * @category   test
  * @copyright  2012 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -34,7 +34,7 @@ use Behat\Behat\Context\Step\When as When;
 /**
  * Log in log out steps definitions.
  *
- * @package    core
+ * @package    core_auth
  * @category   test
  * @copyright  2012 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index fb9b9e1..5b0ae4c 100644 (file)
@@ -862,6 +862,15 @@ class convert_path {
     /**
      * Constructor
      *
+     * The optional recipe array can have three keys, and for each key, the value is another array.
+     * - newfields    => array fieldname => defaultvalue indicates fields that have been added to the table,
+     *                                                   and so should be added to the XML.
+     * - dropfields   => array fieldname                 indicates fieldsthat have been dropped from the table,
+     *                                                   and so can be dropped from the XML.
+     * - renamefields => array oldname => newname        indicates fieldsthat have been renamed in the table,
+     *                                                   and so should be renamed in the XML.
+     * {@line moodle1_course_outline_handler} is a good example that uses all of these.
+     *
      * @param string $name name of the element
      * @param string $path path of the element
      * @param array $recipe basic description of the structure conversion
index e9058b8..d32e1e2 100644 (file)
@@ -124,6 +124,7 @@ class restore_course_task extends restore_task {
         $contents = array();
 
         $contents[] = new restore_decode_content('course', 'summary');
+        $contents[] = new restore_decode_content('event', 'description');
 
         return $contents;
     }
index a4e42ef..cba5771 100644 (file)
@@ -2223,6 +2223,7 @@ class restore_calendarevents_structure_step extends restore_structure_step {
         $result = $DB->record_exists_sql($sql, $arg);
         if (empty($result)) {
             $newitemid = $DB->insert_record('event', $params);
+            $this->set_mapping('event', $oldid, $newitemid);
             $this->set_mapping('event_description', $oldid, $newitemid, $restorefiles);
         }
 
@@ -2832,6 +2833,12 @@ class restore_activity_grades_structure_step extends restore_structure_step {
         }
         // no need to save any grade_letter mapping
     }
+
+    public function after_restore() {
+        // Fix grade item's sortorder after restore, as it might have duplicates.
+        $courseid = $this->get_task()->get_courseid();
+        grade_item::fix_duplicate_sortorder($courseid);
+    }
 }
 
 
index 84fb672..463f278 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Backup and restore actions to help behat feature files writting.
  *
- * @package    core
+ * @package    core_backup
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -35,7 +35,7 @@ use Behat\Gherkin\Node\TableNode as TableNode,
 /**
  * Backup-related steps definitions.
  *
- * @package    core
+ * @package    core_backup
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index ad9d427..1941738 100644 (file)
@@ -45,7 +45,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('html', html_writer::tag('span', '', array('class' => 'notconnected', 'id' => 'connection-error')));
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
         $status = html_writer::tag('span', get_string('notconnected', 'badges'),
             array('class' => 'notconnected', 'id' => 'connection-status'));
         $mform->addElement('static', 'status', get_string('status'), $status);
@@ -67,7 +67,7 @@ class edit_backpack_form extends moodleform {
         $mform->addElement('hidden', 'userid', $USER->id);
         $mform->setType('userid', PARAM_INT);
 
-        $mform->addElement('hidden', 'backpackurl', BADGE_BACKPACKURL);
+        $mform->addElement('hidden', 'backpackurl', 'http://' . BADGE_BACKPACKURL);
         $mform->setType('backpackurl', PARAM_URL);
 
     }
@@ -118,7 +118,7 @@ class edit_collections_form extends moodleform {
 
         $mform->addElement('header', 'backpackheader', get_string('backpackconnection', 'badges'));
         $mform->addHelpButton('backpackheader', 'backpackconnection', 'badges');
-        $mform->addElement('static', 'url', get_string('url'), BADGE_BACKPACKURL);
+        $mform->addElement('static', 'url', get_string('url'), 'http://' . BADGE_BACKPACKURL);
 
         $status = html_writer::tag('span', get_string('connected', 'badges'), array('class' => 'connected'));
         $mform->addElement('static', 'status', get_string('status'), $status);
index bbcc78f..e7616ec 100644 (file)
@@ -29,6 +29,7 @@ define('AJAX_SCRIPT', true);
 require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->dirroot . '/badges/lib/backpacklib.php');
 require_once($CFG->libdir . '/filelib.php');
+require_once($CFG->libdir . '/badgeslib.php');
 
 require_sesskey();
 require_login();
@@ -86,7 +87,7 @@ if (!isset($data->status) || $data->status != 'okay') {
 
 // Make sure email matches a backpack.
 $check = new stdClass();
-$check->backpackurl = BADGE_BACKPACKURL;
+$check->backpackurl = 'http://' . BADGE_BACKPACKURL;
 $check->email = $data->email;
 
 $bp = new OpenBadgesBackpackHandler($check);
@@ -105,7 +106,7 @@ if (isset($request->status) && $request->status == 'missing') {
 $obj = new stdClass();
 $obj->userid = $USER->id;
 $obj->email = $data->email;
-$obj->backpackurl = BADGE_BACKPACKURL;
+$obj->backpackurl = 'http://' . BADGE_BACKPACKURL;
 $obj->backpackuid = $backpackuid;
 $obj->autosync = 0;
 $obj->password = '';
index 6e6b3da..4ecf084 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-/*
- * URL of backpack. Currently only the Open Badges backpack
- * is supported.
- */
-define('BADGE_BACKPACKURL', 'http://backpack.openbadges.org');
-
 global $CFG;
 require_once($CFG->libdir . '/filelib.php');
 
index 4e77e3d..1e31410 100644 (file)
@@ -91,7 +91,7 @@ $namefields = get_all_user_name_fields(true, 'u');
 $sql = "SELECT b.userid, b.dateissued, b.uniquehash, $namefields
     FROM {badge_issued} b INNER JOIN {user} u
         ON b.userid = u.id
-    WHERE b.badgeid = :badgeid
+    WHERE b.badgeid = :badgeid AND u.deleted = 0
     ORDER BY $sortby $sorthow";
 
 $totalcount = $DB->count_records('badge_issued', array('badgeid' => $badge->id));
index 10bfe2a..35ebdd3 100644 (file)
@@ -663,7 +663,9 @@ class core_badges_renderer extends plugin_renderer_base {
         }
 
         if (has_capability('moodle/badges:viewawarded', $context)) {
-            $awarded = $DB->count_records('badge_issued', array('badgeid' => $badgeid));
+            $awarded = $DB->count_records_sql('SELECT COUNT(b.userid)
+                                               FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
+                                               WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $badgeid));
             $row[] = new tabobject('awards',
                         new moodle_url('/badges/recipients.php', array('id' => $badgeid)),
                         get_string('bawards', 'badges', $awarded)
index 64bf704..7371491 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_badges @_only_local
+@core @core_badges @_file_upload
 Feature: Add badges to the system
   In order to give badges to users for their achievements
   As an admin
index 458df34..faf198c 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_badges @_only_local
+@core @core_badges @_file_upload
 Feature: Award badges
   In order to award badges to users for their achievements
   As an admin
index bab46ee..8dd1f87 100644 (file)
@@ -13,3 +13,5 @@ information provided here is intended especially for developers.
   allows to indicate that a badge should be archived instead of fully deleted.
   If this parameter is set to FALSE, a badge will all its information, criteria,
   and awards will be removed from the database.
+* BADGE_BACKPACKURL constant has been moved from badges/lib/backpacklib.php to lib/badgeslib.php, and URI scheme
+  name ('http://') has been removed.
index ba76908..50df25f 100644 (file)
@@ -1,4 +1,4 @@
-@block @block_activity_modules @_only_local
+@block @block_activity_modules
 Feature: Block activity modules
   In order to overview activity modules in a course
   As a manager
@@ -11,13 +11,11 @@ Feature: Block activity modules
     And I expand "Activity modules" node
     And I follow "Manage activities"
     And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Feedback" "table_row"
-    And I click on "//a[@title=\"Show\"]" "xpath_element" in the "Assignment (2.2)" "table_row"
 
   Scenario: Add activities block on the frontpage
-    And the following "activities" exists:
+    Given the following "activities" exists:
       | activity   | name                        | intro                              | course               | idnumber    |
       | assign     | Frontpage assignment name   | Frontpage assignment description   | Acceptance test site | assign0     |
-      | assignment | Frontpage assignment22 name | Frontpage assignment22 description | Acceptance test site | assignment0 |
       | book       | Frontpage book name         | Frontpage book description         | Acceptance test site | book0       |
       | chat       | Frontpage chat name         | Frontpage chat description         | Acceptance test site | chat0       |
       | choice     | Frontpage choice name       | Frontpage choice description       | Acceptance test site | choice0     |
@@ -45,9 +43,6 @@ Feature: Block activity modules
     And I click on "Assignments" "link" in the "Activities" "block"
     Then I should see "Frontpage assignment name"
     And I am on homepage
-    And I click on "Assignments (2.2)" "link" in the "Activities" "block"
-    And I should see "Frontpage assignment22 name"
-    And I am on homepage
     And I click on "Chats" "link" in the "Activities" "block"
     And I should see "Frontpage chat name"
     And I am on homepage
@@ -99,7 +94,6 @@ Feature: Block activity modules
     And the following "activities" exists:
       | activity   | name                   | intro                         | course | idnumber    |
       | assign     | Test assignment name   | Test assignment description   | C1     | assign1     |
-      | assignment | Test assignment22 name | Test assignment22 description | C1     | assignment1 |
       | book       | Test book name         | Test book description         | C1     | book1       |
       | chat       | Test chat name         | Test chat description         | C1     | chat1       |
       | choice     | Test choice name       | Test choice description       | C1     | choice1     |
@@ -128,9 +122,6 @@ Feature: Block activity modules
     And I click on "Assignments" "link" in the "Activities" "block"
     Then I should see "Test assignment name"
     And I follow "Course 1"
-    And I click on "Assignments (2.2)" "link" in the "Activities" "block"
-    And I should see "Test assignment22 name"
-    And I follow "Course 1"
     And I click on "Chats" "link" in the "Activities" "block"
     And I should see "Test chat name"
     And I follow "Course 1"
index 059fe1d..094c668 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Commenting system steps definitions.
  *
- * @package    core_comment
+ * @package    block_comments
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -33,7 +33,7 @@ use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException,
 /**
  * Steps definitions to deal with the commenting system
  *
- * @package    core_comment
+ * @package    block_comments
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
diff --git a/blocks/html/styles.css b/blocks/html/styles.css
deleted file mode 100644 (file)
index de90b14..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-.block.block_html .content {padding:0;}
-.block.block_html .content .no-overflow {padding:4px;}
\ No newline at end of file
index c3fa178..6dc243e 100644 (file)
@@ -16,7 +16,9 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Manage files in folder in private area - to be replaced by something better hopefully....
+ * Manage files in folder in private area.
+ *
+ * This page is not used and now redirects to the page to manage the private files.
  *
  * @package   block_private_files
  * @copyright 2010 Petr Skoda (http://skodak.org)
  */
 
 require('../../config.php');
-require_once("$CFG->dirroot/blocks/private_files/edit_form.php");
-require_once("$CFG->dirroot/repository/lib.php");
-
-require_login();
-if (isguestuser()) {
-    die();
-}
-//TODO: add capability check here!
-
-$context = context_user::instance($USER->id);
-$title = get_string('privatefiles', 'block_private_files');
-$struser = get_string('user');
-
-$PAGE->set_url('/blocks/private_files/edit.php');
-$PAGE->set_context($context);
-$PAGE->set_title($title);
-$PAGE->set_heading($title);
-$PAGE->set_pagelayout('mydashboard');
-$PAGE->set_pagetype('user-private-files');
-
-$data = new stdClass();
-$options = array('subdirs'=>1, 'maxbytes'=>$CFG->userquota, 'maxfiles'=>-1, 'accepted_types'=>'*');
-file_prepare_standard_filemanager($data, 'files', $options, $context, 'user', 'private', 0);
-
-$mform = new block_private_files_form(null, array('data'=>$data, 'options'=>$options));
-
-if ($mform->is_cancelled()) {
-    redirect(new moodle_url('/my/'));
-
-} else if ($formdata = $mform->get_data()) {
-    $formdata = file_postupdate_standard_filemanager($formdata, 'files', $options, $context, 'user', 'private', 0);
-    redirect(new moodle_url('/my/'));
-}
 
-echo $OUTPUT->header();
-echo $OUTPUT->box_start('generalbox');
-$mform->display();
-echo $OUTPUT->box_end();
-echo $OUTPUT->footer();
+redirect(new moodle_url('/user/files.php'));
index 11e1ba8..64321bb 100644 (file)
@@ -23,7 +23,6 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['managemyfiles'] = 'Manage my files';
 $string['pluginname'] = 'My private files';
 $string['privatefiles'] = 'Private files';
 $string['private_files:addinstance'] = 'Add a new private files block';
diff --git a/blocks/private_files/styles.css b/blocks/private_files/styles.css
new file mode 100644 (file)
index 0000000..95fd6bb
--- /dev/null
@@ -0,0 +1,5 @@
+/* Rule so that the table tree view works with word-wrap: break-word. */
+.block_private_files .content table {
+    table-layout: fixed;
+    width: 100%;
+}
\ No newline at end of file
index 3aaa309..e829a25 100644 (file)
@@ -72,6 +72,8 @@ class block_site_main_menu extends block_list {
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
+        } else {
+            $strmove = get_string('move');
         }
         $editbuttons = '';
 
@@ -90,18 +92,12 @@ class block_site_main_menu extends block_list {
                 if (!$ismoving) {
                     $actions = course_get_cm_edit_actions($mod, -1);
 
-                    // Add the action move.
-                    $modcontext = context_module::instance($mod->id);
-                    $hasmanageactivities = has_capability('moodle/course:manageactivities', $modcontext);
-                    if ($hasmanageactivities) {
-                        $baseurl = new moodle_url('/course/mod.php', array('sesskey' => sesskey()));
-                        $actions['move'] = new action_menu_link_primary(
-                            new moodle_url($baseurl, array('copy' => $mod->id)),
-                            new pix_icon('t/move', get_string('move'), 'moodle', array('class' => 'iconsmall', 'title' => '')),
-                            null,
-                            array('title' => get_string('move'))
-                        );
-                    }
+                    // Prepend list of actions with the 'move' action.
+                    $actions = array('move' => new action_menu_link_primary(
+                        new moodle_url('/course/mod.php', array('sesskey' => sesskey(), 'copy' => $mod->id)),
+                        new pix_icon('t/move', $strmove, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                        $strmove
+                    )) + $actions;
 
                     $editbuttons = html_writer::tag('div',
                         $courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
index 271eca5..dd8f92f 100644 (file)
@@ -1,7 +1,7 @@
 .block_site_main_menu li { clear: both; }
 .block_site_main_menu li .column { width: 100%; }
-.block_site_main_menu li .buttons { float: right; margin-top: 3px;}
+.block_site_main_menu li .buttons { float: right; margin: 0; }
 .dir-rtl .block_site_main_menu li .buttons { float: left; }
-.block_site_main_menu li .buttons a img{ vertical-align: text-bottom; margin: 0 3px;}
+.block_site_main_menu li .buttons a img{ vertical-align: text-bottom;}
 .block_site_main_menu .footer { margin-top: 1em; }
 .block_site_main_menu .section_add_menus noscript div { display: inline;}
index 96f7a90..e7fce57 100644 (file)
@@ -74,6 +74,8 @@ class block_social_activities extends block_list {
             $strmovefull = strip_tags(get_string('movefull', '', "'$USER->activitycopyname'"));
             $strcancel= get_string('cancel');
             $stractivityclipboard = $USER->activitycopyname;
+        } else {
+            $strmove = get_string('move');
         }
         $editbuttons = '';
 
@@ -91,8 +93,18 @@ class block_social_activities extends block_list {
                 }
                 if (!$ismoving) {
                     $actions = course_get_cm_edit_actions($mod, -1);
-                    $editbuttons = '<br />'.
-                            $courserenderer->course_section_cm_edit_actions($actions, $mod);
+
+                    // Prepend list of actions with the 'move' action.
+                    $actions = array('move' => new action_menu_link_primary(
+                        new moodle_url('/course/mod.php', array('sesskey' => sesskey(), 'copy' => $mod->id)),
+                        new pix_icon('t/move', $strmove, 'moodle', array('class' => 'iconsmall', 'title' => '')),
+                        $strmove
+                    )) + $actions;
+
+                    $editbuttons = html_writer::tag('div',
+                        $courserenderer->course_section_cm_edit_actions($actions, $mod, array('donotenhance' => true)),
+                        array('class' => 'buttons')
+                    );
                 } else {
                     $editbuttons = '';
                 }
diff --git a/blocks/social_activities/styles.css b/blocks/social_activities/styles.css
new file mode 100644 (file)
index 0000000..003155a
--- /dev/null
@@ -0,0 +1,5 @@
+.block_social_activities li { clear: both; }
+.block_social_activities li .column { width: 100%; }
+.block_social_activities li .buttons { float: right; margin: 0; }
+.dir-rtl .block_social_activities li .buttons { float: left; }
+.block_social_activities li .buttons a img{ vertical-align: text-bottom;}
index 28d59ca..056bfbd 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Steps definitions related with blocks.
  *
- * @package   core
+ * @package   core_block
  * @category  test
  * @copyright 2012 David Monllaó
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -32,7 +32,7 @@ use Behat\Behat\Context\Step\Given as Given;
 /**
  * Blocks management steps definitions.
  *
- * @package    core
+ * @package    core_block
  * @category   test
  * @copyright  2012 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
index be9f906..69de81d 100644 (file)
@@ -575,32 +575,6 @@ function calendar_get_upcoming($courses, $groups, $users, $daysinfuture, $maxeve
                         continue;
                     }
                 }
-                if ($event->modulename == 'assignment'){
-                    // create calendar_event to test edit_event capability
-                    // this new event will also prevent double creation of calendar_event object
-                    $checkevent = new calendar_event($event);
-                    // TODO: rewrite this hack somehow
-                    if (!calendar_edit_event_allowed($checkevent)){ // cannot manage entries, eg. student
-                        if (!$assignment = $DB->get_record('assignment', array('id'=>$event->instance))) {
-                            // print_error("invalidid", 'assignment');
-                            continue;
-                        }
-                        // assign assignment to assignment object to use hidden_is_hidden method
-                        require_once($CFG->dirroot.'/mod/assignment/lib.php');
-
-                        if (!file_exists($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php')) {
-                            continue;
-                        }
-                        require_once ($CFG->dirroot.'/mod/assignment/type/'.$assignment->assignmenttype.'/assignment.class.php');
-
-                        $assignmentclass = 'assignment_'.$assignment->assignmenttype;
-                        $assignmentinstance = new $assignmentclass($cm->id, $assignment, $cm);
-
-                        if ($assignmentinstance->description_is_hidden()){//force not to show description before availability
-                            $event->description = get_string('notavailableyet', 'assignment');
-                        }
-                    }
-                }
             }
 
             if ($processed >= $display->maxevents) {
index 2c6ba7b..2021edc 100644 (file)
@@ -35,8 +35,8 @@ Feature: Add cohorts of users
 
   @javascript
   Scenario: Add users to a cohort selecting them from the system users list
-    When I add "user1" user to "333" cohort
-    And I add "user2" user to "333" cohort
+    When I add "First User (first@user.com)" user to "333" cohort members
+    And I add "Second User (second@user.com)" user to "333" cohort members
     Then I should see "2" in the "#cohorts" "css_element"
     And I follow "Assign"
     And the "Current users" select box should contain "First User (first@user.com)"
index 887533b..fc0c854 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Cohorts steps definitions.
  *
- * @package    core
+ * @package    core_cohort
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -32,7 +32,7 @@ use Behat\Behat\Context\Step\Given as Given;
 /**
  * Steps definitions for cohort actions.
  *
- * @package    core
+ * @package    core_cohort
  * @category   test
  * @copyright  2013 David Monllaó
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -40,39 +40,49 @@ use Behat\Behat\Context\Step\Given as Given;
 class behat_cohort extends behat_base {
 
     /**
-     * Adds the user to the specified cohort.
+     * Adds the user to the specified cohort. The user should be specified like "Firstname Lastname (user@email.com)".
      *
-     * @Given /^I add "(?P<user_username_string>(?:[^"]|\\")*)" user to "(?P<cohort_idnumber_string>(?:[^"]|\\")*)" cohort$/
-     * @param string $username
+     * @Given /^I add "(?P<user_fullname_string>(?:[^"]|\\")*)" user to "(?P<cohort_idnumber_string>(?:[^"]|\\")*)" cohort members$/
+     * @param string $user
      * @param string $cohortidnumber
      */
-    public function i_add_user_to_cohort($username, $cohortidnumber) {
-        global $DB;
-
-        // The user was created by the data generator, executed by the same PHP process that is
-        // running this step, not by any Selenium action.
-        $userid = $DB->get_field('user', 'id', array('username' => $username));
+    public function i_add_user_to_cohort_members($user, $cohortidnumber) {
 
         $steps = array(
             new Given('I click on "' . get_string('assign', 'cohort') . '" "link" in the "' . $this->escape($cohortidnumber) . '" "table_row"'),
-            new Given('I select "' . $userid . '" from "' . get_string('potusers', 'cohort') . '"'),
+            new Given('I select "' . $this->escape($user) . '" from "' . get_string('potusers', 'cohort') . '"'),
             new Given('I press "' . get_string('add') . '"'),
             new Given('I press "' . get_string('backtocohorts', 'cohort') . '"')
         );
 
         // If we are not in the cohorts management we should move there before anything else.
         if (!$this->getSession()->getPage()->find('css', 'input#cohort_search_q')) {
-            $steps = array_merge(
-                array(
-                    new Given('I am on homepage'),
-                    new Given('I collapse "' . get_string('frontpagesettings', 'admin') . '" node'),
-                    new Given('I expand "' . get_string('administrationsite') . '" node'),
-                    new Given('I expand "' . get_string('users', 'admin') . '" node'),
-                    new Given('I expand "' . get_string('accounts', 'admin') . '" node'),
-                    new Given('I follow "' . get_string('cohorts', 'cohort') . '"')
-                ),
-                $steps
-            );
+
+            // With JS enabled we should expand a few tree nodes.
+            if ($this->running_javascript()) {
+                $steps = array_merge(
+                    array(
+                        new Given('I am on homepage'),
+                        new Given('I collapse "' . get_string('frontpagesettings', 'admin') . '" node'),
+                        new Given('I expand "' . get_string('administrationsite') . '" node'),
+                        new Given('I expand "' . get_string('users', 'admin') . '" node'),
+                        new Given('I expand "' . get_string('accounts', 'admin') . '" node'),
+                        new Given('I follow "' . get_string('cohorts', 'cohort') . '"')
+                    ),
+                    $steps
+                );
+
+            } else {
+                // JS disabled.
+                $steps = array_merge(
+                    array(
+                        new Given('I am on homepage'),
+                        new Given('I follow "' . get_string('administrationsite') . '" node'),
+                        new Given('I follow "' . get_string('cohorts', 'cohort') . '"')
+                    ),
+                    $steps
+                );
+            }
         }
 
         return $steps;
index eb0ea17..2213329 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_cohort @_only_local
+@core @core_cohort @_file_upload
 Feature: Upload users to a cohort
   In order to quickly fill site-wide groups with users
   As an admin
index d56d954..606d335 100644 (file)
@@ -61,7 +61,8 @@ class comment_manager {
         }
         $comments = array();
 
-        $sql = "SELECT c.id, c.contextid, c.itemid, c.commentarea, c.userid, c.content, u.firstname, u.lastname, c.timecreated
+        $usernamefields = get_all_user_name_fields(true, 'u');
+        $sql = "SELECT c.id, c.contextid, c.itemid, c.commentarea, c.userid, c.content, $usernamefields, c.timecreated
                   FROM {comments} c
                   JOIN {user} u
                        ON u.id=c.userid
@@ -74,8 +75,9 @@ class comment_manager {
             $item->time = userdate($item->timecreated);
             $item->content = format_text($item->content, FORMAT_MOODLE, $formatoptions);
             // Unset fields not related to the comment
-            unset($item->firstname);
-            unset($item->lastname);
+            foreach (get_all_user_name_fields() as $namefield) {
+                unset($item->$namefield);
+            }
             unset($item->timecreated);
             // Record the comment
             $comments[] = $item;
index 8e700b1..52e7a26 100644 (file)
@@ -37,13 +37,13 @@ Feature: Restrict activity availability through date conditions
       | assignsubmission_file_enabled | 0 |
       | id_availablefrom_day | 31 |
       | id_availablefrom_month | 12 |
-      | id_availablefrom_year | 2050 |
+      | id_availablefrom_year | 2037 |
       | id_showavailability | 1 |
     And I press "Save and return to course"
     And I log out
     When I log in as "student1"
     And I follow "Course 1"
-    Then I should see "Available from 31 December 2050."
+    Then I should see "Available from 31 December 2037."
     And "Test assignment 1" activity should be hidden
     And I log out
 
index 964a646..6733547 100644 (file)
@@ -654,6 +654,10 @@ $CFG->admin = 'admin';
 // Example:
 //   $CFG->behat_additionalfeatures = array('/home/developer/code/wipfeatures');
 //
+// You can make behat save a screenshot when a scenario fails.
+// Example:
+//   $CFG->behat_screenshots_path = '/my/path/to/save/screenshots';
+//
 //=========================================================================
 // 12. DEVELOPER DATA GENERATOR
 //=========================================================================
index 84aea52..62054d9 100644 (file)
@@ -162,22 +162,6 @@ class dndupload_handler {
         }
     }
 
-    /**
-     * No external code should be directly adding new types - they should be added via a 'addtypes' array, returned
-     * by MODNAME_dndupload_register.
-     *
-     * @deprecated deprecated since Moodle 2.5
-     * @param string $identifier
-     * @param array $datatransfertypes
-     * @param string $addmessage
-     * @param string $namemessage
-     * @param int $priority
-     */
-    public function add_type($identifier, $datatransfertypes, $addmessage, $namemessage, $priority=100) {
-        debugging('add_type() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
-        $this->register_type($identifier, $datatransfertypes, $addmessage, $namemessage, '', $priority);
-    }
-
     /**
      * Used to add a new mime type that can be drag and dropped onto a
      * course displayed in a browser window
@@ -210,23 +194,6 @@ class dndupload_handler {
         $this->types[$identifier] = $add;
     }
 
-    /**
-     * No external code should be directly adding new type handlers - they should be added via a 'addtypes' array, returned
-     * by MODNAME_dndupload_register.
-     *
-     * @deprecated deprecated since Moodle 2.5
-     * @param string $type The name of the type (as declared in add_type)
-     * @param string $module The name of the module to handle this type
-     * @param string $message The message to show the user if more than one handler is registered
-     *                        for a type and the user needs to make a choice between them
-     * @param bool $noname If true, the 'name' dialog should be disabled in the pop-up.
-     * @throws coding_exception
-     */
-    public function add_type_handler($type, $module, $message, $noname) {
-        debugging('add_type_handler() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
-        $this->register_type_handler($type, $module, $message, $noname);
-    }
-
     /**
      * Used to declare that a particular module will handle a particular type
      * of dropped data
@@ -252,21 +219,6 @@ class dndupload_handler {
         $this->types[$type]->handlers[] = $add;
     }
 
-    /**
-     * No external code should be directly adding new file handlers - they should be added via a 'files' array, returned
-     * by MODNAME_dndupload_register.
-     *
-     * @deprecated deprecated since Moodle 2.5
-     * @param string $extension The file extension to handle ('*' for all types)
-     * @param string $module The name of the module to handle this type
-     * @param string $message The message to show the user if more than one handler is registered
-     *                        for a type and the user needs to make a choice between them
-     */
-    public function add_file_handler($extension, $module, $message) {
-        debugging('add_file_handler() is deprecated. Plugins should be using the MODNAME_dndupload_register callback.');
-        $this->register_file_handler($extension, $module, $message);
-    }
-
     /**
      * Used to declare that a particular module will handle a particular type
      * of dropped file
index c1e9a14..aade33b 100644 (file)
@@ -1979,7 +1979,7 @@ function course_get_cm_edit_actions(cm_info $mod, $indent = -1, $sr = null) {
     }
 
     // Indent.
-    if ($hasmanageactivities) {
+    if ($hasmanageactivities && $indent >= 0) {
         $indentlimits = new stdClass();
         $indentlimits->min = 0;
         $indentlimits->max = 16;
index 0e58239..56101a1 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_course @_alerts
+@core @core_course @_alert
 Feature: Course activity controls works as expected
   In order to manage my course's activities
   As a teacher
index 994161a..a83ca83 100644 (file)
@@ -900,13 +900,13 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $record = new stdClass();
         $record->course = $course->id;
         $module1 = self::getDataGenerator()->create_module('forum', $record);
-        $module2 = self::getDataGenerator()->create_module('assignment', $record);
+        $module2 = self::getDataGenerator()->create_module('assign', $record);
 
         // Check the forum was correctly created.
         $this->assertEquals(1, $DB->count_records('forum', array('id' => $module1->id)));
 
         // Check the assignment was correctly created.
-        $this->assertEquals(1, $DB->count_records('assignment', array('id' => $module2->id)));
+        $this->assertEquals(1, $DB->count_records('assign', array('id' => $module2->id)));
 
         // Check data exists in the course modules table.
         $this->assertEquals(2, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
@@ -939,7 +939,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         $this->assertEquals(0, $DB->count_records('forum', array('id' => $module1->id)));
 
         // Check the assignment was deleted.
-        $this->assertEquals(0, $DB->count_records('assignment', array('id' => $module2->id)));
+        $this->assertEquals(0, $DB->count_records('assign', array('id' => $module2->id)));
 
         // Check we retrieve no data in the course modules table.
         $this->assertEquals(0, $DB->count_records_select('course_modules', 'id = :module1 OR id = :module2',
@@ -955,7 +955,7 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
 
         // Create two modules.
         $module1 = self::getDataGenerator()->create_module('forum', $record);
-        $module2 = self::getDataGenerator()->create_module('assignment', $record);
+        $module2 = self::getDataGenerator()->create_module('assign', $record);
 
         // Since these modules were recreated the user will not have capabilities
         // to delete them, ensure exception is thrown if they try.
index d3bf0c1..183420c 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-debug.js differ
index f9bddf4..0425628 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser-min.js differ
index d3bf0c1..183420c 100644 (file)
Binary files a/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js and b/course/yui/build/moodle-course-modchooser/moodle-course-modchooser.js differ
index 3268417..7eb72a7 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js differ
index 67d01c9..7a2e8e5 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js differ
index 3268417..7eb72a7 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js differ
index b1033e3..f9627d2 100644 (file)
@@ -5,7 +5,7 @@
  */
 
 var CSS = {
-    PAGECONTENT : 'div#page-content',
+    PAGECONTENT : 'body',
     SECTION : 'li.section',
     SECTIONMODCHOOSER : 'span.section-modchooser-link',
     SITEMENU : 'div.block_site_main_menu',
index 7fdfaac..7fd7b3b 100644 (file)
@@ -47,7 +47,7 @@ var CSS = {
         INSTANCENAME : 'span.instancename',
         MODINDENTDIV : '.mod-indent',
         MODINDENTOUTER : '.mod-indent-outer',
-        PAGECONTENT : 'div#page-content',
+        PAGECONTENT : 'body',
         SECTIONLI : 'li.section',
         SHOW : 'a.'+CSS.SHOW,
         SHOWHIDE : 'a.editing_showhide'
index 314db1e..58e1fcd 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_files @_only_local
+@core @core_files
 Feature: Course files
   In order to add legacy files
   As a user
index 8ece768..d7bfb3d 100644 (file)
@@ -1,6 +1,12 @@
 This file describes API changes in core filter API and plugins,
 information provided here is intended especially for developers.
 
+=== 2.7 ===
+
+* Finally filter may use $PAGE and $OUTPUT, yay!
+* Old global text caching was removed, each filter is now responsible
+  for own caching.
+
 === 2.6 ===
 
 * filtersettings.php is now deprecated, migrate to standard settings.php
index 8ea947c..715d56e 100644 (file)
@@ -11,8 +11,8 @@
 .gradingform_guide .criterion .remark {vertical-align: top;}
 
 .gradingform_guide.editor .criterion .controls,
-.gradingform_guide .criterion .description,
-.gradingform_guide .criterion .remark {padding:3px;}
+.gradingform_guide.editor .criterion .description,
+.gradingform_guide.editor .criterion .remark {padding:3px;}
 
 .gradingform_guide .criteria {height:100%;}
 .gradingform_guide .criterion {border:1px solid #DDD;overflow: hidden;}
 .gradingform_guide.editor .hiddenelement {display:none;}
 .gradingform_guide.editor .pseudotablink {background-color:transparent;border:0 solid;height:1px;width:1px;color:transparent;padding:0;margin:0;position:relative;float:right;}
 
-.gradingform_guide .markingguidecomment {cursor: pointer;}
-.jsenabled .gradingform_guide .markingguidecomment:before {content: url([[pix:t/add]]);padding-right:2px;}
+.jsenabled .gradingform_guide .markingguidecomment {cursor: pointer;}
+.jsenabled .gradingform_guide .markingguidecomment:before {
+    content: url([[pix:t/add]]);
+    padding-right:2px;
+}
+.dir-rtl.jsenabled .gradingform_guide .markingguidecomment:before {
+    padding-right: 0;
+    padding-left: 2px;
+}
 .gradingform_guide .commentheader  {font-weight:bold;font-size:1.1em;padding-bottom:5px;}
 
 .jsenabled .gradingform_guide .criterionnamelabel {display: none;}
 .jsenabled .gradingform_guide .criterionshortname {font-weight:bold;}
 .gradingform_guide table {width: 100%}
+.gradingform_guide .descriptionreadonly {
+    vertical-align: top;
+}
 .gradingform_guide .criteriondescriptionmarkers {width: 300px;}
-.gradingform_guide .markingguideremark {width: 100%;}
-.gradingform_guide .criteriondescriptionscore {display: inline;}
\ No newline at end of file
+.gradingform_guide .markingguideremark {
+    margin: 0;
+    width: 100%;
+    -moz-box-sizing: border-box;
+         box-sizing: border-box;
+}
+.gradingform_guide .criteriondescriptionscore {display: inline;}
+.gradingform_guide .score label {
+    display: block;
+}
+.gradingform_guide .score input {
+    margin: 0;
+    width: auto;
+}
index 59906d7..a73da44 100644 (file)
@@ -179,6 +179,9 @@ table#user-grades .userpic {
 }
 table#user-grades .quickfeedback {
     border: 1px dashed #000;
+    width: auto;
+    margin: 0;
+    padding: 0;
     margin-left: 10px;
 }
 .dir-rtl table#user-grades .quickfeedback {
@@ -240,8 +243,9 @@ table#user-grades th.courseitem,
     border-style: solid;
     border-width: 0 1px 1px;
 }
-.path-grade-report-grader table td.topleft {
-    border-bottom: 0;
+.path-grade-report-grader .left_scroller table td.topleft {
+    background-color: #fff;
+    border-bottom-color: #cecece;
 }
 table#user-grades td.topleft {
     background-color: #fff;
@@ -287,6 +291,9 @@ table#user-grades td.topleft {
 }
 .path-grade-report-grader td input.text {
     border: 1px solid #666;
+    width: auto;
+    margin: 0;
+    padding: 0;
 }
 .path-grade-report-grader td input.submit {
     margin: 10px 10px 0px 10px;
@@ -508,6 +515,9 @@ table#user-grades td.controls,
 .path-grade-report-grader .grade_icons {
     margin-bottom: .3em;
 }
+.path-grade-report-grader tr.controls .grade_icons {
+    margin-bottom: 0;
+}
 .path-grade-report-grader .yui3-overlay {
     background-color: #FFEE69;
     border-color: #D4C237 #A6982B #A6982B;
index adb3bad..c215827 100644 (file)
@@ -415,7 +415,7 @@ class grade_report_user extends grade_report {
                     $data['weight']['headers'] = "$header_cat $header_row weight";
                     // has a weight assigned, might be extra credit
                     if ($grade_object->aggregationcoef > 0 && $type <> 'courseitem') {
-                        $data['weight']['content'] = number_format($grade_object->aggregationcoef,2).'%';
+                        $data['weight']['content'] = number_format($grade_object->aggregationcoef,2);
                     }
                 }
 
diff --git a/grade/tests/behat/behat_grade.php b/grade/tests/behat/behat_grade.php
new file mode 100644 (file)
index 0000000..19b1c3e
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Behat grade related steps definitions.
+ *
+ * @package    core_grades
+ * @category   test
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
+
+require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
+
+use Behat\Behat\Context\Step\Given as Given;
+
+class behat_grade extends behat_base {
+
+    /**
+     * Enters a grade via the gradebook for a specific grade item and user when viewing the 'Grader report' with editing mode turned on.
+     *
+     * @Given /^I give the grade "(?P<grade_number>(?:[^"]|\\")*)" to the user "(?P<username_string>(?:[^"]|\\")*)" for the grade item "(?P<grade_activity_string>(?:[^"]|\\")*)"$/
+     * @param int $grade
+     * @param string $userfullname the user's fullname as returned by fullname()
+     * @param string $itemname
+     * @return Given
+     */
+    public function i_give_the_grade($grade, $userfullname, $itemname) {
+        $gradelabel = $userfullname . ' ' . $itemname;
+        $fieldstr = get_string('useractivitygrade', 'gradereport_grader', $gradelabel);
+
+        return new Given('I fill in "' . $this->escape($fieldstr) . '" with "' . $grade . '"');
+    }
+}
diff --git a/grade/tests/behat/grade_view.feature b/grade/tests/behat/grade_view.feature
new file mode 100644 (file)
index 0000000..24e5458
--- /dev/null
@@ -0,0 +1,76 @@
+@core @core_grades
+Feature: We can enter in grades and view reports from the gradebook
+  In order to check the expected results are displayed
+  As a teacher
+  I need to assign grades and check that they display correctly in the gradebook.
+  I need to enable grade weightings and check that they are displayed correctly.
+
+  Background:
+    Given the following "courses" exists:
+      | fullname | shortname | format |
+      | Course 1 | C1 | topics |
+    And the following "users" exists:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And the following "course enrolments" exists:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Assignment" to section "1" and I fill the form with:
+      | Assignment name | Test assignment name |
+      | Description | Submit your online text |
+      | assignsubmission_onlinetext_enabled | 1 |
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test assignment name"
+    When I press "Add submission"
+    And I fill the moodle form with:
+      | Online text | This is a submission |
+    And I press "Save changes"
+    Then I should see "Submitted for grading"
+    And I log out
+    And I log in as "teacher1"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I turn editing mode on
+    And I give the grade "80.00" to the user "Student 1" for the grade item "Test assignment name"
+    And I press "Update"
+
+  @javascript
+  Scenario: Grade a grade item and ensure the results display correctly in the gradebook
+    When I select "User report" from "Grade report"
+    And the "Grade report" select box should contain "Grader report"
+    And the "Grade report" select box should contain "Outcomes report"
+    And the "Grade report" select box should contain "User report"
+    And the "Select all or one user" select box should contain "All users (1)"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Grades"
+    And I should see "80.00" in the "Test assignment name" "table_row"
+    And I select "Overview report" from "Grade report"
+    And I should see "80.00" in the "overview-grade" "table"
+
+  @javascript
+  Scenario: We can add a weighting to a grade item and it is displayed properly in the user report
+    When I select "Full view" from "Grade report"
+    And I select "Weighted mean of grades" from "Aggregation"
+    And I fill the moodle form with:
+      | Extra credit value for Test assignment name | 0.72 |
+    And I press "Save changes"
+    And I select "User report" from "Grade report"
+    And I follow "Course grade settings"
+    And I fill the moodle form with:
+      | Show weightings | Show |
+    And I press "Save changes"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Grades"
+    Then I should see "0.72" in the "Test assignment name" "table_row"
+    And I should not see "0.72%" in the "Test assignment name" "table_row"
index 64cdfd3..e570ccc 100644 (file)
@@ -50,6 +50,11 @@ $strgroups           = get_string('groups');
 $strparticipants     = get_string('participants');
 $strautocreategroups = get_string('autocreategroups', 'group');
 
+$PAGE->set_title($strgroups);
+$PAGE->set_heading($course->fullname. ': '.$strgroups);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $courseid)));
+
 // Print the page and form
 $preview = '';
 $error = '';
@@ -233,9 +238,6 @@ $PAGE->navbar->add($strparticipants, new moodle_url('/user/index.php', array('id
 $PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$courseid)));
 $PAGE->navbar->add($strautocreategroups);
 
-/// Print header
-$PAGE->set_title($strgroups);
-$PAGE->set_heading($course->fullname. ': '.$strgroups);
 echo $OUTPUT->header();
 echo $OUTPUT->heading($strautocreategroups);
 
index b58bb0c..88430a5 100644 (file)
@@ -32,6 +32,7 @@ $groupids = required_param('groups', PARAM_SEQUENCE);
 $confirm = optional_param('confirm', 0, PARAM_BOOL);
 
 $PAGE->set_url('/group/delete.php', array('courseid'=>$courseid,'groups'=>$groupids));
+$PAGE->set_pagelayout('standard');
 
 // Make sure course is OK and user has access to manage groups
 if (!$course = $DB->get_record('course', array('id' => $courseid))) {
index b6aa753..3a61893 100644 (file)
@@ -74,6 +74,12 @@ require_login($course);
 $context = context_course::instance($course->id);
 require_capability('moodle/course:managegroups', $context);
 
+$strgroups = get_string('groups');
+$PAGE->set_title($strgroups);
+$PAGE->set_heading($course->fullname . ': '.$strgroups);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
+
 $returnurl = $CFG->wwwroot.'/group/index.php?id='.$course->id.'&group='.$id;
 
 // Prepare the description editor: We do support files for group descriptions
@@ -123,8 +129,6 @@ $PAGE->navbar->add($strgroups, new moodle_url('/group/index.php', array('id'=>$c
 $PAGE->navbar->add($strheading);
 
 /// Print header
-$PAGE->set_title($strgroups);
-$PAGE->set_heading($course->fullname . ': '.$strgroups);
 echo $OUTPUT->header();
 echo '<div id="grouppicture">';
 if ($id) {
index bdfe58a..aa20ac2 100644 (file)
@@ -66,8 +66,13 @@ require_login($course);
 $context = context_course::instance($course->id);
 require_capability('moodle/course:managegroups', $context);
 
-$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$course->id;
+$strgroupings = get_string('groupings', 'group');
+$PAGE->set_title($strgroupings);
+$PAGE->set_heading($course->fullname. ': '.$strgroupings);
+$PAGE->set_pagelayout('standard');
+navigation_node::override_active_url(new moodle_url('/group/index.php', array('id' => $course->id)));
 
+$returnurl = $CFG->wwwroot.'/group/groupings.php?id='.$course->id;
 
 if ($id and $delete) {
     if (!empty($grouping->idnumber) && !has_capability('moodle/course:changeidnumber', $context)) {
@@ -126,9 +131,7 @@ if ($editform->is_cancelled()) {
 
 }
 
-$strgroupings    = get_string('groupings', 'group');
 $strparticipants = get_string('participants');
-
 if ($id) {
     $strheading = get_string('editgroupingsettings', 'group');
 } else {
@@ -140,8 +143,6 @@ $PAGE->navbar->add($strgroupings, new moodle_url('/group/groupings.php', array('
 $PAGE->navbar->add($strheading);
 
 /// Print header
-$PAGE->set_title($strgroupings);
-$PAGE->set_heading($course->fullname. ': '.$strgroupings);
 echo $OUTPUT->header();
 echo $OUTPUT->heading($strheading);
 $editform->display();
index 1511619..5e83918 100644 (file)
@@ -144,7 +144,7 @@ $strparticipants = get_string('participants');
 /// Print header
 $PAGE->set_title($strgroups);
 $PAGE->set_heading($course->fullname);
-$PAGE->set_pagelayout('admin');
+$PAGE->set_pagelayout('standard');
 echo $OUTPUT->header();
 
 // Add tabs
index f32ce79..625a9a1 100644 (file)
@@ -40,18 +40,16 @@ use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 class behat_groups extends behat_base {
 
     /**
-     * Add the specified user to the group. You should be in the groups page when running this step.
+     * Add the specified user to the group. You should be in the groups page when running this step. The user should be specified like "Firstname Lastname (user@email.com)".
      *
-     * @Given /^I add "(?P<username_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group$/
+     * @Given /^I add "(?P<user_fullname_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group members$/
      * @throws ElementNotFoundException Thrown by behat_base::find
      * @param string $username
      * @param string $groupname
      */
-    public function i_add_user_to_group($username, $groupname) {
-        global $DB;
+    public function i_add_user_to_group_members($userfullname, $groupname) {
 
-        $user = $DB->get_record('user', array('username' => $username));
-        $userfullname = $this->getSession()->getSelectorsHandler()->xpathLiteral(fullname($user));
+        $userfullname = $this->getSession()->getSelectorsHandler()->xpathLiteral($userfullname);
 
         // Using a xpath liternal to avoid problems with quotes and double quotes.
         $groupname = $this->getSession()->getSelectorsHandler()->xpathLiteral($groupname);
index 7ec7622..e2ce592 100644 (file)
@@ -35,10 +35,10 @@ Feature: Organize students into groups
     And I fill the moodle form with:
       | Group name | Group 2 |
     And I press "Save changes"
-    When I add "student0" user to "Group 1" group
-    And I add "student1" user to "Group 1" group
-    And I add "student2" user to "Group 2" group
-    And I add "student3" user to "Group 2" group
+    When I add "Student 0 (student0@asd.com)" user to "Group 1" group members
+    And I add "Student 1 (student1@asd.com)" user to "Group 1" group members
+    And I add "Student 2 (student2@asd.com)" user to "Group 2" group members
+    And I add "Student 3 (student3@asd.com)" user to "Group 2" group members
     Then I select "Group 1 (2)" from "groups"
     And the "members" select box should contain "Student 0"
     And the "members" select box should contain "Student 1"
index ed021a6..fef297a 100644 (file)
@@ -1,4 +1,4 @@
-@core @core_group @_only_local
+@core @core_group @_file_upload
 Feature: Importing of groups and groupings
   In order to import groups and grouping
   As a teacher
index 94f8a26..f94de6f 100644 (file)
@@ -40,7 +40,7 @@ $string['cannotsavemd5file'] = 'No se puede guardar el archivo md5';
 $string['cannotsavezipfile'] = 'No se puede guardar el archivo ZIP';
 $string['cannotunzipfile'] = 'No se puede descomprimir el archivo';
 $string['componentisuptodate'] = 'El componente está actualizado';
-$string['dmlexceptiononinstall'] = '<p>Ha ocurrido un error de base de datos [{$s->errorcode}].<br/>{$a->debuginfo}</p>';
+$string['dmlexceptiononinstall'] = '<p>Se ha producido un error de base de datos [{$a->errorcode}].<br />{$a->debuginfo}</p>';
 $string['downloadedfilecheckfailed'] = 'Ha fallado la comprobación del archivo descargado';
 $string['invalidmd5'] = 'La variable de verificación MD5 es incorrecta no es valida - trate nuevamente';
 $string['missingrequiredfield'] = 'Falta algún campo necesario';
index b93cf32..0388de7 100644 (file)
@@ -31,8 +31,8 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['cannotcreatedboninstall'] = '<p>Impossible de créer la base de données.</p>
-<p>La base de données indiquée n\'existe pas et le nom d\'utilisateur n\'a pas l\'autorisation de créer la base de données.</p>
-<p>L\'administrateur du site doit vérifier la configuration de la base de données.</p>';
+<p>La base de données indiquées n\'existe pas et l\'utilisateur spécifié n\'a pas les autorisations permettant de créer une base de données.</p>.
+<p>L\'administrateur du site doit revoir la configuration de la base de données.</p>';
 $string['cannotcreatelangdir'] = 'Création du dossier lang impossible';
 $string['cannotcreatetempdir'] = 'Création du dossier temp impossible';
 $string['cannotdownloadcomponents'] = 'Téléchargement des composants impossible';
index d3671be..2ef3ae1 100644 (file)
@@ -92,7 +92,6 @@ $string['bookmarkdeleted'] = 'Bookmark deleted.';
 $string['bookmarkthispage'] = 'Bookmark this page';
 $string['cachejs'] = 'Cache Javascript';
 $string['cachejs_help'] = 'Javascript caching and compression greatly improves page loading performance. it is strongly recommended for production sites. Developers will probably want to disable this feature.';
-$string['cachetext'] = 'Text cache lifetime';
 $string['calendarexportsalt'] = 'Calendar export salt';
 $string['calendarsettings'] = 'Calendar';
 $string['calendartype'] = 'Calendar type';
@@ -147,7 +146,6 @@ $string['configauthenticationplugins'] = 'Please choose the authentication plugi
 $string['configautolang'] = 'Detect default language from browser setting, if disabled site default is used.';
 $string['configautologinguests'] = 'Should visitors be logged in as guests automatically when entering courses with guest access?';
 $string['configbloglevel'] = 'This setting allows you to restrict the level to which user blogs can be viewed on this site.  Note that they specify the maximum context of the VIEWER not the poster or the types of blog posts.  Blogs can also be disabled completely if you don\'t want them at all.';
-$string['configcachetext'] = 'For larger sites or sites that use text filters, this setting can really speed things up.  Copies of texts will be retained in their processed form for the time specified here.  Setting this too small may actually slow things down slightly,  but setting it too large may mean texts take too long to refresh (with new links, for example).';
 $string['configcalendarcustomexport'] = 'Enable custom date range export of calendar';
 $string['configcalendarexportsalt'] = 'This random text is used for improving of security of authentication tokens used for exporting of calendars. Please note that all current tokens are invalidated if you change this hash salt.';
 $string['configclamactlikevirus'] = 'Treat files like viruses';
@@ -382,10 +380,8 @@ $string['cronerrorclionly'] = 'Sorry, internet access to this page has been disa
 $string['cronerrorpassword'] = 'Sorry, you have not provided a valid password to access this page';
 $string['cronremotepassword'] = 'Cron password for remote access';
 $string['cronwarning'] = 'The <a href="cron.php">cron.php maintenance script</a> has not been run for at least 24 hours.';
-$string['ctyperecommended'] = 'Installing the optional ctype PHP extension is highly recommended in order to improve site performance, particularly if your site is supporting non-latin languages.';
 $string['ctyperequired'] = 'The ctype PHP extension is now required by Moodle, in order to improve site performance and to offer multilingual compatibility.';
 $string['curlcache'] = 'cURL cache TTL';
-$string['curlrecommended'] = 'Installing the optional cURL library is highly recommended in order to enable Moodle Networking functionality.';
 $string['curlrequired'] = 'The cURL PHP extension is now required by Moodle, in order to communicate with Moodle repositories.';
 $string['curltimeoutkbitrate'] = 'Bitrate to use when calculating cURL timeouts (Kbps)';
 $string['curltimeoutkbitrate_help'] = 'This setting is used to calculate an appropriate timeout during large cURL requests. As part of this calculation an HTTP HEAD request is made to determine the size of the content. Setting this to 0 disables this request from being made.';
@@ -396,14 +392,6 @@ $string['custommenuitems'] = 'Custom menu items';
 $string['datarootsecurityerror'] = '<p><strong>SECURITY WARNING!</strong></p><p>Your dataroot directory is in the wrong location and is exposed to the web. This means that all your private files are available to anyone in the world, and some of them could be used by a cracker to obtain unauthorised administrative access to your site!</p>
 <p>You <em>must</em> move dataroot directory ({$a}) to a new location that is not within your public web directory, and update the <code>$CFG->dataroot</code> setting in your config.php accordingly.</p>';
 $string['datarootsecuritywarning'] = 'Your site configuration might not be secure. Please make sure that your dataroot directory ({$a}) is not directly accessible via web.';
-$string['dbmigrate'] = 'Moodle database migration';
-$string['dbmigrateconnecerror'] = 'Could not connect to the database specified.';
-$string['dbmigrateencodingerror'] = 'The database specified has encoding {$a} rather than required UNICODE/UTF8.<br />Please specify another.';
-$string['dbmigratepostgres'] = 'It seems that you are using PostgreSQL as the database server. To continue the migration process you need to manually create a new database with encoding "UNICODE"(PostgreSQL 7) or "UTF8" (PostgreSQL 8) to store the migrated data. Please enter your new database connection settings below to continue:';
-$string['dbmigratewarning'] = 'Please make sure that you have backed up your Moodle database before commencing this procedure. If you are unsure of how to do that, please contact your system admin. Your Moodle site will be put under maintenance mode after you start the migration process';
-$string['dbmigratewarning2'] = '<b>Warning: You are about to start the database migration process. Please be very sure that your entire Moodle database is backed up.</b>';
-$string['dbmigrationdeprecateddb'] = '<font color="#ff0000">This database is migrated to a new UTF8 database and deprecated. Please edit your config.php and use the new database for this moodle.</font>';
-$string['dbmigrationdupfailed'] = 'Database duplication failed with possible error:<font color="#ff0000"><pre>{$a}</pre></font>';
 $string['dbsessions'] = 'Use database for session information';
 $string['debug'] = 'Debug messages';
 $string['debugall'] = 'ALL: Show all reasonable PHP debug messages';
@@ -549,12 +537,10 @@ $string['frontpageroles'] = 'Front page roles';
 $string['frontpagesettings'] = 'Front page settings';
 $string['fullnamedisplay'] = 'Full name format';
 $string['fullnamedisplayprivate'] = 'Full name format - private';
-$string['gdrecommended'] = 'GD extension is used for conversion of images, some features such as user profile images will not be available if missing.';
 $string['gdrequired'] = 'The GD extension is now required by Moodle for image conversion.';
 $string['generalsettings'] = 'General settings';
 $string['geoipfile'] = 'GeoIP city data file';
 $string['getremoteaddrconf'] = 'Logged IP address source';
-$string['globalswarning'] = '<p><strong>SECURITY WARNING!</strong></p><p> To operate properly, Moodle requires <br />that you make certain changes to your current PHP settings.</p><p>You <em>must</em> set <code>register_globals=off</code>.</p><p>This setting is controlled by editing your <code>php.ini</code>, Apache/IIS <br />configuration or <code>.htaccess</code> file.</p>';
 $string['groupenrolmentkeypolicy'] = 'Group enrolment key policy';
 $string['groupenrolmentkeypolicy_desc'] = 'Turning this on will make Moodle check group enrolment keys against a valid password policy.';
 $string['googlemapkey3'] = 'Google Maps API V3 key';
@@ -592,7 +578,6 @@ $string['change'] = 'change';
 $string['checkboxno'] = 'No';
 $string['checkboxyes'] = 'Yes';
 $string['choosefiletoedit'] = 'Choose file to edit';
-$string['iconvrecommended'] = 'Installing the optional ICONV library is highly recommended in order to improve site performance, particularly if your site is supporting non-Latin languages.';
 $string['iconvrequired'] = 'Installing ICONV extension is required.';
 $string['ignore'] = 'Ignore';
 $string['includemoduleuserdata'] = 'Include module user data';
@@ -740,8 +725,6 @@ $string['mymoodleredirect'] = 'Force users to use My Moodle';
 $string['mypage'] = 'Default My home page';
 $string['myprofile'] = 'Default profile page';
 $string['mypagelocked'] = 'Lock default page';
-$string['mysql416bypassed'] = 'However, if your site is using iso-8859-1 (latin) languages ONLY, you may continue using your currently installed MySQL 4.1.12 (or higher).';
-$string['mysql416required'] = 'MySQL 4.1.16 is the minimum version required for Moodle 1.6 in order to guarantee that all data can be converted to UTF-8 in the future.';
 $string['navadduserpostslinks'] = 'Add links to view user posts';
 $string['navadduserpostslinks_help'] = 'If enabled two links will be added to each user in the navigation to view discussions the user has started and posts the user has made in forums throughout the site or in specific courses.';
 $string['navigationupgrade'] = 'This upgrade introduces two new navigation blocks that will replace these blocks: Administration, Courses, Activities and Participants.  If you had set any special permissions on those blocks you should check to make sure everything is behaving as you want it.';
@@ -798,9 +781,7 @@ $string['perfdebug'] = 'Performance info';
 $string['performance'] = 'Performance';
 $string['pgcluster'] = 'PostgreSQL Cluster';
 $string['pgclusterdescription'] = 'PostgreSQL version/cluster parameter for command line operations. If you only have one postgresql on your system or you are not sure what this is, leave this blank.';
-$string['php533warning'] = 'PHP 5.3.3 and upwards is recommended';
 $string['phpfloatproblem'] = 'Detected unexpected problem in handling of PHP float numbers - {$a}';
-$string['php50restricted'] = 'PHP 5.0.x has a number of known problems, please upgrade to 5.1.x or downgrade to 4.3.x or 4.4.x';
 $string['pleaserefreshregistration'] = 'Your site has been registered with moodle.org, please consider updating the registration if significant changes happened since your last update, on {$a}';
 $string['pleaseregister'] = 'Please register your site to remove this button';
 $string['plugin'] = 'Plugin';
@@ -887,8 +868,6 @@ $string['proxypassword'] = 'Proxy password';
 $string['proxyport'] = 'Proxy port';
 $string['proxytype'] = 'Proxy type';
 $string['proxyuser'] = 'Proxy username';
-$string['qtyperqpwillberemoved'] = 'During the upgrade, the RQP question type will be removed. You were not using this question type, so you should not experience any problems.';
-$string['qtyperqpwillberemovedanyway'] = 'During the upgrade, the RQP question type will be removed. You have some RQP questions in your database, and these will stop working unless you reinstall the code from http://moodle.org/mod/data/view.php?d=13&amp;rid=797 before continuing with the upgrade.';
 $string['quarantinedir'] = 'Quarantine directory';
 $string['question'] = 'Question';
 $string['questionbehaviours'] = 'Question behaviours';
@@ -1034,7 +1013,6 @@ $string['tools'] = 'Admin tools';
 $string['toolsmanage'] = 'Manage admin tools';
 $string['unattendedoperation'] = 'Unattended operation';
 $string['unbookmarkthispage'] = 'Unbookmark this page';
-$string['unicoderecommended'] = 'Storing all your data in Unicode (UTF-8) is recommended. New installations should be performed into databases that have their default character set as Unicode.  If you are upgrading, you should perform the UTF-8 migration process (see the Admin page).';
 $string['unicoderequired'] = 'It is required that you store all your data in Unicode format (UTF-8). New installations must be performed into databases that have their default character set as Unicode.  If you are upgrading, you should perform the UTF-8 migration process (see the Admin page).';
 $string['uninstallplugin'] = 'Uninstall';
 $string['unlockaccount'] = 'Unlock account';
@@ -1097,9 +1075,6 @@ $string['upgradesure'] = '<p>Your Moodle files have been changed, and you are ab
 <p>Once you do this you can not go back again. Please note that this process can take a long time.</p>
 <p>Are you sure you want to upgrade this server to this version?</p>';
 $string['upgradetimedout'] = 'Upgrade timed out, please restart the upgrade.';
-$string['upgrade197notice'] = '<p>Moodle 1.9.7 contains a number of security fixes to user passwords and backups to protect the user data on your site. As a result some of your settings and permissions relating to backups may have changed.<br />
-See the <a href="http://docs.moodle.org/dev/Moodle_1.9.7_release_notes" target="_blank">Moodle 1.9.7 release notes</a> for full details.</p>';
-$string['upgrade197noticesubject'] = 'Moodle 1.9.7 upgrade security notices';
 $string['upgradingdata'] = 'Upgrading data';
 $string['upgradinglogs'] = 'Upgrading logs';
 $string['upgradingversion'] = 'Upgrading to new version';
index 00527cd..e38b5a4 100644 (file)
@@ -50,6 +50,7 @@ $string['hidepanel'] = 'Hide panel';
 $string['moveblock'] = 'Move {$a} block';
 $string['moveblockafter'] = 'Move block to after {$a} block';
 $string['moveblockbefore'] = 'Move block to before {$a} block';
+$string['moveblockinregion'] = 'Move block to {$a} region';
 $string['movingthisblockcancel'] = 'Moving this block ({$a})';
 $string['onthispage'] = 'On this page';
 $string['pagetypes'] = 'Page types';
index 26966ec..e1d4908 100644 (file)
@@ -65,6 +65,8 @@ $string['error7023'] = 'The remote site has tried to decrypt your message with a
 $string['error7024'] = 'You send an unencrypted message to the remote site, but the remote site doesn\'t accept unencrypted communication from your site. This is very unexpected; you should probably file a bug if this occurs (giving as much information as possible about the application versions in question, etc.';
 $string['error7026'] = 'The key that your message was signed with differs from the key that the remote host has on file for your server. Further, the remote host attempted to fetch your current key and failed to do so. Please manually re-key with the remote host and try again.';
 $string['error709'] = 'The remote site failed to obtain a SSL key from you.';
+$string['eventaccesscontrolcreated'] = 'Access control created';
+$string['eventaccesscontrolupdated'] = 'Access control updated';
 $string['expired'] = 'This key expired on';
 $string['expires'] = 'Valid until';
 $string['expireyourkey'] = 'Delete this key';
index da2502e..1a9e518 100644 (file)
@@ -715,7 +715,9 @@ $string['errorwhenconfirming'] = 'You are not confirmed yet because an error occ
 $string['eventcommentcreated'] = 'Comment created';
 $string['eventcommentdeleted'] = 'Comment deleted';
 $string['eventcommentsviewed'] = 'Comments viewed';
+$string['eventcoursecategorycreated'] = 'Category created';
 $string['eventcoursecategorydeleted'] = 'Category deleted';
+$string['eventcoursecategoryupdated'] = 'Category updated';
 $string['eventcoursecontentdeleted'] = 'Course content deleted';
 $string['eventcoursecreated'] = 'Course created';
 $string['eventcoursedeleted'] = 'Course deleted';
@@ -729,6 +731,7 @@ $string['eventcourserestored'] = 'Course restored';
 $string['eventcourseupdated'] = 'Course updated';
 $string['eventcoursesectionupdated'] = ' Course section updated';
 $string['eventcoursemoduleinstancelistviewed'] = 'Course module instance list viewed';
+$string['eventemailfailed'] = 'Email failed to send';
 $string['eventusercreated'] = 'User created';
 $string['eventuserdeleted'] = 'User deleted';
 $string['eventuserlistviewed'] = 'User list viewed';
@@ -1121,8 +1124,6 @@ $string['minute'] = 'minute';
 $string['minutes'] = 'minutes';
 $string['miscellaneous'] = 'Miscellaneous';
 $string['missingcategory'] = 'You need to choose a category';
-$string['missingcity'] = 'Missing city/town';
-$string['missingcountry'] = 'Missing country';
 $string['missingdescription'] = 'Missing description';
 $string['missingemail'] = 'Missing email address';
 $string['missingfirstname'] = 'Missing given name';
index 092e50f..15d32ad 100644 (file)
@@ -25,6 +25,7 @@
 $string['addmorechoiceblanks'] = 'Blanks for {no} more choices';
 $string['addcategory'] = 'Add category';
 $string['adminreport'] = 'Report on possible problems in your question database.';
+$string['advancedsearchoptions'] = 'Search options';
 $string['answers'] = 'Answers';
 $string['availableq'] = 'Available?';
 $string['badbase'] = 'Bad base before **: {$a}**';
@@ -379,6 +380,7 @@ $string['questionbehavioursorder'] = 'Question behaviours order';
 $string['questionbehavioursorderexplained'] = 'Enter a comma separated list of behaviours in the order you want them to appear in dropdown menu';
 $string['questionidmismatch'] = 'Question ids mismatch';
 $string['questionname'] = 'Question name';
+$string['questionnamecopy'] = '{$a} (copy)';
 $string['questionpreviewdefaults'] = 'Question preview defaults';
 $string['questionpreviewdefaults_desc'] = 'These defaults are used when a user first previews a question in the question bank. Once a user has previewed a question, their personal preferences are stored as user preferences.';
 $string['questions'] = 'Questions';
index 8e92faf..6680b41 100644 (file)
@@ -2615,8 +2615,11 @@ function get_default_role_archetype_allows($type, $archetype) {
  * Reset role capabilities to default according to selected role archetype.
  * If no archetype selected, removes all capabilities.
  *
- * @param int $roleid
- * @return void
+ * This applies to capabilities that are assigned to the role (that you could
+ * edit in the 'define roles' interface), and not to any capability overrides
+ * in different locations.
+ *
+ * @param int $roleid ID of role to reset capabilities for
  */
 function reset_role_capabilities($roleid) {
     global $DB;
@@ -2626,11 +2629,15 @@ function reset_role_capabilities($roleid) {
 
     $systemcontext = context_system::instance();
 
-    $DB->delete_records('role_capabilities', array('roleid'=>$roleid));
+    $DB->delete_records('role_capabilities',
+            array('roleid' => $roleid, 'contextid' => $systemcontext->id));
 
     foreach($defaultcaps as $cap=>$permission) {
         assign_capability($cap, $permission, $roleid, $systemcontext->id);
     }
+
+    // Mark the system context dirty.
+    context_system::instance()->mark_dirty();
 }
 
 /**
index 41c86ef..043e0af 100644 (file)
@@ -750,8 +750,8 @@ interface parentable_part_of_admin_tree extends part_of_admin_tree {
  */
 class admin_category implements parentable_part_of_admin_tree {
 
-    /** @var mixed An array of part_of_admin_tree objects that are this object's children */
-    public $children;
+    /** @var part_of_admin_tree[] An array of part_of_admin_tree objects that are this object's children */
+    protected $children;
     /** @var string An internal name for this category. Must be unique amongst ALL part_of_admin_tree objects */
     public $name;
     /** @var string The displayed name for this category. Usually obtained through get_string() */
@@ -766,6 +766,15 @@ class admin_category implements parentable_part_of_admin_tree {
     /** @var array fast lookup category cache, all categories of one tree point to one cache */
     protected $category_cache;
 
+    /** @var bool If set to true children will be sorted when calling {@link admin_category::get_children()} */
+    protected $sort = false;
+    /** @var bool If set to true children will be sorted in ascending order. */
+    protected $sortasc = true;
+    /** @var bool If set to true sub categories and pages will be split and then sorted.. */
+    protected $sortsplit = true;
+    /** @var bool $sorted True if the children have been sorted and don't need resorting */
+    protected $sorted = false;
+
     /**
      * Constructor for an empty admin category
      *
@@ -830,7 +839,7 @@ class admin_category implements parentable_part_of_admin_tree {
      */
     public function search($query) {
         $result = array();
-        foreach ($this->children as $child) {
+        foreach ($this->get_children() as $child) {
             $subsearch = $child->search($query);
             if (!is_array($subsearch)) {
                 debugging('Incorrect search result from '.$child->name);
@@ -991,6 +1000,104 @@ class admin_category implements parentable_part_of_admin_tree {
         }
         return false;
     }
+
+    /**
+     * Sets sorting on this category.
+     *
+     * Please note this function doesn't actually do the sorting.
+     * It can be called anytime.
+     * Sorting occurs when the user calls get_children.
+     * Code using the children array directly won't see the sorted results.
+     *
+     * @param bool $sort If set to true children will be sorted, if false they won't be.
+     * @param bool $asc If true sorting will be ascending, otherwise descending.
+     * @param bool $split If true we sort pages and sub categories separately.
+     */
+    public function set_sorting($sort, $asc = true, $split = true) {
+        $this->sort = (bool)$sort;
+        $this->sortasc = (bool)$asc;
+        $this->sortsplit = (bool)$split;
+    }
+
+    /**
+     * Returns the children associated with this category.
+     *
+     * @return part_of_admin_tree[]
+     */
+    public function get_children() {
+        // If we should sort and it hasn't already been sorted.
+        if ($this->sort && !$this->sorted) {
+            if ($this->sortsplit) {
+                $categories = array();
+                $pages = array();
+                foreach ($this->children as $child) {
+                    if ($child instanceof admin_category) {
+                        $categories[] = $child;
+                    } else {
+                        $pages[] = $child;
+                    }
+                }
+                core_collator::asort_objects_by_property($categories, 'visiblename');
+                core_collator::asort_objects_by_property($pages, 'visiblename');
+                if (!$this->sortasc) {
+                    $categories = array_reverse($categories);
+                    $pages = array_reverse($pages);
+                }
+                $this->children = array_merge($pages, $categories);
+            } else {
+                core_collator::asort_objects_by_property($this->children, 'visiblename');
+                if (!$this->sortasc) {
+                    $this->children = array_reverse($this->children);
+                }
+            }
+            $this->sorted = true;
+        }
+        return $this->children;
+    }
+
+    /**
+     * Magically gets a property from this object.
+     *
+     * @param $property
+     * @return part_of_admin_tree[]
+     * @throws coding_exception
+     */
+    public function __get($property) {
+        if ($property === 'children') {
+            return $this->get_children();
+        }
+        throw new coding_exception('Invalid property requested.');
+    }
+
+    /**
+     * Magically sets a property against this object.
+     *
+     * @param string $property
+     * @param mixed $value
+     * @throws coding_exception
+     */
+    public function __set($property, $value) {
+        if ($property === 'children') {
+            $this->sorted = false;
+            $this->children = $value;
+        } else {
+            throw new coding_exception('Invalid property requested.');
+        }
+    }
+
+    /**
+     * Checks if an inaccessible property is set.
+     *
+     * @param string $property
+     * @return bool
+     * @throws coding_exception
+     */
+    public function __isset($property) {
+        if ($property === 'children') {
+            return isset($this->children);
+        }
+        throw new coding_exception('Invalid property requested.');
+    }
 }
 
 
index ab24525..7b2c820 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
@@ -34,16 +33,16 @@ $contextid = required_param('contextid', PARAM_INT);
 $subpage = optional_param('subpage', '', PARAM_ALPHANUMEXT);
 $cmid = optional_param('cmid', null, PARAM_INT);
 $action = optional_param('action', '', PARAM_ALPHA);
-// Params for blocks-move actions
-$bui_moveid = optional_param('bui_moveid', 0, PARAM_INT);
-$bui_newregion = optional_param('bui_newregion', '', PARAM_ALPHAEXT);
-$bui_beforeid = optional_param('bui_beforeid', 0, PARAM_INT);
+// Params for blocks-move actions.
+$buimoveid = optional_param('bui_moveid', 0, PARAM_INT);
+$buinewregion = optional_param('bui_newregion', '', PARAM_ALPHAEXT);
+$buibeforeid = optional_param('bui_beforeid', 0, PARAM_INT);
 
-// Setting pagetype and URL
+// Setting pagetype and URL.
 $PAGE->set_pagetype($pagetype);
 $PAGE->set_url('/lib/ajax/blocks.php', array('courseid' => $courseid, 'pagelayout' => $pagelayout, 'pagetype' => $pagetype));
 
-// Verifying login and session
+// Verifying login and session.
 $cm = null;
 if (!is_null($cmid)) {
     $cm = get_coursemodule_from_id(null, $cmid, $courseid, false, MUST_EXIST);
@@ -54,7 +53,7 @@ require_sesskey();
 // Set context from ID, so we don't have to guess it from other info.
 $PAGE->set_context(context::instance_by_id($contextid));
 
-// Setting layout to replicate blocks configuration for the page we edit
+// Setting layout to replicate blocks configuration for the page we edit.
 $PAGE->set_pagelayout($pagelayout);
 $PAGE->set_subpage($subpage);
 $pagetype = explode('-', $pagetype);
@@ -78,21 +77,27 @@ switch ($pagetype[0]) {
         break;
 }
 
-echo $OUTPUT->header(); // send headers
+// Send headers.
+echo $OUTPUT->header();
 
 switch ($action) {
     case 'move':
-        // Loading blocks and instances for the region
+        // Loading blocks and instances for the region.
         $PAGE->blocks->load_blocks();
-        $instances = $PAGE->blocks->get_blocks_for_region($bui_newregion);
+        $instances = $PAGE->blocks->get_blocks_for_region($buinewregion);
 
-        $bui_newweight = null;
-        if ($bui_beforeid == 0) {
-            // Moving to very bottom
-            $last = end($instances);
-            $bui_newweight = $last->instance->weight + 1;
+        $buinewweight = null;
+        if ($buibeforeid == 0) {
+            if (count($instances) === 0) {
+                // Moving the block into an empty region. Give it the default weight.
+                $buinewweight = 0;
+            } else {
+                // Moving to very bottom.
+                $last = end($instances);
+                $buinewweight = $last->instance->weight + 1;
+            }
         } else {
-            // Moving somewhere
+            // Moving somewhere.
             $lastweight = 0;
             $lastblock = 0;
             $first = reset($instances);
@@ -101,14 +106,13 @@ switch ($action) {
             }
 
             foreach ($instances as $instance) {
-                if ($instance->instance->id == $bui_beforeid) {
-                    // Location found, just calculate weight like in
-                    // block_manager->create_block_contents() and quit the loop.
-                    if ($lastblock == $bui_moveid) {
-                        // same block, same place - nothing to move
+                if ($instance->instance->id == $buibeforeid) {
+                    // Location found, just calculate weight like in block_manager->create_block_contents() and quit the loop.
+                    if ($lastblock == $buimoveid) {
+                        // Same block, same place - nothing to move.
                         break;
                     }
-                    $bui_newweight = ($lastweight + $instance->instance->weight) / 2;
+                    $buinewweight = ($lastweight + $instance->instance->weight) / 2;
                     break;
                 }
                 $lastweight = $instance->instance->weight;
@@ -116,10 +120,10 @@ switch ($action) {
             }
         }
 
-        // Move block if we need
-        if (isset($bui_newweight)) {
-            // Nasty hack
-            $_POST['bui_newweight'] = $bui_newweight;
+        // Move block if we need.
+        if (isset($buinewweight)) {
+            // Nasty hack.
+            $_POST['bui_newweight'] = $buinewweight;
             $PAGE->blocks->process_url_move();
         }
         break;
index 957d0a9..457f8d9 100644 (file)
@@ -112,16 +112,16 @@ try {
     $html = ob_get_contents();
     ob_end_clean();
 } catch (Exception $e) {
-    die('Error: '.$e->getMessage());
+    throw new coding_exception('Error: '.$e->getMessage());
 }
 
 // Check if the buffer contianed anything if it did ERROR!
 if (trim($html) !== '') {
-    die('Errors were encountered while producing the navigation branch'."\n\n\n".$html);
+    throw new coding_exception('Errors were encountered while producing the navigation branch'."\n\n\n".$html);
 }
 // Check that branch isn't empty... if it is ERROR!
-if (empty($branch) || $branch->nodetype !== navigation_node::NODETYPE_BRANCH) {
-    die('No further information available for this branch');
+if (empty($branch) || ($branch->nodetype !== navigation_node::NODETYPE_BRANCH && !$branch->isexpandable)) {
+    throw new coding_exception('No further information available for this branch');
 }
 
 // Prepare an XML converter for the branch
index f233078..c24400e 100644 (file)
@@ -93,6 +93,11 @@ define('BADGE_MESSAGE_DAILY', 2);
 define('BADGE_MESSAGE_WEEKLY', 3);
 define('BADGE_MESSAGE_MONTHLY', 4);
 
+/*
+ * URL of backpack. Currently only the Open Badges backpack is supported.
+ */
+define('BADGE_BACKPACKURL', 'backpack.openbadges.org');
+
 /**
  * Class that represents badge.
  *
@@ -331,10 +336,11 @@ class badge {
      */
     public function has_awards() {
         global $DB;
-        if ($DB->record_exists('badge_issued', array('badgeid' => $this->id))) {
-            return true;
-        }
-        return false;
+        $awarded = $DB->record_exists_sql('SELECT b.uniquehash
+                    FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
+                    WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
+
+        return $awarded;
     }
 
     /**
@@ -349,7 +355,7 @@ class badge {
                 'SELECT b.userid, b.dateissued, b.uniquehash, u.firstname, u.lastname
                     FROM {badge_issued} b INNER JOIN {user} u
                         ON b.userid = u.id
-                    WHERE b.badgeid = :badgeid', array('badgeid' => $this->id));
+                    WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $this->id));
 
         return $awards;
     }
@@ -815,7 +821,9 @@ function badges_get_badges($type, $courseid = 0, $sort = '', $dir = '', $page =
             $badges[$r->id]->dateissued = $r->dateissued;
             $badges[$r->id]->uniquehash = $r->uniquehash;
         } else {
-            $badges[$r->id]->awards = $DB->count_records('badge_issued', array('badgeid' => $badge->id));
+            $badges[$r->id]->awards = $DB->count_records_sql('SELECT COUNT(b.userid)
+                                        FROM {badge_issued} b INNER JOIN {user} u ON b.userid = u.id
+                                        WHERE b.badgeid = :badgeid AND u.deleted = 0', array('badgeid' => $badge->id));
             $badges[$r->id]->statstring = $badge->get_status_name();
         }
     }
@@ -1174,7 +1182,7 @@ function badges_check_backpack_accessibility() {
     // Using fake assertion url to check whether backpack can access the web site.
     $fakeassertion = new moodle_url('/badges/assertion.php', array('b' => 'abcd1234567890'));
 
-    // Curl request to http://backpack.openbadges.org/baker.
+    // Curl request to backpack baker.
     $curl = new curl();
     $options = array(
         'FRESH_CONNECT' => true,
@@ -1182,7 +1190,7 @@ function badges_check_backpack_accessibility() {
         'HEADER' => 0,
         'CONNECTTIMEOUT' => 2,
     );
-    $location = 'http://backpack.openbadges.org/baker';
+    $location = 'http://' . BADGE_BACKPACKURL . '/baker';
     $out = $curl->get($location, array('assertion' => $fakeassertion->out(false)), $options);
 
     $data = json_decode($out);
@@ -1251,7 +1259,7 @@ function badges_setup_backpack_js() {
     if (!empty($CFG->badges_allowexternalbackpack)) {
         $PAGE->requires->string_for_js('error:backpackproblem', 'badges');
         $protocol = (strpos($CFG->wwwroot, 'https://') === 0) ? 'https://' : 'http://';
-        $PAGE->requires->js(new moodle_url($protocol . 'backpack.openbadges.org/issuer.js'), true);
+        $PAGE->requires->js(new moodle_url($protocol . BADGE_BACKPACKURL . '/issuer.js'), true);
         $PAGE->requires->js('/badges/backpack.js', true);
     }
 }
index 619dcd6..76a4d25 100644 (file)
@@ -66,10 +66,30 @@ class behat_command {
 
     /**
      * Returns the executable path
+     *
+     * Allows returning a customized command for cygwin when the
+     * command is just displayed, when using exec(), system() and
+     * friends we stay with DIRECTORY_SEPARATOR as they use the
+     * normal cmd.exe (in Windows).
+     *
+     * @param  bool $custombyterm  If the provided command should depend on the terminal where it runs
      * @return string
      */
-    public final static function get_behat_command() {
-        return 'vendor' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'behat';
+    public final static function get_behat_command($custombyterm = false) {
+
+        $separator = DIRECTORY_SEPARATOR;
+        $exec = 'behat';
+
+        // Cygwin uses linux-style directory separators.
+        if ($custombyterm && testing_is_cygwin()) {
+            $separator = '/';
+
+            // MinGW can not execute .bat scripts.
+            if (!testing_is_mingw()) {
+                $exec = 'behat.bat';
+            }
+        }
+        return 'vendor' . $separator . 'bin' . $separator . $exec;
     }
 
     /**
index a967cad..7da36b9 100644 (file)
@@ -154,21 +154,36 @@ class behat_config_manager {
     /**
      * Returns the behat config file path used by the steps definition list
      *
-     * Note this can only be called from web-based scripts so it will return the
-     * production dataroot not behat_dataroot. With this the steps definitions
-     * list is accessible without having to install the behat test site.
-     *
      * @return string
      */
     public static function get_steps_list_config_filepath() {
         global $USER;
 
+        // We don't cygwin-it as it is called using exec() which uses cmd.exe.
         $userdir = behat_command::get_behat_dir() . '/users/' . $USER->id;
         make_writable_directory($userdir);
 
         return $userdir . '/behat.yml';
     }
 
+    /**
+     * Returns the behat config file path used by the behat cli command.
+     *
+     * @return string
+     */
+    public static function get_behat_cli_config_filepath() {
+        global $CFG;
+
+        $command = $CFG->behat_dataroot . DIRECTORY_SEPARATOR . 'behat' . DIRECTORY_SEPARATOR . 'behat.yml';
+
+        // Cygwin uses linux-style directory separators.
+        if (testing_is_cygwin()) {
+            $command = str_replace('\\', '/', $command);
+        }
+
+        return $command;
+    }
+
     /**
      * Behat config file specifing the main context class,
      * the required Behat extensions and Moodle test wwwroot.
diff --git a/lib/classes/event/course_category_created.php b/lib/classes/event/course_category_created.php
new file mode 100644 (file)
index 0000000..33a4ea1
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Course category created event.
+ *
+ * @package    core
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_category_created extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'course_categories';
+        $this->data['crud'] = 'c';
+        $this->data['level'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcoursecategorycreated');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The course category with the id ' . $this->objectid . ' was created by the user with the id ' .
+            $this->userid;
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array(SITEID, 'category', 'add', 'editcategory.php?id=' . $this->objectid, $this->objectid);
+    }
+}
diff --git a/lib/classes/event/course_category_updated.php b/lib/classes/event/course_category_updated.php
new file mode 100644 (file)
index 0000000..978c9e4
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Course category updated event.
+ *
+ * @package    core
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class course_category_updated extends base {
+
+    /** @var array The legacy log data. */
+    private $legacylogdata;
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'course_categories';
+        $this->data['crud'] = 'u';
+        $this->data['level'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventcoursecategoryupdated');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'The course category with the id ' . $this->objectid . ' was updated by the user with the id ' .
+            $this->userid;
+    }
+
+    /**
+     * Set the legacy data used for add_to_log().
+     *
+     * @param array $logdata
+     */
+    public function set_legacy_logdata($logdata) {
+        $this->legacylogdata = $logdata;
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        if (!empty($this->legacylogdata)) {
+            return $this->legacylogdata;
+        }
+
+        return array(SITEID, 'category', 'update', 'editcategory.php?id=' . $this->objectid, $this->objectid);
+    }
+}
diff --git a/lib/classes/event/email_failed.php b/lib/classes/event/email_failed.php
new file mode 100644 (file)
index 0000000..1f561e9
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Email failed event.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class email_failed extends base {
+
+    /**
+     * Initialise the event data.
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['level'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventemailfailed');
+    }
+
+    /**
+     * Returns non-localised description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'Failed to send an email from the user with the id ' . $this->userid . ' to the user with the id ' .
+            $this->relateduserid . ' due to the following error: \'' . $this->other['errorinfo'] . '\'';
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: ' . $this->other['errorinfo']);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     */
+    protected function validate_data() {
+        if (!isset($this->other['subject'])) {
+            throw new \coding_exception('The subject needs to be set in $other');
+        }
+        if (!isset($this->other['message'])) {
+            throw new \coding_exception('The message needs to be set in $other');
+        }
+        if (!isset($this->other['errorinfo'])) {
+            throw new \coding_exception('The error info needs to be set in $other');
+        }
+    }
+}
diff --git a/lib/classes/event/mnet_access_control_created.php b/lib/classes/event/mnet_access_control_created.php
new file mode 100644 (file)
index 0000000..ce7ce3c
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Mnet access control created event class.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class mnet_access_control_created extends base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'mnet_sso_access_control';
+        $this->data['crud'] = 'c';
+        $this->data['level'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventaccesscontrolcreated', 'mnet');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('admin/mnet/access_control.php');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
+        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+
+        return 'Access control created for the user with the username \'' . $mnetaccesscontrol->username . '\' belonging
+            to the mnet host \'' . $mnethost->name . '\'';
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
+        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+
+        return array($this->courseid, 'admin/mnet', 'add', 'admin/mnet/access_control.php', 'SSO ACL: ' .
+            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
+            $mnethost->name);
+    }
+}
diff --git a/lib/classes/event/mnet_access_control_updated.php b/lib/classes/event/mnet_access_control_updated.php
new file mode 100644 (file)
index 0000000..0ca9af6
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Mnet access control updated event class.
+ *
+ * @package    core
+ * @copyright  2013 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+class mnet_access_control_updated extends base {
+
+    /**
+     * Init method.
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'mnet_sso_access_control';
+        $this->data['crud'] = 'u';
+        $this->data['level'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Returns localised general event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventaccesscontrolupdated', 'mnet');
+    }
+
+    /**
+     * Returns relevant URL.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('admin/mnet/access_control.php');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
+        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+
+        return 'Access control created for the user with the username \'' . $mnetaccesscontrol->username . '\' belonging
+            to the mnet host \'' . $mnethost->name . '\'';
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        $mnetaccesscontrol = $this->get_record_snapshot('mnet_sso_access_control', $this->objectid);
+        $mnethost = $this->get_record_snapshot('mnet_host', $mnetaccesscontrol->mnet_host_id);
+
+        return array($this->courseid, 'admin/mnet', 'update', 'admin/mnet/access_control.php', 'SSO ACL: ' .
+            $mnetaccesscontrol->accessctrl . ' user \'' . $mnetaccesscontrol->username . '\' from ' .
+            $mnethost->name);
+    }
+}
index 078659a..d4e30dc 100644 (file)
@@ -427,7 +427,12 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
             $DB->update_record('course_categories', $updatedata);
         }
 
-        add_to_log(SITEID, "category", 'add', "editcategory.php?id=$newcategory->id", $newcategory->id);
+        $event = \core\event\course_category_created::create(array(
+            'objectid' => $newcategory->id,
+            'context' => $categorycontext
+        ));
+        $event->trigger();
+
         cache_helper::purge_by_event('changesincoursecat');
 
         return self::get($newcategory->id, MUST_EXIST, true);
@@ -516,13 +521,19 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
 
         $newcategory->timemodified = time();
 
+        $categorycontext = $this->get_context();
         if ($editoroptions) {
-            $categorycontext = $this->get_context();
             $newcategory = file_postupdate_standard_editor($newcategory, 'description', $editoroptions, $categorycontext,
                                                            'coursecat', 'description', 0);
         }
         $DB->update_record('course_categories', $newcategory);
-        add_to_log(SITEID, "category", 'update', "editcategory.php?id=$this->id", $this->id);
+
+        $event = \core\event\course_category_updated::create(array(
+            'objectid' => $newcategory->id,
+            'context' => $categorycontext
+        ));
+        $event->trigger();
+
         fix_course_sortorder();
         // Purge cache even if fix_course_sortorder() did not do it.
         cache_helper::purge_by_event('changesincoursecat');
@@ -1741,7 +1752,13 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
             foreach ($children as $childcat) {
                 $childcat->change_parent_raw($newparentcat);
                 // Log action.
-                add_to_log(SITEID, "category", "move", "editcategory.php?id=$childcat->id", $childcat->id);
+                $event = \core\event\course_category_updated::create(array(
+                    'objectid' => $childcat->id,
+                    'context' => $childcat->get_context()
+                ));
+                $event->set_legacy_logdata(array(SITEID, 'category', 'move', 'editcategory.php?id=' . $childcat->id,
+                    $childcat->id));
+                $event->trigger();
             }
             fix_course_sortorder();
         }
@@ -1909,7 +1926,13 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
             fix_course_sortorder();
             cache_helper::purge_by_event('changesincoursecat');
             $this->restore();
-            add_to_log(SITEID, "category", "move", "editcategory.php?id=$this->id", $this->id);
+
+            $event = \core\event\course_category_updated::create(array(
+                'objectid' => $this->id,
+                'context' => $this->get_context()
+            ));
+            $event->set_legacy_logdata(array(SITEID, 'category', 'move', 'editcategory.php?id=' . $this->id, $this->id));
+            $event->trigger();
         }
     }
 
@@ -1975,7 +1998,13 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
     public function hide() {
         if ($this->hide_raw(0)) {
             cache_helper::purge_by_event('changesincoursecat');
-            add_to_log(SITEID, "category", "hide", "editcategory.php?id=$this->id", $this->id);
+
+            $event = \core\event\course_category_updated::create(array(
+                'objectid' => $this->id,
+                'context' => $this->get_context()
+            ));
+            $event->set_legacy_logdata(array(SITEID, 'category', 'hide', 'editcategory.php?id=' . $this->id, $this->id));
+            $event->trigger();
         }
     }
 
@@ -2028,7 +2057,13 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
     public function show() {
         if ($this->show_raw()) {
             cache_helper::purge_by_event('changesincoursecat');
-            add_to_log(SITEID, "category", "show", "editcategory.php?id=$this->id", $this->id);
+
+            $event = \core\event\course_category_updated::create(array(
+                'objectid' => $this->id,
+                'context' => $this->get_context()
+            ));
+            $event->set_legacy_logdata(array(SITEID, 'category', 'show', 'editcategory.php?id=' . $this->id, $this->id));
+            $event->trigger();
         }
     }
 
@@ -2530,7 +2565,15 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
             $DB->set_field('course_categories', 'sortorder', $swapcategory->sortorder, array('id' => $this->id));
             $DB->set_field('course_categories', 'sortorder', $this->sortorder, array('id' => $swapcategory->id));
             $this->sortorder = $swapcategory->sortorder;
-            add_to_log(SITEID, "category", "move", "management.php?categoryid={$this->id}", $this->id);
+
+            $event = \core\event\course_category_updated::create(array(
+                'objectid' => $this->id,
+                'context' => $this->get_context()
+            ));
+            $event->set_legacy_logdata(array(SITEID, 'category', 'move', 'management.php?categoryid=' . $this->id,
+                $this->id));
+            $event->trigger();
+
             // Finally reorder courses.
             fix_course_sortorder();
             cache_helper::purge_by_event('changesincoursecat');
index 37e09ba..703c1fb 100644 (file)
@@ -133,14 +133,6 @@ function cron_run() {
         }
 
 
-        // Delete old cached texts
-        if (!empty($CFG->cachetext)) {   // Defined in config.php
-            $cachelifetime = time() - $CFG->cachetext - 60;  // Add an extra minute to allow for really heavy sites
-            $DB->delete_records_select('cache_text', "timemodified < ?", array($cachelifetime));
-            mtrace(" Deleted old cache_text records");
-        }
-
-
         if (!empty($CFG->usetags)) {
             require_once($CFG->dirroot.'/tag/lib.php');
             tag_cron();
index 7c63c50..b14ac79 100644 (file)
  */
 
 defined('MOODLE_INTERNAL') || die();
-
-/**
- * This function will look for the risky PHP setting register_globals
- * in order to inform about. MDL-12914
- *
- * @param object $result the environment_results object to be modified
- * @return mixed null if the test is irrelevant or environment_results object with
- *               status set to true (test passed) or false (test failed)
- */
-function php_check_register_globals($result) {
-
-/// Check for register_globals. If enabled, security warning
-    if (ini_get_bool('register_globals')) {
-        $result->status = false;
-    } else {
-        $result = null;
-    }
-
-    return $result;
-}
-
-function php_check_php533($result) {
-    if (version_compare(phpversion(), '5.3.3') < 0) {
-        $result->status = false;
-    } else {
-        $result = null;
-    }
-    return $result;
-}
index ec46f1d..99a8360 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20131009" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20140112" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <INDEX NAME="filter_md5key" UNIQUE="false" FIELDS="filter, md5key"/>
       </INDEXES>
     </TABLE>
-    <TABLE NAME="cache_text" COMMENT="For storing temporary copies of processed texts">
-      <FIELDS>
-        <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
-        <FIELD NAME="md5key" TYPE="char" LENGTH="32" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="formattedtext" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
-        <FIELD NAME="timemodified" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
-      </FIELDS>
-      <KEYS>
-        <KEY NAME="primary" TYPE="primary" FIELDS="id"/>
-      </KEYS>
-      <INDEXES>
-        <INDEX NAME="md5key" UNIQUE="false" FIELDS="md5key"/>
-        <INDEX NAME="timemodified" UNIQUE="false" FIELDS="timemodified" COMMENT="Mainly to help deletion of expired records from cron"/>
-      </INDEXES>
-    </TABLE>
     <TABLE NAME="log" COMMENT="Every action is logged as far as possible">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/>
index 94a6b04..4937628 100644 (file)
@@ -2903,5 +2903,29 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2013122400.01);
     }
 
+    if ($oldversion < 2014011000.01) {
+
+        // Define table cache_text to be dropped.
+        $table = new xmldb_table('cache_text');
+
+        // Conditionally launch drop table for cache_text.
+        if ($dbman->table_exists($table)) {
+            $dbman->drop_table($table);
+        }
+
+        unset_config('cachetext');
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014011000.01);
+    }
+
+    if ($oldversion < 2014011701.00) {
+        // Fix gradebook sortorder duplicates.
+        upgrade_grade_item_fix_sortorder();
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014011701.00);
+    }
+
     return true;
 }
index 4828dcf..070f0cb 100644 (file)
@@ -924,6 +924,54 @@ abstract class moodle_database {
         }
     }
 
+    /**
+     * Ensures that limit params are numeric and positive integers, to be passed to the database.
+     * We explicitly treat null, '' and -1 as 0 in order to provide compatibility with how limit
+     * values have been passed historically.
+     *
+     * @param int $limitfrom Where to start results from
+     * @param int $limitnum How many results to return
+     * @return array Normalised limit params in array($limitfrom, $limitnum)
+     */
+    protected function normalise_limit_from_num($limitfrom, $limitnum) {
+        global $CFG;
+
+        // We explicilty treat these cases as 0.
+        if ($limitfrom === null || $limitfrom === '' || $limitfrom === -1) {
+            $limitfrom = 0;
+        }
+        if ($limitnum === null || $limitnum === '' || $limitnum === -1) {
+            $limitnum = 0;
+        }
+
+        if ($CFG->debugdeveloper) {
+            if (!is_numeric($limitfrom)) {
+                $strvalue = var_export($limitfrom, true);
+                debugging("Non-numeric limitfrom parameter detected: $strvalue, did you pass the correct arguments?",
+                    DEBUG_DEVELOPER);
+            } else if ($limitfrom < 0) {
+                debugging("Negative limitfrom parameter detected: $limitfrom, did you pass the correct arguments?",
+                    DEBUG_DEVELOPER);
+            }
+
+            if (!is_numeric($limitnum)) {
+                $strvalue = var_export($limitnum, true);
+                debugging("Non-numeric limitnum parameter detected: $strvalue, did you pass the correct arguments?",
+                    DEBUG_DEVELOPER);
+            } else if ($limitnum < 0) {
+                debugging("Negative limitnum parameter detected: $limitnum, did you pass the correct arguments?",
+                    DEBUG_DEVELOPER);
+            }
+        }
+
+        $limitfrom = (int)$limitfrom;
+        $limitnum  = (int)$limitnum;
+        $limitfrom = max(0, $limitfrom);
+        $limitnum  = max(0, $limitnum);
+
+        return array($limitfrom, $limitnum);
+    }
+
     /**
      * Return tables in database WITHOUT current prefix.
      * @param bool $usecache if true, returns list of cached tables.
index 70bd6f3..0a324b1 100644 (file)
@@ -693,10 +693,9 @@ class mssql_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
+
         if ($limitfrom or $limitnum) {
             if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
                 $fetch = $limitfrom + $limitnum;
index 34320d9..3cb62a3 100644 (file)
@@ -912,10 +912,8 @@ class mysqli_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
 
         if ($limitfrom or $limitnum) {
             if ($limitnum < 1) {
@@ -976,10 +974,8 @@ class mysqli_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
 
         if ($limitfrom or $limitnum) {
             if ($limitnum < 1) {
index 0c6f700..00f79d6 100644 (file)
@@ -712,11 +712,7 @@ class oci_native_moodle_database extends moodle_database {
      */
     private function get_limit_sql($sql, array $params = null, $limitfrom=0, $limitnum=0) {
 
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
-
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
         // TODO: Add the /*+ FIRST_ROWS */ hint if there isn't another hint
 
         if ($limitfrom and $limitnum) {
index 5c369a7..2ee1635 100644 (file)
@@ -681,10 +681,9 @@ class pgsql_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_recordset_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
+
         if ($limitfrom or $limitnum) {
             if ($limitnum < 1) {
                 $limitnum = "ALL";
@@ -724,10 +723,9 @@ class pgsql_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_records_sql($sql, array $params=null, $limitfrom=0, $limitnum=0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum  = (int)$limitnum;
-        $limitfrom = ($limitfrom < 0) ? 0 : $limitfrom;
-        $limitnum  = ($limitnum < 0)  ? 0 : $limitnum;
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
+
         if ($limitfrom or $limitnum) {
             if ($limitnum < 1) {
                 $limitnum = "ALL";
index 6f12524..f6cab36 100644 (file)
@@ -762,10 +762,8 @@ class sqlsrv_native_moodle_database extends moodle_database {
      * @throws dml_exception A DML specific exception is thrown for any errors.
      */
     public function get_recordset_sql($sql, array $params = null, $limitfrom = 0, $limitnum = 0) {
-        $limitfrom = (int)$limitfrom;
-        $limitnum = (int)$limitnum;
-        $limitfrom = max(0, $limitfrom);
-        $limitnum = max(0, $limitnum);
+
+        list($limitfrom, $limitnum) = $this->normalise_limit_from_num($limitfrom, $limitnum);
 
         if ($limitfrom or $limitnum) {
             if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
index ceed3db..869d6ba 100644 (file)
@@ -4997,6 +4997,74 @@ class core_dml_testcase extends database_driver_testcase {
         $this->assertEquals(2, reset($records)->count);
         $this->assertEquals(2, end($records)->count);
     }
+
+    /**
+     * Test debugging messages about invalid limit number values.
+     */
+    public function test_invalid_limits_debugging() {
+        $DB = $this->tdb;
+        $dbman = $DB->get_manager();
+
+        // Setup test data.
+        $table = $this->get_test_table();
+        $tablename = $table->getName();
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0');
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_table($table);
+        $DB->insert_record($tablename, array('course' => '1'));
+
+        // Verify that get_records_sql throws debug notices with invalid limit params.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 'invalid');
+        $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?");
+
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid');
+        $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?");
+
+        // Verify that get_recordset_sql throws debug notices with invalid limit params.
+        $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 'invalid');
+        $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: 'invalid', did you pass the correct arguments?");
+        $rs->close();
+
+        $rs = $DB->get_recordset_sql("SELECT * FROM {{$tablename}}", null, 1, 'invalid');
+        $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: 'invalid', did you pass the correct arguments?");
+        $rs->close();
+
+        // Verify that some edge cases do no create debugging messages.
+        // String form of integer values.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, '1');
+        $this->assertDebuggingNotCalled();
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, '2');
+        $this->assertDebuggingNotCalled();
+        // Empty strings.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, '');
+        $this->assertDebuggingNotCalled();
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, '');
+        $this->assertDebuggingNotCalled();
+        // Null values.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, null);
+        $this->assertDebuggingNotCalled();
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, null);
+        $this->assertDebuggingNotCalled();
+
+        // Verify that empty arrays DO create debugging mesages.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, array());
+        $this->assertDebuggingCalled("Non-numeric limitfrom parameter detected: array (\n), did you pass the correct arguments?");
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, array());
+        $this->assertDebuggingCalled("Non-numeric limitnum parameter detected: array (\n), did you pass the correct arguments?");
+
+        // Verify Negative number handling:
+        // -1 is explicitly treated as 0 for historical reasons.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -1);
+        $this->assertDebuggingNotCalled();
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -1);
+        $this->assertDebuggingNotCalled();
+        // Any other negative values should throw debugging messages.
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, -2);
+        $this->assertDebuggingCalled("Negative limitfrom parameter detected: -2, did you pass the correct arguments?");
+        $DB->get_records_sql("SELECT * FROM {{$tablename}}", null, 1, -2);
+        $this->assertDebuggingCalled("Negative limitnum parameter detected: -2, did you pass the correct arguments?");
+    }
 }
 
 /**
index 48b3292..30fa353 100644 (file)
@@ -1387,45 +1387,10 @@ class environment_results {
     }
 }
 
-/// Here all the bypass functions are coded to be used by the environment
-/// checker. All those functions will receive the result object and will
-/// return it modified as needed (status and bypass string)
-
-/**
- * This function will bypass MySQL 4.1.16 reqs if:
- *   - We are using MySQL > 4.1.12, informing about problems with non latin chars in the future
- *
- * @param object result object to handle
- * @return boolean true/false to determinate if the bypass has to be performed (true) or no (false)
- */
-function bypass_mysql416_reqs ($result) {
-/// See if we are running MySQL >= 4.1.12
-    if (version_compare($result->getCurrentVersion(), '4.1.12', '>=')) {
-        return true;
-    }
-
-    return false;
-}
-
 /// Here all the restrict functions are coded to be used by the environment
 /// checker. All those functions will receive the result object and will
 /// return it modified as needed (status and bypass string)
 
-/**
- * This function will restrict PHP reqs if:
- *   - We are using PHP 5.0.x, informing about the buggy version
- *
- * @param object $result object to handle
- * @return boolean true/false to determinate if the restrict has to be performed (true) or no (false)
- */
-function restrict_php50_version($result) {
-    if (version_compare($result->getCurrentVersion(), '5.0.0', '>=')
-      and version_compare($result->getCurrentVersion(), '5.0.99', '<')) {
-        return true;
-    }
-    return false;
-}
-
 /**
  * @param array $element the element from the environment.xml file that should have
  *      either a level="required" or level="optional" attribute.
diff --git a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js
new file mode 100644 (file)
index 0000000..8ec50a7
Binary files /dev/null and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-debug.js differ
diff --git a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js
new file mode 100644 (file)
index 0000000..6c426ff
Binary files /dev/null and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask-min.js differ
diff --git a/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js
new file mode 100644 (file)
index 0000000..8ec50a7
Binary files /dev/null and b/lib/form/yui/build/moodle-form-passwordunmask/moodle-form-passwordunmask.js differ
diff --git a/lib/form/yui/passwordunmask/passwordunmask.js b/lib/form/yui/passwordunmask/passwordunmask.js
deleted file mode 100644 (file)
index 1bbefcc..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-YUI.add('moodle-form-passwordunmask', function(Y) {
-    var PASSWORDUNMASK = function() {
-        PASSWORDUNMASK.superclass.constructor.apply(this, arguments);
-    }
-
-    Y.extend(PASSWORDUNMASK, Y.Base, {
-        //Initialize checkbox if id is passed
-        initializer : function(params) {
-            if (params && params.formid) {
-               this.add_checkbox(params.formid, params.checkboxlabel, params.checkboxname);
-            }
-        },
-        //Create checkbox for unmasking password
-        add_checkbox : function(elementid, checkboxlabel, checkboxname) {
-            var node = Y.one('#'+elementid);
-
-            //retaining unmask div from previous implementation.
-            var unmaskdiv = Y.Node.create('<div id="'+elementid+'unmaskdiv" class="unmask"></div>');
-
-            //Add checkbox for unmasking to unmaskdiv
-            var unmaskchb = Y.Node.create('<input id="'+elementid+'unmask" type="checkbox" name="'+
-                checkboxname+'unmask">');
-            unmaskdiv.appendChild(unmaskchb);
-            //Attach event using static javascript function for unmasking password.
-            unmaskchb.on('click', function() {unmaskPassword(elementid);});
-
-            //Add label for checkbox to unmaskdiv
-            var unmasklabel = Y.Node.create('<label for="'+elementid+'unmask">'+checkboxlabel+'</label>');
-            unmaskdiv.appendChild(unmasklabel);
-
-            //Insert unmask div in the same div as password input.
-            node.get('parentNode').insert(unmaskdiv, node.get('lastNode'));
-        }
-    });
-
-    M.form = M.form || {};
-    M.form.passwordunmask = function(params) {
-        return new PASSWORDUNMASK(params);
-    }
-}, '@VERSION@', {requires:['base', 'node']});
diff --git a/lib/form/yui/src/passwordunmask/build.json b/lib/form/yui/src/passwordunmask/build.json
new file mode 100644 (file)
index 0000000..9ad9dac
--- /dev/null
@@ -0,0 +1,10 @@
+{
+    "name": "moodle-form-passwordunmask",
+    "builds": {
+        "moodle-form-passwordunmask": {
+            "jsfiles": [
+                "passwordunmask.js"
+            ]
+        }
+    }
+}
diff --git a/lib/form/yui/src/passwordunmask/js/passwordunmask.js b/lib/form/yui/src/passwordunmask/js/passwordunmask.js
new file mode 100644 (file)
index 0000000..63b4748
--- /dev/null
@@ -0,0 +1,39 @@
+var PASSWORDUNMASK = function() {
+    PASSWORDUNMASK.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(PASSWORDUNMASK, Y.Base, {
+    // Initialize checkbox if id is passed.
+    initializer : function(params) {
+        if (params && params.formid) {
+            this.add_checkbox(params.formid, params.checkboxlabel, params.checkboxname);
+        }
+    },
+
+    // Create checkbox for unmasking password.
+    add_checkbox : function(elementid, checkboxlabel, checkboxname) {
+        var node = Y.one('#'+elementid);
+
+        // Retaining unmask div from previous implementation.
+        var unmaskdiv = Y.Node.create('<div id="'+elementid+'unmaskdiv" class="unmask"></div>');
+
+        // Add checkbox for unmasking to unmaskdiv.
+        var unmaskchb = Y.Node.create('<input id="'+elementid+'unmask" type="checkbox" name="'+
+            checkboxname+'unmask">');
+        unmaskdiv.appendChild(unmaskchb);
+        // Attach event using static javascript function for unmasking password.
+        unmaskchb.on('click', function() {unmaskPassword(elementid);});
+
+        // Add label for checkbox to unmaskdiv.
+        var unmasklabel = Y.Node.create('<label for="'+elementid+'unmask">'+checkboxlabel+'</label>');
+        unmaskdiv.appendChild(unmasklabel);
+
+        // Insert unmask div in the same div as password input.
+        node.get('parentNode').insert(unmaskdiv, node.get('lastNode'));
+    }
+});
+
+M.form = M.form || {};
+M.form.passwordunmask = function(params) {
+    return new PASSWORDUNMASK(params);
+};
diff --git a/lib/form/yui/src/passwordunmask/meta/passwordunmask.json b/lib/form/yui/src/passwordunmask/meta/passwordunmask.json
new file mode 100644 (file)
index 0000000..1830917
--- /dev/null
@@ -0,0 +1,8 @@
+{
+    "moodle-form-passwordunmask": {
+        "requires": [
+            "node",
+            "base"
+        ]
+    }
+}
index 654b9de..f3da219 100644 (file)
@@ -172,7 +172,7 @@ class moodle_google_curlio extends Google_CurlIO {
             if (!is_string($name)) {
                 $name = $this->get_option_name_from_constant($name);
             }
-            $safeParams[$name] = $vale;
+            $safeParams[$name] = $value;
         }
         parent::setOptions($safeParams);
     }
index 18d1180..4a0aa6e 100644 (file)
@@ -1209,6 +1209,42 @@ class grade_item extends grade_object {
         $this->set_sortorder($sortorder + 1);
     }
 
+    /**
+     * Detect duplicate grade item's sortorder and re-sort them.
+     * Note: Duplicate sortorder will be introduced while duplicating activities or
+     * merging two courses.
+     *
+     * @param int $courseid id of the course for which grade_items sortorder need to be fixed.
+     */
+    public static function fix_duplicate_sortorder($courseid) {
+        global $DB;
+
+        $transaction = $DB->start_delegated_transaction();
+
+        $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+                    FROM {grade_items} g1
+                    JOIN {grade_items} g2 ON g1.courseid = g2.courseid
+                WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
+                ORDER BY g1.sortorder DESC, g1.id DESC";
+
+        // Get all duplicates in course highest sort order, and higest id first so that we can make space at the
+        // bottom higher end of the sort orders and work down by id.
+        $rs = $DB->get_recordset_sql($sql, array('courseid' => $courseid));
+
+        foreach($rs as $duplicate) {
+            $DB->execute("UPDATE {grade_items}
+                            SET sortorder = sortorder + 1
+                          WHERE courseid = :courseid AND
+                          (sortorder > :sortorder OR (sortorder = :sortorder2 AND id > :id))",
+                array('courseid' => $duplicate->courseid,
+                    'sortorder' => $duplicate->sortorder,
+                    'sortorder2' => $duplicate->sortorder,
+                    'id' => $duplicate->id));
+        }
+        $rs->close();
+        $transaction->allow_commit();
+    }
+
     /**
      * Returns the most descriptive field for this object.
      *
index cc3dbb0..52613b5 100644 (file)
@@ -81,11 +81,11 @@ abstract class grade_base_testcase extends advanced_testcase {
     }
 
     private function load_modules() {
-        $this->activities[0] = $this->getDataGenerator()->create_module('assignment', array('course'=>$this->course->id));
-        $this->course_module[0] = get_coursemodule_from_instance('assignment', $this->activities[0]->id);
+        $this->activities[0] = $this->getDataGenerator()->create_module('assign', array('course'=>$this->course->id));
+        $this->course_module[0] = get_coursemodule_from_instance('assign', $this->activities[0]->id);
 
-        $this->activities[1] = $this->getDataGenerator()->create_module('assignment', array('course'=>$this->course->id));
-        $this->course_module[1] = get_coursemodule_from_instance('assignment', $this->activities[1]->id);
+        $this->activities[1] = $this->getDataGenerator()->create_module('assign', array('course'=>$this->course->id));
+        $this->course_module[1] = get_coursemodule_from_instance('assign', $this->activities[1]->id);
 
         $this->activities[2] = $this->getDataGenerator()->create_module('forum', array('course'=>$this->course->id));
         $this->course_module[2] = get_coursemodule_from_instance('forum', $this->activities[2]->id);
index 7b29077..34ba416 100644 (file)
@@ -65,6 +65,7 @@ class core_grade_item_testcase extends grade_base_testcase {
         $this->sub_test_grade_item_compute();
         $this->sub_test_update_final_grade();
         $this->sub_test_grade_item_can_control_visibility();
+        $this->sub_test_grade_item_fix_sortorder();
     }
 
     protected function sub_test_grade_item_construct() {
@@ -630,4 +631,106 @@ class core_grade_item_testcase extends grade_base_testcase {
         $grade_item = new grade_item($this->grade_items[11], false);
         $this->assertFalse($grade_item->can_control_visibility());
     }
+
+    /**
+     * Test the {@link grade_item::fix_duplicate_sortorder() function with
+     * faked duplicate sortorder data.
+     */
+    public function sub_test_grade_item_fix_sortorder() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Each set is used for filling the db with fake data and will be representing the result of query:
+        // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
+        $testsets = array(
+            // Items that need no action.
+            array(1,2,3),
+            array(5,6,7),
+            array(7,6,1,3,2,5),
+            // Items with sortorder duplicates
+            array(1,2,2,3,3,4,5),
+            // Only one sortorder duplicate.
+            array(1,1),
+            array(3,3),
+            // Non-sequential sortorders with one or multiple duplicates.
+            array(3,3,7,5,6,6,9,10,8,3),
+            array(7,7,3),
+            array(3,4,5,3,5,4,7,1)
+        );
+        $origsequence = array();
+
+        // Generate the data and remember the initial sequence or items.
+        foreach ($testsets as $testset) {
+            $course = $this->getDataGenerator()->create_course();
+            foreach ($testset as $sortorder) {
+                $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
+            }
+            $DB->get_records('grade_items');
+            $origsequence[$course->id] = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
+                "WHERE courseid = ? ORDER BY sortorder, id", array($course->id));
+        }
+
+        $duplicatedetectionsql = "SELECT courseid, sortorder
+                                    FROM {grade_items}
+                                WHERE courseid = :courseid
+                                GROUP BY courseid, sortorder
+                                  HAVING COUNT(id) > 1";
+
+        // Do the work.
+        foreach ($origsequence as $courseid => $ignore) {
+            grade_item::fix_duplicate_sortorder($courseid);
+            // Verify that no duplicates are left in the database.
+            $dupes = $DB->record_exists_sql($duplicatedetectionsql, array('courseid' => $courseid));
+            $this->assertFalse($dupes);
+        }
+
+        // Verify that sequences are exactly the same as they were before upgrade script.
+        $idx = 0;
+        foreach ($origsequence as $courseid => $sequence) {
+            if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
+                // If there were no duplicates for this course verify that sortorders are not modified.
+                $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
+                $this->assertEquals($testsets[$idx], $newsortorders);
+            }
+            $newsequence = $DB->get_fieldset_sql("SELECT id FROM {grade_items} ".
+                "WHERE courseid = ? ORDER BY sortorder, id", array($courseid));
+            $this->assertEquals($sequence, $newsequence,
+                    "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
+            $idx++;
+        }
+    }
+
+    /**
+     * Populate some fake grade items into the database with specified
+     * sortorder and course id.
+     *
+     * NOTE: This function doesn't make much attempt to respect the
+     * gradebook internals, its simply used to fake some data for
+     * testing the upgradelib function. Please don't use it for other
+     * purposes.
+     *
+     * @param int $courseid id of course
+     * @param int $sortorder numeric sorting order of item
+     * @return stdClass grade item object from the database.
+     */
+    private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
+        global $DB, $CFG;
+        require_once($CFG->libdir.'/gradelib.php');
+
+        $item = new stdClass();
+        $item->courseid = $courseid;
+        $item->sortorder = $sortorder;
+        $item->gradetype = GRADE_TYPE_VALUE;
+        $item->grademin = 30;
+        $item->grademax = 110;
+        $item->itemnumber = 1;
+        $item->iteminfo = '';
+        $item->timecreated = time();
+        $item->timemodified = time();
+
+        $item->id = $DB->insert_record('grade_items', $item);
+
+        return $DB->get_record('grade_items', array('id' => $item->id));
+    }
 }
index eca6360..690c352 100644 (file)
@@ -27,7 +27,6 @@ DirectoryIndex index.php index.html index.htm
 
 ### Thirdly, set up some PHP variables that Moodle needs
 
-php_flag register_globals        0
 php_flag file_uploads            1
 php_flag short_open_tag          1
 php_flag session.auto_start      0
index 8fa85e9..066b625 100644 (file)
@@ -718,7 +718,9 @@ M.util.js_watch_io();
  * @return boolean - True if there is any pending js.
  */
 M.util.js_complete = function(uniqid) {
-    var index = M.util.pending_js.indexOf(uniqid);
+    // Use the Y.Array.indexOf instead of the native because some older browsers do not support
+    // the native function. Y.Array polyfills the native function if it does not exist.
+    var index = Y.Array.indexOf(M.util.pending_js, uniqid);
     if (index >= 0) {
         M.util.complete_js.push(M.util.pending_js.splice(index, 1));
     }
index 073ecbc..312ac4e 100644 (file)
@@ -3628,7 +3628,7 @@ function fullname($user, $override=false) {
     // The special characters are Japanese brackets that are common enough to make allowances for them (not covered by :punct:).
     $patterns[] = '/[[:punct:]「」]*EMPTY[[:punct:]「」]*/u';
     // This regular expression is to remove any double spaces in the display name.
-    $patterns[] = '/\s{2,}/';
+    $patterns[] = '/\s{2,}/u';
     foreach ($patterns as $pattern) {
         $displayname = preg_replace($pattern, ' ', $displayname);
     }
@@ -5830,7 +5830,18 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml = '',
         }
         return true;
     } else {
-        add_to_log(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: '. $mail->ErrorInfo);
+        // Trigger event for failing to send email.
+        $event = \core\event\email_failed::create(array(
+            'context' => context_system::instance(),
+            'userid' => $from->id,
+            'relateduserid' => $user->id,
+            'other' => array(
+                'subject' => $subject,
+                'message' => $messagetext,
+                'errorinfo' => $mail->ErrorInfo
+            )
+        ));
+        $event->trigger();
         if (CLI_SCRIPT) {
             mtrace('Error: lib/moodlelib.php email_to_user(): '.$mail->ErrorInfo);
         }
index 4ae18b2..06081c0 100644 (file)
@@ -2748,6 +2748,9 @@ class global_navigation_for_ajax extends global_navigation {
         $this->rootnodes['site']    = $this->add_course($SITE);
         $this->rootnodes['mycourses'] = $this->add(get_string('mycourses'), new moodle_url('/my'), self::TYPE_ROOTNODE, null, 'mycourses');
         $this->rootnodes['courses'] = $this->add(get_string('courses'), null, self::TYPE_ROOTNODE, null, 'courses');
+        // The courses branch is always displayed, and is always expandable (although may be empty).
+        // This mimicks what is done during {@link global_navigation::initialise()}.
+        $this->rootnodes['courses']->isexpandable = true;
 
         // Branchtype will be one of navigation_node::TYPE_*
         switch ($this->branchtype) {
index af3fea5..d43d7c7 100644 (file)
@@ -1413,7 +1413,7 @@ class core_renderer extends renderer_base {
                 $output .= $this->block($bc, $region);
                 $lastblock = $bc->title;
             } else if ($bc instanceof block_move_target) {
-                $output .= $this->block_move_target($bc, $zones, $lastblock);
+                $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
             } else {
                 throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
             }
@@ -1427,11 +1427,18 @@ class core_renderer extends renderer_base {
      * @param block_move_target $target with the necessary details.
      * @param array $zones array of areas where the block can be moved to
      * @param string $previous the block located before the area currently being rendered.
+     * @param string $region the name of the region
      * @return string the HTML to be output.
      */
-    public function block_move_target($target, $zones, $previous) {
+    public function block_move_target($target, $zones, $previous, $region) {
         if ($previous == null) {
-            $position = get_string('moveblockbefore', 'block', $zones[0]);
+            if (empty($zones)) {
+                // There are no zones, probably because there are no blocks.
+                $regions = $this->page->theme->get_all_block_regions();
+                $position = get_string('moveblockinregion', 'block', $regions[$region]);
+            } else {
+                $position = get_string('moveblockbefore', 'block', $zones[0]);
+            }
         } else {
             $position = get_string('moveblockafter', 'block', $previous);
         }
index 749bcfa..983e5e2 100644 (file)
@@ -205,7 +205,6 @@ ini_set('display_errors', '1');
 ini_set('log_errors', '1');
 
 $CFG->noemailever = true; // better not mail anybody from tests, override temporarily if necessary
-$CFG->cachetext = 0; // disable this very nasty setting
 
 // some ugly hacks
 $CFG->themerev = 1;
index a04b326..2d5dfe7 100644 (file)
@@ -108,7 +108,9 @@ class Hint_ResultPrinter extends PHPUnit_TextUI_ResultPrinter {
             $executable = 'phpunit';
             if (testing_is_cygwin()) {
                 $file = str_replace('\\', '/', $file);
-                $executable = 'phpunit.bat';
+                if (!testing_is_mingw()) {
+                    $executable = 'phpunit.bat';
+                }
             }
         }
 
index 8c61eeb..0e6c477 100644 (file)
@@ -704,9 +704,6 @@ function ini_get_bool($ini_get_arg) {
 function setup_validate_php_configuration() {
    // this must be very fast - no slow checks here!!!
 
-   if (ini_get_bool('register_globals')) {
-       print_error('globalswarning', 'admin');
-   }
    if (ini_get_bool('session.auto_start')) {
        print_error('sessionautostartwarning', 'admin');
    }
index c67a1a9..13c8466 100644 (file)
@@ -97,6 +97,30 @@ function testing_is_cygwin() {
     }
 }
 
+/**
+ * Returns whether a mingw CLI is running.
+ *
+ * MinGW sets $_SERVER['TERM'] to cygwin, but it
+ * can not run .bat files; this function may be useful
+ * when we need to output proposed commands to users
+ * using Windows CLI interfaces.
+ *
+ * @link http://sourceforge.net/p/mingw/bugs/1902
+ * @return bool
+ */
+function testing_is_mingw() {
+
+    if (!testing_is_cygwin()) {
+        return false;
+    }
+
+    if (!empty($_SERVER['MSYSTEM'])) {
+        return true;
+    }
+
+    return false;
+}
+
 /**
  * Mark empty dataroot to be used for testing.
  * @param string $dataroot  The dataroot directory
index 80df165..6a9f8d6 100644 (file)
@@ -2799,6 +2799,41 @@ class core_accesslib_testcase extends advanced_testcase {
         }
         $this->assertEquals($perms1, $perms2);
     }
+
+    /**
+     * Tests reset_role_capabilities function.
+     */
+    public function test_reset_role_capabilities() {
+        global $DB;
+        $this->resetAfterTest(true);
+        $generator = $this->getDataGenerator();
+
+        // Create test course and user, enrol one in the other.
+        $course = $generator->create_course();
+        $user = $generator->create_user();
+        $roleid = $DB->get_field('role', 'id', array('shortname' => 'student'), MUST_EXIST);
+        $generator->enrol_user($user->id, $course->id, $roleid);
+
+        // Change student role so it DOES have 'mod/forum:addinstance'.
+        $systemcontext = context_system::instance();
+        assign_capability('mod/forum:addinstance', CAP_ALLOW, $roleid, $systemcontext->id);
+
+        // Override course so it does NOT allow students 'mod/forum:viewdiscussion'.
+        $coursecontext = context_course::instance($course->id);
+        assign_capability('mod/forum:viewdiscussion', CAP_PREVENT, $roleid, $coursecontext->id);
+
+        // Check expected capabilities so far.
+        $this->assertTrue(has_capability('mod/forum:addinstance', $coursecontext, $user));
+        $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
+
+        // Oops, allowing student to add forums was a mistake, let's reset the role.
+        reset_role_capabilities($roleid);
+
+        // Check new expected capabilities - role capabilities should have been reset,
+        // while the override at course level should remain.
+        $this->assertFalse(has_capability('mod/forum:addinstance', $coursecontext, $user));
+        $this->assertFalse(has_capability('mod/forum:viewdiscussion', $coursecontext, $user));
+    }
 }
 
 /**
index 2203702..ab6b291 100644 (file)
@@ -60,16 +60,7 @@ class behat_deprecated extends behat_base {
             '" in the "' . $this->escape($tablerowtext) . '" "table_row"';
         $this->deprecated_message($alternative);
 
-        // The table row container.
-        $nocontainerexception = new ElementNotFoundException($this->getSession(), '"' . $tablerowtext . '" row text ');
-        $tablerowtext = $this->getSession()->getSelectorsHandler()->xpathLiteral($tablerowtext);
-        $rownode = $this->find('xpath', "//tr[contains(., $tablerowtext)]", $nocontainerexception);
-
-        // Looking for the element DOM node inside the specified row.
-        list($selector, $locator) = $this->transform_selector($selectortype, $element);
-        $elementnode = $this->find($selector, $locator, false, $rownode);
-        $this->ensure_element_is_visible($elementnode);
-        $elementnode->click();
+        return new Given($alternative);
     }
 
     /**
@@ -233,6 +224,84 @@ class behat_deprecated extends behat_base {
         return array(new Given($alternative));
     }
 
+    /**
+     * Sends a message to the specified user from the logged user.
+     *
+     * @deprecated since 2.7
+     * @todo MDL-42862 This will be deleted in Moodle 2.9
+     * @see behat_message::i_send_message_to_user()
+     *
+     * @Given /^I send "(?P<message_contents_string>(?:[^"]|\\")*)" message to "(?P<username_string>(?:[^"]|\\")*)"$/
+     * @throws ElementNotFoundException
+     * @param string $messagecontent
+     * @param string $tousername
+     */
+    public function i_send_message_to_user($messagecontent, $tousername) {
+
+        global $DB;
+
+        // Runs by CLI, same PHP process that created the user.
+        $touser = $DB->get_record('user', array('username' => $tousername));
+        if (!$touser) {
+            throw new ElementNotFoundException($this->getSession(), '"' . $tousername . '" ');
+        }
+        $tofullname = fullname($touser);
+
+        $alternative = 'I send "' . $this->escape($messagecontent) . '" message to "' . $tofullname . '" user';
+        $this->deprecated_message($alternative);
+        return new Given($alternative);
+    }
+
+    /**
+     * Adds the user to the specified cohort.
+     *
+     * @deprecated since 2.7
+     * @todo MDL-42862 This will be deleted in Moodle 2.9
+     * @see behat_cohort::i_add_user_to_cohort_members()
+     *
+     * @Given /^I add "(?P<user_username_string>(?:[^"]|\\")*)" user to "(?P<cohort_idnumber_string>(?:[^"]|\\")*)" cohort$/
+     * @param string $username
+     * @param string $cohortidnumber
+     */
+    public function i_add_user_to_cohort($username, $cohortidnumber) {
+        global $DB;
+
+        // The user was created by the data generator, executed by the same PHP process that is
+        // running this step, not by any Selenium action.
+        $user = $DB->get_record('user', array('username' => $username));
+        $userlocator = $user->firstname . ' ' . $user->lastname . ' (' . $user->email . ')';
+
+        $alternative = 'I add "' . $this->escape($userlocator) .
+            '" user to "' . $this->escape($cohortidnumber) . '" cohort members';
+        $this->deprecated_message($alternative);
+
+        return new Given($alternative);
+    }
+
+    /**
+     * Add the specified user to the group. You should be in the groups page when running this step.
+     *
+     * @deprecated since 2.7
+     * @todo MDL-42862 This will be deleted in Moodle 2.9
+     * @see behat_groups::i_add_user_to_group_members()
+     *
+     * @Given /^I add "(?P<username_string>(?:[^"]|\\")*)" user to "(?P<group_name_string>(?:[^"]|\\")*)" group$/
+     * @param string $username
+     * @param string $groupname
+     */
+    public function i_add_user_to_group($username, $groupname) {
+        global $DB;
+
+        $user = $DB->get_record('user', array('username' => $username));
+        $userfullname = fullname($user);
+
+        $alternative = 'I add "' . $this->escape($userfullname) .
+            '" user to "' . $this->escape($groupname) . '" group members';
+        $this->deprecated_message($alternative);
+
+        return new Given($alternative);
+    }
+
     /**
      * Throws an exception if $CFG->behat_usedeprecated is not allowed.
      *
index 2478e78..5bf603d 100644 (file)
@@ -77,6 +77,13 @@ class behat_hooks extends behat_base {
      */
     protected static $currentstepexception = null;
 
+    /**
+     * If we are saving screenshots on failures we should use the same parent dir during a run.
+     *
+     * @var The parent dir name
+     */
+    protected static $screenshotsdirname = false;
+
     /**
      * Gives access to moodle codebase, ensures all is ready and sets up the test lock.
      *
@@ -134,6 +141,10 @@ class behat_hooks extends behat_base {
             // Store the initial browser session opening.
             self::$lastbrowsersessionstart = time();
         }
+
+        if (!empty($CFG->behat_screenshots_path) && !is_writable($CFG->behat_screenshots_path)) {
+            throw new Exception('You set $CFG->behat_screenshots_path to a non-writable directory');
+        }
     }
 
     /**
@@ -258,6 +269,13 @@ class behat_hooks extends behat_base {
      * @AfterStep @javascript
      */
     public function after_step_javascript($event) {
+        global $CFG;
+
+        // Save a screenshot if the step failed.
+        if (!empty($CFG->behat_screenshots_path) &&
+                $event->getResult() === StepEvent::FAILED) {
+            $this->take_screenshot($event);
+        }
 
         try {
             $this->wait_for_pending_js();
@@ -278,6 +296,52 @@ class behat_hooks extends behat_base {
         }
     }
 
+    /**
+     * Getter for self::$screenshotsdirname
+     *
+     * @return string
+     */
+    protected function get_run_screenshots_dir() {
+        return self::$screenshotsdirname;
+    }
+
+    /**
+     * Take screenshot when a step fails.
+     *
+     * @throws Exception
+     * @param StepEvent $event
+     */
+    protected function take_screenshot(StepEvent $event) {
+        global $CFG;
+
+        // Goutte can't save screenshots.
+        if (!$this->running_javascript()) {
+            return false;
+        }
+
+        // All the run screenshots in the same parent dir.
+        if (!$screenshotsdirname = self::get_run_screenshots_dir()) {
+            $screenshotsdirname = self::$screenshotsdirname = date('Ymd_Hi');
+
+            $dir = $CFG->behat_screenshots_path . DIRECTORY_SEPARATOR . $screenshotsdirname;
+
+            if (!mkdir($dir, $CFG->directorypermissions, true)) {
+                // It shouldn't, we already checked that the directory is writable.
+                throw new Exception('No directories can be created inside $CFG->behat_screenshots_path, check the directory permissions.');
+            }
+        } else {
+            // We will always need to know the full path.
+            $dir = $CFG->behat_screenshots_path . DIRECTORY_SEPARATOR . $screenshotsdirname;
+        }
+
+        // The scenario title + the failed step text.
+        // We want a i-am-the-scenario-title_i-am-the-failed-step.png format.
+        $filename = $event->getStep()->getParent()->getTitle() . '_' . $event->getStep()->getText();
+        $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename) . '.png';
+
+        $this->saveScreenshot($filename, $dir);
+    }
+
     /**
      * Waits for all the JS to be loaded.
      *
diff --git a/lib/tests/events_test.php b/lib/tests/events_test.php
new file mode 100644 (file)
index 0000000..e298bcc
--- /dev/null
@@ -0,0 +1,174 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Events tests.
+ *
+ * @package   core
+ * @category  test
+ * @copyright 2014 Mark Nelson <markn@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+class core_events_testcase extends advanced_testcase {
+
+    /**
+     * Test set up.
+     *
+     * This is executed before running any test in this file.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+    }
+
+    /**
+     * Test the course category created event.
+     */
+    public function test_course_category_created() {
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $category = $this->getDataGenerator()->create_category();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_created', $event);
+        $this->assertEquals(context_coursecat::instance($category->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'add', 'editcategory.php?id=' . $category->id, $category->id);
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+
+    /**
+     * Test the course category updated event.
+     */
+    public function test_course_category_updated() {
+        // Create a category.
+        $category = $this->getDataGenerator()->create_category();
+
+        // Create some data we are going to use to update this category.
+        $data = new stdClass();
+        $data->name = 'Category name change';
+
+        // Trigger and capture the event for updating a category.
+        $sink = $this->redirectEvents();
+        $category->update($data);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($category->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'update', 'editcategory.php?id=' . $category->id, $category->id);
+        $this->assertEventLegacyLogData($expected, $event);
+
+        // Create another category and a child category.
+        $category2 = $this->getDataGenerator()->create_category();
+        $childcat = $this->getDataGenerator()->create_category(array('parent' => $category2->id));
+
+        // Trigger and capture the event for changing the parent of a category.
+        $sink = $this->redirectEvents();
+        $childcat->change_parent($category);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($childcat->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'move', 'editcategory.php?id=' . $childcat->id, $childcat->id);
+        $this->assertEventLegacyLogData($expected, $event);
+
+        // Trigger and capture the event for changing the sortorder of a category.
+        $sink = $this->redirectEvents();
+        $category2->change_sortorder_by_one(true);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($category2->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'move', 'management.php?categoryid=' . $category2->id, $category2->id);
+        $this->assertEventLegacyLogData($expected, $event);
+
+        // Trigger and capture the event for deleting a category and moving it's children to another.
+        $sink = $this->redirectEvents();
+        $category->delete_move($category->id);
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($childcat->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'move', 'editcategory.php?id=' . $childcat->id, $childcat->id);
+        $this->assertEventLegacyLogData($expected, $event);
+
+        // Trigger and capture the event for hiding a category.
+        $sink = $this->redirectEvents();
+        $category2->hide();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($category2->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'hide', 'editcategory.php?id=' . $category2->id, $category2->id);
+        $this->assertEventLegacyLogData($expected, $event);
+
+        // Trigger and capture the event for unhiding a category.
+        $sink = $this->redirectEvents();
+        $category2->show();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        // Check that the event data is valid.
+        $this->assertInstanceOf('\core\event\course_category_updated', $event);
+        $this->assertEquals(context_coursecat::instance($category2->id), $event->get_context());
+        $expected = array(SITEID, 'category', 'show', 'editcategory.php?id=' . $category2->id, $category2->id);
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+
+    /**
+     * Test the email failed event.
+     *
+     * It's not possible to use the moodle API to simulate the failure of sending
+     * an email, so here we simply create the event and trigger it.
+     */
+    public function test_email_failed() {
+        // Trigger event for failing to send email.
+        $event = \core\event\email_failed::create(array(
+            'context' => context_system::instance(),
+            'userid' => 1,
+            'relateduserid' => 2,
+            'other' => array(
+                'subject' => 'This is a subject',
+                'message' => 'This is a message',
+                'errorinfo' => 'The email failed to send!'
+            )
+        ));
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $event->trigger();
+        $events = $sink->get_events();
+        $event = reset($events);
+
+        $this->assertInstanceOf('\core\event\email_failed', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+        $expected = array(SITEID, 'library', 'mailer', qualified_me(), 'ERROR: The email failed to send!');
+        $this->assertEventLegacyLogData($expected, $event);
+    }
+}
index 7e9be49..0ff743f 100644 (file)
@@ -42,4 +42,111 @@ class core_upgradelib_testcase extends advanced_testcase {
         // if there aren't any old files in the codebase.
         $this->assertFalse(upgrade_stale_php_files_present());
     }
+
+    /**
+     * Test the {@link upgrade_grade_item_fix_sortorder() function with
+     * faked duplicate sortorder data.
+     */
+    public function test_upgrade_grade_item_fix_sortorder() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // The purpose of this test is to make sure that after upgrade script
+        // there is no duplicates in the field grade_items.sortorder (for each course)
+        // and the result of query "SELECT id FROM grade_items WHERE courseid=? ORDER BY sortorder, id" does not change.
+        $sequencesql = 'SELECT id FROM {grade_items} WHERE courseid=? ORDER BY sortorder, id';
+
+        // Each set is used for filling the db with fake data and will be representing the result of query:
+        // "SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id".
+        $testsets = array(
+            // Items that need no action.
+            array(1,2,3),
+            array(5,6,7),
+            array(7,6,1,3,2,5),
+            // Items with sortorder duplicates
+            array(1,2,2,3,3,4,5),
+            // Only one sortorder duplicate.
+            array(1,1),
+            array(3,3),
+            // Non-sequential sortorders with one or multiple duplicates.
+            array(3,3,7,5,6,6,9,10,8,3),
+            array(7,7,3),
+            array(3,4,5,3,5,4,7,1)
+        );
+        $origsequences = array();
+
+        // Generate the data and remember the initial sequence or items.
+        foreach ($testsets as $testset) {
+            $course = $this->getDataGenerator()->create_course();
+            foreach ($testset as $sortorder) {
+                $this->insert_fake_grade_item_sortorder($course->id, $sortorder);
+            }
+            $DB->get_records('grade_items');
+            $origsequences[$course->id] = $DB->get_fieldset_sql($sequencesql, array($course->id));
+        }
+
+        $duplicatedetectionsql = "SELECT courseid, sortorder
+                                    FROM {grade_items}
+                                GROUP BY courseid, sortorder
+                                  HAVING COUNT(id) > 1";
+
+        // Verify there are duplicates before we start the fix.
+        $dupes = $DB->record_exists_sql($duplicatedetectionsql);
+        $this->assertTrue($dupes);
+
+        // Do the work.
+        upgrade_grade_item_fix_sortorder();
+
+        // Verify that no duplicates are left in the database.
+        $dupes = $DB->record_exists_sql($duplicatedetectionsql);
+        $this->assertFalse($dupes);
+
+        // Verify that sequences are exactly the same as they were before upgrade script.
+        $idx = 0;
+        foreach ($origsequences as $courseid => $origsequence) {
+            if (count(($testsets[$idx])) == count(array_unique($testsets[$idx]))) {
+                // If there were no duplicates for this course verify that sortorders are not modified.
+                $newsortorders = $DB->get_fieldset_sql("SELECT sortorder from {grade_items} WHERE courseid=? ORDER BY id", array($courseid));
+                $this->assertEquals($testsets[$idx], $newsortorders);
+            }
+            $newsequence = $DB->get_fieldset_sql($sequencesql, array($courseid));
+            $this->assertEquals($origsequence, $newsequence,
+                    "Sequences do not match for test set $idx : ".join(',', $testsets[$idx]));
+            $idx++;
+        }
+    }
+
+    /**
+     * Populate some fake grade items into the database with specified
+     * sortorder and course id.
+     *
+     * NOTE: This function doesn't make much attempt to respect the
+     * gradebook internals, its simply used to fake some data for
+     * testing the upgradelib function. Please don't use it for other
+     * purposes.
+     *
+     * @param int $courseid id of course
+     * @param int $sortorder numeric sorting order of item
+     * @return stdClass grade item object from the database.
+     */
+    private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
+        global $DB, $CFG;
+        require_once($CFG->libdir.'/gradelib.php');
+
+        $item = new stdClass();
+        $item->courseid = $courseid;
+        $item->sortorder = $sortorder;
+        $item->gradetype = GRADE_TYPE_VALUE;
+        $item->grademin = 30;
+        $item->grademax = 110;
+        $item->itemnumber = 1;
+        $item->iteminfo = '';
+        $item->timecreated = time();
+        $item->timemodified = time();
+
+        $item->id = $DB->insert_record('grade_items', $item);
+
+        return $DB->get_record('grade_items', array('id' => $item->id));
+    }
 }
index afc80e3..15a06ca 100644 (file)
@@ -3,6 +3,8 @@ information provided here is intended especially for developers.
 
 === 2.7 ===
 
+* $core_renderer->block_move_target() changed to support more verbose move-block-here descriptions.
+
 DEPRECATIONS:
 * Abstract class \core\event\course_module_instances_list_viewed is deprecated now, use \core\event\instances_list_viewed instead.
 * mod_book\event\instances_list_viewed has been deprecated. Please use mod_book\event\course_module_instance_list_viewed instead.
index 0ec6e24..24563fd 100644 (file)
@@ -2049,3 +2049,42 @@ function upgrade_rename_old_backup_files_using_shortname() {
         @rename($dir . '/' . $file, $dir . '/' . $newname);
     }
 }
+
+/**
+ * Detect duplicate grade item sortorders and resort the
+ * items to remove them.
+ */
+function upgrade_grade_item_fix_sortorder() {
+    global $DB;
+
+    // The simple way to fix these sortorder duplicates would be simply to resort each
+    // affected course. But in order to reduce the impact of this upgrade step we're trying
+    // to do it more efficiently by doing a series of update statements rather than updating
+    // every single grade item in affected courses.
+
+    $transaction = $DB->start_delegated_transaction();
+
+    $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+              FROM {grade_items} g1
+              JOIN {grade_items} g2 ON g1.courseid = g2.courseid
+             WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id
+             ORDER BY g1.courseid ASC, g1.sortorder DESC, g1.id DESC";
+
+    // Get all duplicates in course order, highest sort order, and higest id first so that we can make space at the
+    // bottom higher end of the sort orders and work down by id.
+    $rs = $DB->get_recordset_sql($sql);
+
+    foreach($rs as $duplicate) {
+        $DB->execute("UPDATE {grade_items}
+                         SET sortorder = sortorder + 1
+                       WHERE courseid = :courseid AND
+                       (sortorder > :sortorder OR (sortorder = :sortorder2 AND id > :id))",
+            array('courseid' => $duplicate->courseid,
+                'sortorder' => $duplicate->sortorder,
+                'sortorder2' => $duplicate->sortorder,
+                'id' => $duplicate->id));
+    }
+    $rs->close();
+
+    $transaction->allow_commit();
+}
index a5780b0..43abf4d 100644 (file)
@@ -1097,7 +1097,6 @@ function format_text_menu() {
  */
 function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseiddonotuse = null) {
     global $CFG, $DB, $PAGE;
-    static $croncache = array();
 
     if ($text === '' || is_null($text)) {
         // No need to do any filters and cleaning.
@@ -1166,34 +1165,6 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd
         $filtermanager = new null_filter_manager();
     }
 
-    if (!empty($CFG->cachetext) and empty($options['nocache'])) {
-        $hashstr = $text.'-'.$filtermanager->text_filtering_hash($context).'-'.$context->id.'-'.current_language().'-'.
-                (int)$format.(int)$options['trusted'].(int)$options['noclean'].
-                (int)$options['para'].(int)$options['newlines'];
-
-        $time = time() - $CFG->cachetext;
-        $md5key = md5($hashstr);
-        if (CLI_SCRIPT) {
-            if (isset($croncache[$md5key])) {
-                return $croncache[$md5key];
-            }
-        }
-
-        if ($oldcacheitem = $DB->get_record('cache_text', array('md5key' => $md5key), '*', IGNORE_MULTIPLE)) {
-            if ($oldcacheitem->timemodified >= $time) {
-                if (CLI_SCRIPT) {
-                    if (count($croncache) > 150) {
-                        reset($croncache);
-                        $key = key($croncache);
-                        unset($croncache[$key]);
-                    }
-                    $croncache[$md5key] = $oldcacheitem->formattedtext;
-                }
-                return $oldcacheitem->formattedtext;
-            }
-        }
-    }
-
     switch ($format) {
         case FORMAT_HTML:
             if (!$options['noclean']) {
@@ -1257,65 +1228,15 @@ function format_text($text, $format = FORMAT_MOODLE, $options = null, $courseidd
         }
     }
 
-    // Warn people that we have removed this old mechanism, just in case they
-    // were stupid enough to rely on it.
-    if (isset($CFG->currenttextiscacheable)) {
-        debugging('Once upon a time, Moodle had a truly evil use of global variables ' .
-            'called $CFG->currenttextiscacheable. The good news is that this no ' .
-            'longer exists. The bad news is that you seem to be using a filter that '.
-            'relies on it. Please seek out and destroy that filter code.', DEBUG_DEVELOPER);
-    }
-
     if (!empty($options['overflowdiv'])) {
         $text = html_writer::tag('div', $text, array('class' => 'no-overflow'));
     }
 
-    if (empty($options['nocache']) and !empty($CFG->cachetext)) {
-        if (CLI_SCRIPT) {
-            // Special static cron cache - no need to store it in db if its not already there.
-            if (count($croncache) > 150) {
-                reset($croncache);
-                $key = key($croncache);
-                unset($croncache[$key]);
-            }
-            $croncache[$md5key] = $text;
-            return $text;
-        }
-
-        $newcacheitem = new stdClass();
-        $newcacheitem->md5key = $md5key;
-        $newcacheitem->formattedtext = $text;
-        $newcacheitem->timemodified = time();
-        if ($oldcacheitem) {
-            // See bug 4677 for discussion.
-            $newcacheitem->id = $oldcacheitem->id;
-            try {
-                // Update existing record in the cache table.
-                $DB->update_record('cache_text', $newcacheitem);
-            } catch (dml_exception $e) {
-                // It's unlikely that the cron cache cleaner could have
-                // deleted this entry in the meantime, as it allows
-                // some extra time to cover these cases.
-            }
-        } else {
-            try {
-                // Insert a new record in the cache table.
-                $DB->insert_record('cache_text', $newcacheitem);
-            } catch (dml_exception $e) {
-                // Again, it's possible that another user has caused this
-                // record to be created already in the time that it took
-                // to traverse this function.  That's OK too, as the
-                // call above handles duplicate entries, and eventually
-                // the cron cleaner will delete them.
-            }
-        }
-    }
-
     return $text;
 }
 
 /**
- * Resets all data related to filters, called during upgrade or when filter settings change.
+ * Resets some data related to filters, called during upgrade or when general filter settings change.
  *
  * @param bool $phpunitreset true means called from our PHPUnit integration test reset
  * @return void
@@ -1325,14 +1246,12 @@ function reset_text_filters_cache($phpunitreset = false) {
 
     if ($phpunitreset) {
         // HTMLPurifier does not change, DB is already reset to defaults,
-        // nothing to