Merge branch 'MDL-50637' of git://github.com/sk-unikent/moodle
authorDavid Monllao <davidm@moodle.com>
Mon, 17 Aug 2015 05:56:18 +0000 (13:56 +0800)
committerDavid Monllao <davidm@moodle.com>
Mon, 17 Aug 2015 05:56:18 +0000 (13:56 +0800)
482 files changed:
admin/cli/fix_orphaned_question_categories.php [new file with mode: 0644]
admin/cli/install.php
admin/environment.xml
admin/roles/classes/define_role_table_advanced.php
admin/search.php
admin/settings.php
admin/tool/behat/tests/behat/edit_permissions.feature
admin/tool/behat/tests/behat/nasty_strings.feature
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js
admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js
admin/tool/capability/yui/src/search/js/search.js
admin/tool/generator/lib.php
admin/tool/langimport/classes/controller.php
admin/tool/langimport/index.php
admin/tool/langimport/lang/en/tool_langimport.php
admin/tool/log/store/legacy/db/tasks.php
admin/tool/log/store/legacy/version.php
admin/tool/log/store/standard/db/tasks.php
admin/tool/log/store/standard/version.php
admin/tool/phpunit/webrunner.php
admin/upgradesettings.php
auth/db/auth.php
auth/ldap/auth.php
auth/ldap/tests/plugin_test.php
auth/ldap/upgrade.txt [new file with mode: 0644]
auth/shibboleth/index.php
availability/classes/info.php
availability/classes/multiple_messages.php [new file with mode: 0644]
availability/classes/tree.php
availability/condition/completion/tests/behat/conditional_bug.feature [new file with mode: 0644]
availability/condition/grade/classes/condition.php
availability/renderer.php
availability/tests/component_test.php
availability/tests/tree_test.php
backup/controller/backup_controller.class.php
backup/controller/restore_controller.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/moodle2/tests/moodle2_course_format_test.php
backup/util/dbops/restore_dbops.class.php
backup/util/ui/restore_ui.class.php
backup/util/ui/restore_ui_stage.class.php
badges/view.php
blocks/blog_tags/tests/behat/blogtag.feature [new file with mode: 0644]
blocks/navigation/renderer.php
blocks/rss_client/block_rss_client.php
blocks/rss_client/db/install.xml
blocks/rss_client/db/upgrade.php [new file with mode: 0644]
blocks/rss_client/tests/cron_test.php [new file with mode: 0644]
blocks/rss_client/version.php
blocks/settings/styles.css
blocks/site_main_menu/block_site_main_menu.php
blocks/site_main_menu/tests/behat/add_url.feature [new file with mode: 0644]
blocks/tag_youtube/db/install.php [new file with mode: 0644]
blocks/tag_youtube/tests/block_tag_youtube_test.php [new file with mode: 0644]
blocks/tags/tests/behat/coursetags.feature [new file with mode: 0644]
blocks/tags/tests/behat/tagcloud.feature [new file with mode: 0644]
blocks/tests/behat/restrict_available_blocks.feature
blog/tests/behat/blog_visibility.feature
cache/classes/helper.php
cache/stores/file/lib.php
cache/stores/file/tests/file_test.php
cache/tests/cache_test.php
calendar/lib.php
calendar/set.php
cohort/tests/cohortlib_test.php
completion/criteria/completion_criteria.php
completion/tests/behat/behat_completion.php
composer.json
composer.lock
course/dndupload.js
course/externallib.php
course/lib.php
course/modlib.php
course/moodleform_mod.php
course/renderer.php
course/tests/behat/navigate_course_list.feature
course/tests/behat/restrict_available_activities.feature
course/tests/courselib_test.php
course/togglecompletion.php
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/toolboxes/js/resource.js
enrol/index.php
enrol/locallib.php
enrol/meta/locallib.php
enrol/users_forms.php
error/index.php
filter/emoticon/filter.php
filter/emoticon/tests/filter_test.php
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-debug.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker-min.js
filter/glossary/yui/build/moodle-filter_glossary-autolinker/moodle-filter_glossary-autolinker.js
filter/glossary/yui/src/autolinker/js/autolinker.js
filter/glossary/yui/src/autolinker/meta/autolinker.json
filter/upgrade.txt
filter/urltolink/filter.php
grade/edit/tree/category.php
grade/edit/tree/item.php
grade/edit/tree/lib.php
grade/edit/tree/outcomeitem.php
grade/export/lib.php
grade/export/txt/tests/behat/export.feature
grade/grading/form/guide/js/guideeditor.js
grade/grading/form/guide/renderer.php
grade/grading/form/rubric/js/rubriceditor.js
grade/grading/form/rubric/lang/en/gradingform_rubric.php
grade/grading/form/rubric/renderer.php
grade/grading/form/rubric/styles.css
grade/grading/form/rubric/version.php
grade/import/xml/grade_import_form.php
grade/import/xml/index.php
grade/import/xml/lang/en/gradeimport_xml.php
grade/lib.php
grade/report/grader/lib.php
grade/report/lib.php
grade/report/singleview/classes/local/screen/user.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_aggregation_changes.feature [new file with mode: 0644]
grade/tests/behat/grade_calculated_grade_items.feature [new file with mode: 0644]
grade/tests/behat/grade_calculated_grade_items_20150627.feature [new file with mode: 0644]
grade/tests/behat/grade_minmax.feature
grade/tests/behat/grade_natural_exclude_empty.feature [new file with mode: 0644]
grade/tests/behat/grade_natural_exclude_empty_20150619.feature [new file with mode: 0644]
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_natural_normalisation_20150619.feature [new file with mode: 0644]
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
grade/tests/behat/grade_to_pass.feature
grade/tests/behat/grade_view.feature
group/externallib.php
group/module.js
group/overview.php
group/tests/behat/create_groups.feature
group/tests/externallib_test.php
install/lang/bg/admin.php
install/lang/es/install.php
install/lang/es_mx/error.php
install/lang/es_mx/langconfig.php
install/lang/gl/error.php
install/lang/ja/error.php
install/lang/mk/install.php
install/lang/oc_gsc/admin.php [new file with mode: 0644]
install/lang/oc_gsc/error.php [new file with mode: 0644]
install/lang/oc_gsc/install.php [new file with mode: 0644]
install/lang/oc_gsc/langconfig.php [moved from install/lang/sr_cr_bo/langconfig.php with 94% similarity]
install/lang/oc_gsc/moodle.php [new file with mode: 0644]
install/lang/pt_br/error.php
install/lang/sma/langconfig.php [new file with mode: 0644]
install/lang/smj/langconfig.php [new file with mode: 0644]
install/lang/sr_cr/install.php
install/lang/sr_lt/install.php
install/lang/tn/langconfig.php [moved from install/lang/sr/langconfig.php with 96% similarity]
lang/en/admin.php
lang/en/auth.php
lang/en/cache.php
lang/en/deprecated.txt
lang/en/enrol.php
lang/en/grades.php
lang/en/moodle.php
lang/en/notes.php
lang/en/portfolio.php
lib/accesslib.php
lib/adminlib.php
lib/adodb/adodb.inc.php
lib/adodb/readme_moodle.txt
lib/ajax/getsiteadminbranch.php
lib/ajax/service.php
lib/amd/build/ajax.min.js
lib/amd/build/templates.min.js
lib/amd/src/ajax.js
lib/amd/src/templates.js
lib/authlib.php
lib/badgeslib.php
lib/behat/behat_files.php
lib/behat/classes/behat_command.php
lib/behat/lib.php
lib/blocklib.php
lib/classes/component.php
lib/classes/message/message.php
lib/classes/progress/none.php [moved from lib/classes/progress/null.php with 73% similarity]
lib/classes/session/manager.php
lib/classes/task/manager.php
lib/clilib.php
lib/datalib.php
lib/db/access.php
lib/db/caches.php
lib/db/install.php
lib/db/renamedclasses.php
lib/db/services.php
lib/db/upgrade.php
lib/db/upgradelib.php
lib/ddllib.php
lib/deprecatedlib.php
lib/dml/mssql_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/dmllib.php
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-debug.js
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button-min.js
lib/editor/atto/plugins/link/yui/build/moodle-atto_link-button/moodle-atto_link-button.js
lib/editor/atto/plugins/link/yui/src/button/js/button.js
lib/environmentlib.php
lib/filelib.php
lib/filterlib.php
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-debug.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector-min.js
lib/form/yui/build/moodle-form-dateselector/moodle-form-dateselector.js
lib/form/yui/src/dateselector/js/dateselector.js
lib/formslib.php
lib/grade/grade_category.php
lib/grade/grade_grade.php
lib/grade/grade_item.php
lib/grade/tests/grade_item_test.php
lib/medialib.php
lib/moodlelib.php
lib/myprofilelib.php
lib/navigationlib.php
lib/outputlib.php
lib/outputrenderers.php
lib/pagelib.php
lib/phpunit/classes/advanced_testcase.php
lib/phpunit/classes/base_testcase.php [new file with mode: 0644]
lib/phpunit/classes/basic_testcase.php
lib/phpunit/classes/hint_resultprinter.php
lib/phpunit/classes/util.php
lib/phpunit/lib.php
lib/phpunit/phpunit.xsd
lib/questionlib.php
lib/setuplib.php
lib/tablelib.php
lib/testing/generator/data_generator.php
lib/testing/lib.php
lib/tests/accesslib_test.php
lib/tests/behat/behat_data_generators.php
lib/tests/behat/behat_forms.php
lib/tests/behat/behat_navigation.php
lib/tests/behat/behat_permissions.php
lib/tests/environment_test.php
lib/tests/filelib_test.php
lib/tests/filter_manager_test.php [new file with mode: 0644]
lib/tests/medialib_test.php
lib/tests/moodlelib_test.php
lib/tests/questionlib_test.php
lib/tests/scheduled_task_test.php
lib/tests/upgradelib_test.php
lib/tests/weblib_format_text_test.php [new file with mode: 0644]
lib/tests/weblib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/weblib.php
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js
lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-debug.js [new file with mode: 0644]
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm-min.js [new file with mode: 0644]
lib/yui/build/moodle-core-languninstallconfirm/moodle-core-languninstallconfirm.js [new file with mode: 0644]
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/dragdrop/js/dragdrop.js
lib/yui/src/languninstallconfirm/build.json [new file with mode: 0644]
lib/yui/src/languninstallconfirm/js/languninstallconfirm.js [new file with mode: 0644]
lib/yui/src/languninstallconfirm/meta/languninstallconfirm.json [new file with mode: 0644]
lib/yui/src/notification/js/info.js
login/index.php
login/signup_form.php
login/token.php
message/externallib.php
message/lib.php
message/tests/behat/display_history.feature
message/tests/behat/manage_contacts.feature
message/tests/behat/message_participants.feature
message/tests/behat/recent_conversations.feature
message/tests/behat/search_history.feature
message/tests/externallib_test.php
message/tests/messagelib_test.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/editpdf/classes/page_editor.php
mod/assign/mod_form.php
mod/assign/settings.php
mod/assign/tests/externallib_test.php
mod/assign/version.php
mod/book/styles.css
mod/choice/view.php
mod/data/classes/external.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/styles.css
mod/data/templates.php
mod/feedback/lang/en/deprecated.txt [new file with mode: 0644]
mod/feedback/lang/en/feedback.php
mod/feedback/lib.php
mod/folder/classes/external.php [new file with mode: 0644]
mod/folder/db/services.php [new file with mode: 0644]
mod/folder/lib.php
mod/folder/tests/externallib_test.php [new file with mode: 0644]
mod/folder/tests/lib_test.php [new file with mode: 0644]
mod/folder/version.php
mod/forum/db/services.php
mod/forum/discuss.php
mod/forum/externallib.php
mod/forum/lang/en/deprecated.txt [new file with mode: 0644]
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/markposts.php
mod/forum/post.php
mod/forum/renderer.php
mod/forum/settracking.php
mod/forum/subscribe.php
mod/forum/subscribers.php
mod/forum/tests/behat/discussion_display.feature
mod/forum/tests/behat/discussion_navigation.feature
mod/forum/tests/behat/edit_post_student.feature
mod/forum/tests/behat/edit_post_teacher.feature
mod/forum/tests/behat/forum_subscriptions_availability.feature
mod/forum/tests/behat/my_forum_posts.feature
mod/forum/tests/behat/post_to_multiple_groups.feature
mod/forum/tests/behat/separate_group_discussions.feature
mod/forum/tests/behat/separate_group_single_group_discussions.feature
mod/forum/tests/behat/single_forum_discussion.feature
mod/forum/tests/behat/track_read_posts.feature
mod/forum/tests/externallib_test.php
mod/glossary/showentry_ajax.php
mod/glossary/view.php
mod/lesson/backup/moodle2/backup_lesson_activity_task.class.php
mod/lesson/backup/moodle2/backup_lesson_stepslib.php
mod/lesson/backup/moodle2/restore_lesson_activity_task.class.php
mod/lesson/backup/moodle2/restore_lesson_stepslib.php
mod/lesson/classes/event/highscore_added.php
mod/lesson/classes/event/highscores_viewed.php
mod/lesson/db/install.xml
mod/lesson/db/upgrade.php
mod/lesson/format.php
mod/lesson/highscores.php [deleted file]
mod/lesson/lang/en/deprecated.txt
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/locallib.php
mod/lesson/mediafile.php
mod/lesson/mod_form.php
mod/lesson/renderer.php
mod/lesson/report.php
mod/lesson/settings.php
mod/lesson/tabs.php
mod/lesson/tests/behat/lesson_delete_answers.feature [new file with mode: 0644]
mod/lesson/tests/behat/lesson_practice.feature
mod/lesson/tests/events_test.php
mod/lesson/upgrade.txt
mod/lesson/version.php
mod/lesson/view.php
mod/lti/edit_form.php
mod/lti/grade.php [deleted file]
mod/lti/lib.php
mod/lti/locallib.php
mod/lti/mod_form.php
mod/lti/toolssettings.php
mod/lti/typessettings.php
mod/page/classes/external.php [new file with mode: 0644]
mod/page/db/services.php [new file with mode: 0644]
mod/page/lib.php
mod/page/tests/externallib_test.php [new file with mode: 0644]
mod/page/tests/lib_test.php [new file with mode: 0644]
mod/page/version.php
mod/page/view.php
mod/quiz/classes/plugininfo/quizaccess.php
mod/quiz/lang/en/quiz.php
mod/quiz/renderer.php
mod/quiz/report/statistics/classes/calculator.php
mod/quiz/report/statistics/report.php
mod/quiz/styles.css
mod/quiz/version.php
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
mod/quiz/yui/src/toolboxes/js/resource.js
mod/resource/classes/external.php [new file with mode: 0644]
mod/resource/db/services.php [new file with mode: 0644]
mod/resource/lib.php
mod/resource/tests/externallib_test.php [new file with mode: 0644]
mod/resource/tests/lib_test.php [new file with mode: 0644]
mod/resource/version.php
mod/resource/view.php
mod/scorm/classes/external.php [new file with mode: 0644]
mod/scorm/db/services.php [new file with mode: 0644]
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/scorm/locallib.php
mod/scorm/player.php
mod/scorm/prereqs.php
mod/scorm/report/userreporttabs.php
mod/scorm/styles.css
mod/scorm/tabs.php
mod/scorm/tests/externallib_test.php [new file with mode: 0644]
mod/scorm/tests/lib_test.php [new file with mode: 0644]
mod/scorm/version.php
mod/scorm/view.php
mod/survey/save.php
mod/upgrade.txt
mod/url/classes/external.php
mod/url/db/services.php
mod/url/view.php
mod/wiki/edit.php
mod/wiki/filesedit.php
mod/workshop/lang/en/workshop.php
mod/workshop/lib.php
mod/workshop/locallib.php
mod/workshop/mod_form.php
mod/workshop/renderer.php
mod/workshop/styles.css
mod/workshop/tests/behat/workshop_assessment.feature
mod/workshop/view.php
my/index.php
my/lib.php
my/tests/behat/restrict_available_blocks.feature
notes/delete.php
notes/externallib.php
notes/lib.php
phpunit.xml.dist
pix/f/epub-128.png [new file with mode: 0644]
pix/f/epub-24.png [new file with mode: 0644]
pix/f/epub-256.png [new file with mode: 0644]
pix/f/epub-32.png [new file with mode: 0644]
pix/f/epub-48.png [new file with mode: 0644]
pix/f/epub-64.png [new file with mode: 0644]
pix/f/epub-72.png [new file with mode: 0644]
pix/f/epub-80.png [new file with mode: 0644]
pix/f/epub-96.png [new file with mode: 0644]
pix/f/epub.png
question/classes/bank/view.php
question/classes/statistics/questions/calculator.php
question/editlib.php
question/engine/lib.php
question/format.php
question/format/xml/format.php
question/format/xml/tests/xmlformat_test.php
question/type/calculatedmulti/questiontype.php
question/type/match/lang/en/qtype_match.php
question/type/match/question.php
question/type/match/renderer.php
question/type/multichoice/lang/en/qtype_multichoice.php
question/type/multichoice/question.php
question/type/multichoice/questiontype.php
question/type/questiontypebase.php
question/type/tests/questiontype_test.php
report/completion/lang/en/report_completion.php
report/completion/user.php
report/log/graph.php
report/outline/tests/behat/user.feature
report/participation/index.php
report/stats/lang/en/report_stats.php
report/stats/user.php
repository/filepicker.js
repository/tests/generator_test.php
repository/wikimedia/wikimedia.php
repository/youtube/db/install.php
repository/youtube/lang/en/repository_youtube.php
tag/manage.php
theme/base/style/blocks.css
theme/base/style/core.css
theme/base/style/filemanager.css
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/message.less
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/less/moodle/responsive.less
theme/bootstrapbase/style/moodle.css
theme/upgrade.txt
user/classes/output/myprofile/manager.php
user/externallib.php
user/lib.php
user/portfolio.php
user/tests/behat/view_full_profile.feature
user/tests/userlib_test.php
user/view.php
version.php
webservice/xmlrpc/lib.php

diff --git a/admin/cli/fix_orphaned_question_categories.php b/admin/cli/fix_orphaned_question_categories.php
new file mode 100644 (file)
index 0000000..eeac16f
--- /dev/null
@@ -0,0 +1,103 @@
+<?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/>.
+
+/**
+ * This script fixes orphaned question categories.
+ *
+ * Orphaned question categories have had their associated context deleted
+ * but the category itself remains in the database with an invalid context.
+ *
+ * @package    core
+ * @subpackage cli
+ * @copyright  2013 Tyler Bannister (tyler.bannister@remote-learner.net)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once($CFG->libdir.'/questionlib.php');
+
+$long = array('fix'  => false, 'help' => false);
+$short = array('f' => 'fix', 'h' => 'help');
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params($long, $short);
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help =
+        "Fix orphaned question categories.
+
+        This scripts detects question categories that have had their
+        context deleted, thus severing them from their original purpose.
+
+        This script will find the orphaned categories and delete the unused
+        questions in each category found.  Used questions will not be
+        deleted, instead they will be moved to a rescue question category.
+
+        Options:
+        -h, --help            Print out this help
+        -f, --fix             Fix the orphaned question categories in the DB.
+                              If not specified only check and report problems to STDERR.
+        Example:
+        \$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php
+        \$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php -f
+        ";
+
+    echo $help;
+    die;
+}
+
+cli_heading('Checking for orphaned categories');
+
+
+$sql = 'SELECT qc.id, qc.contextid, qc.name
+          FROM {question_categories} qc
+     LEFT JOIN {context} c ON qc.contextid = c.id
+         WHERE c.id IS NULL';
+$categories = $DB->get_recordset_sql($sql);
+
+$i = 0;
+foreach ($categories as $category) {
+    $i += 1;
+    echo "Found orphaned category: {$category->name}\n";
+    if (!empty($options['fix'])) {
+        echo "Cleaning...";
+        // One transaction per category.
+        $transaction = $DB->start_delegated_transaction();
+        question_category_delete_safe($category);
+        $transaction->allow_commit();
+        echo "  Done!\n";
+    }
+}
+
+if (($i > 0) && !empty($options['fix'])) {
+    echo "Found and removed {$i} orphaned question categories\n";
+} else if ($i > 0) {
+    echo "Found {$i} orphaned question categories. To fix, run:\n";
+    echo "\$sudo -u www-data /usr/bin/php admin/cli/fix_orphaned_question_categories.php --fix\n";
+} else {
+    echo "No orphaned question categories found.\n";
+}
+
+
+$categories->close();
index acd6060..e43430e 100644 (file)
@@ -286,6 +286,8 @@ if ($options['help']) {
 }
 
 //Print header
+cli_logo();
+echo PHP_EOL;
 echo get_string('cliinstallheader', 'install', $CFG->target_release)."\n";
 
 //Fist select language
index e596403..32bb207 100644 (file)
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="5.4.4" level="required">
+      <RESTRICT function="restrict_php_version_7" message="unsupportedphpversion7" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
           <ON_ERROR message="quizattemptsupgradedmessage" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="2.8" requires="2.2">
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="5.4.4" level="required">
+      <RESTRICT function="restrict_php_version_7" message="unsupportedphpversion7" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
           <ON_ERROR message="quizattemptsupgradedmessage" />
         </FEEDBACK>
       </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_slasharguments" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="slashargumentswarning" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
+      <CUSTOM_CHECK file="lib/upgradelib.php" function="check_database_tables_row_format" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="unsupporteddbtablerowformat" />
+        </FEEDBACK>
+      </CUSTOM_CHECK>
     </CUSTOM_CHECKS>
   </MOODLE>
   <MOODLE version="2.9" requires="2.2">
       <VENDOR name="oracle" version="10.2" />
     </DATABASE>
     <PHP version="5.4.4" level="required">
+      <RESTRICT function="restrict_php_version_7" message="unsupportedphpversion7" />
     </PHP>
     <PCREUNICODE level="optional">
       <FEEDBACK>
index 66cc014..8b4cc51 100644 (file)
@@ -472,7 +472,9 @@ class core_role_define_role_table_advanced extends core_role_capability_table_wi
     }
 
     protected function get_description_field($id) {
-        return print_textarea(true, 10, 50, 50, 10, 'description', $this->role->description, 0, true);
+        return '<textarea class="form-textarea" id="'. s($id) .'" name="description" rows="10" cols="50">' .
+            htmlspecialchars($this->role->description) .
+            '</textarea>';
     }
 
     protected function get_archetype_field($id) {
index dc39e04..82c5969 100644 (file)
@@ -46,6 +46,8 @@ $resultshtml = admin_search_settings_html($query); // case insensitive search on
 echo '<form action="' . $PAGE->url->out(true) . '" method="post" id="adminsettings">';
 echo '<div>';
 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
+// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
+echo prevent_form_autofill_password();
 echo '</div>';
 echo '<fieldset>';
 echo '<div class="clearer"><!-- --></div>';
index 2acbc83..e8f5782 100644 (file)
@@ -77,6 +77,8 @@ if (empty($SITE->fullname)) {
     echo html_writer::input_hidden_params($PAGE->url);
     echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
     echo '<input type="hidden" name="return" value="'.$return.'" />';
+    // HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
+    echo prevent_form_autofill_password();
 
     echo $settingspage->output_html();
 
@@ -119,6 +121,8 @@ if (empty($SITE->fullname)) {
     echo html_writer::input_hidden_params($PAGE->url);
     echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
     echo '<input type="hidden" name="return" value="'.$return.'" />';
+    // HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
+    echo prevent_form_autofill_password();
     echo $OUTPUT->heading($settingspage->visiblename);
 
     echo $settingspage->output_html();
index 26d4f42..29f7981 100644 (file)
@@ -15,7 +15,6 @@ Feature: Edit capabilities
       | user | course | role |
       | teacher1 | C1 | editingteacher |
 
-  @javascript
   Scenario: Default system capabilities modification
     Given I log in as "admin"
     And I set the following system permissions of "Teacher" role:
@@ -30,7 +29,6 @@ Feature: Edit capabilities
     And "moodle/grade:managesharedforms" capability has "Prevent" permission
     And "moodle/course:request" capability has "Prohibit" permission
 
-  @javascript
   Scenario: Course capabilities overrides
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -41,11 +39,11 @@ Feature: Edit capabilities
       | mod/forum:editanypost | Prevent |
       | mod/forum:addquestion | Allow |
     When I set the field "Advanced role override" to "Student (3)"
+    And I press "Go"
     Then "mod/forum:deleteanypost" capability has "Prohibit" permission
     And "mod/forum:editanypost" capability has "Prevent" permission
     And "mod/forum:addquestion" capability has "Allow" permission
 
-  @javascript
   Scenario: Module capabilities overrides
     Given I log in as "teacher1"
     And I follow "Course 1"
@@ -60,6 +58,7 @@ Feature: Edit capabilities
       | mod/forum:editanypost | Prevent |
       | mod/forum:addquestion | Allow |
     When I set the field "Advanced role override" to "Student (3)"
+    And I press "Go"
     Then "mod/forum:deleteanypost" capability has "Prohibit" permission
     And "mod/forum:editanypost" capability has "Prevent" permission
     And "mod/forum:addquestion" capability has "Allow" permission
index 41fac8d..4b4120d 100644 (file)
@@ -13,7 +13,6 @@ Feature: Transform steps arguments
     And I follow "Preferences" in the user menu
     And I follow "Edit profile"
 
-  @javascript
   Scenario: Use nasty strings on steps arguments
     When I set the field "Surname" to "$NASTYSTRING1"
     And I set the field "Description" to "$NASTYSTRING2"
@@ -24,7 +23,6 @@ Feature: Transform steps arguments
     And the field "Surname" matches value "$NASTYSTRING1"
     And the field "City/town" matches value "$NASTYSTRING3"
 
-  @javascript
   Scenario: Use nasty strings on table nodes
     When I set the following fields to these values:
       | Surname | $NASTYSTRING1 |
@@ -36,7 +34,6 @@ Feature: Transform steps arguments
     And the field "Surname" matches value "$NASTYSTRING1"
     And the field "City/town" matches value "$NASTYSTRING3"
 
-  @javascript
   Scenario: Use double quotes
     When I set the following fields to these values:
       | First name | va"lue1 |
@@ -49,7 +46,6 @@ Feature: Transform steps arguments
     And the field "Description" matches value "va\\"lue2"
     And the field "City/town" matches value "va\"lue3"
 
-  @javascript
   Scenario: Nasty strings with other contents
     When I set the field "First name" to "My Firstname $NASTYSTRING1"
     And I set the following fields to these values:
index 3beb4f7..ca1f505 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-debug.js differ
index 8b04092..7e1baef 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search-min.js differ
index 3beb4f7..ca1f505 100644 (file)
Binary files a/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js and b/admin/tool/capability/yui/build/moodle-tool_capability-search/moodle-tool_capability-search.js differ
index 6fbaf67..785263a 100644 (file)
@@ -81,7 +81,6 @@ SEARCH.prototype = {
         div.append(label).append(this.input);
 
         this.select.insert(div, 'before');
-        this.select.one('option').setStyle('display', 'none');
 
         this.input.on('keyup', this.typed, this);
         this.select.on('change', this.validate, this);
index 18e8a09..a27de5f 100644 (file)
@@ -53,7 +53,7 @@ function tool_generator_pluginfile($course, $cm, $context, $filearea, $args, $fo
     $file = $fs->get_file($context->id, 'tool_generator', $filearea, $args[0], '/', $args[1]);
 
     // Send the file, always forcing download, we don't want options.
-    session_get_instance()->write_close();
+    \core\session\manager::write_close();
     send_stored_file($file, 0, 0, true);
 }
 
index d3eee58..bf43258 100644 (file)
@@ -128,7 +128,7 @@ class controller {
             event\langpack_removed::event_with_langcode($lang)->trigger();
             return true;
         } else {    // Nothing deleted, possibly due to permission error.
-            $this->errors[] = 'An error has occurred, language pack is not completely uninstalled, please check file permissions';
+            $this->errors[] = get_string('langpacknotremoved', 'tool_langimport', $lang);
             return false;
         }
     }
index b49dbd3..c1ee2fb 100644 (file)
@@ -37,11 +37,11 @@ if (empty($CFG->langotherroot)) {
     throw new moodle_exception('missingcfglangotherroot', 'tool_langimport');
 }
 
-$mode          = optional_param('mode', 0, PARAM_INT);              // action
-$pack          = optional_param_array('pack', array(), PARAM_SAFEDIR);    // pack to install
-$uninstalllang = optional_param('uninstalllang', '', PARAM_LANG);   // installed pack to uninstall
-$confirm       = optional_param('confirm', 0, PARAM_BOOL);          // uninstallation confirmation
-$purgecaches   = optional_param('purgecaches', false, PARAM_BOOL);  // explicit caches reset
+$mode               = optional_param('mode', 0, PARAM_INT);              // action
+$pack               = optional_param_array('pack', array(), PARAM_SAFEDIR);    // pack to install
+$uninstalllang      = optional_param_array('uninstalllang', array(), PARAM_LANG);// installed pack to uninstall
+$confirmtounistall  = optional_param('confirmtouninstall', '', PARAM_ALPHAEXT);  // uninstallation confirmation
+$purgecaches        = optional_param('purgecaches', false, PARAM_BOOL);  // explicit caches reset
 
 if ($purgecaches) {
     require_sesskey();
@@ -70,21 +70,30 @@ if (($mode == INSTALLATION_OF_SELECTED_LANG) and confirm_sesskey() and !empty($p
     $controller->install_languagepacks($pack);
 }
 
-if ($mode == DELETION_OF_SELECTED_LANG and !empty($uninstalllang)) {
-    if ($uninstalllang == 'en') {
+if ($mode == DELETION_OF_SELECTED_LANG and (!empty($uninstalllang) or !empty($confirmtounistall))) {
+    // Actually deleting languages, languages to delete are passed as GET parameter as string
+    // ...need to populate them to array.
+    if (empty($uninstalllang)) {
+        $uninstalllang = explode('-', $confirmtounistall);
+    }
+
+    if (in_array('en', $uninstalllang)) {
         // TODO.
-        $controller->errors[] = 'English language pack can not be uninstalled';
+        $controller->errors[] = get_string('noenglishuninstall', 'tool_langimport');
 
-    } else if (!$confirm and confirm_sesskey()) {
+    } else if (empty($confirmtounistall) and confirm_sesskey()) { // User chose langs to be deleted, show confirmation.
         echo $OUTPUT->header();
-        echo $OUTPUT->confirm(get_string('uninstallconfirm', 'tool_langimport', $uninstalllang),
-                     'index.php?mode='.DELETION_OF_SELECTED_LANG.'&uninstalllang='.$uninstalllang.'&confirm=1',
+        echo $OUTPUT->confirm(get_string('uninstallconfirm', 'tool_langimport', implode(', ', $uninstalllang)),
+                     'index.php?mode='.DELETION_OF_SELECTED_LANG.'&confirmtouninstall='.implode('-', $uninstalllang),
                      'index.php');
         echo $OUTPUT->footer();
         die;
 
-    } else if (confirm_sesskey()) {
-        $controller->uninstall_language($uninstalllang);
+    } else if (confirm_sesskey()) {   // Deleting languages.
+        foreach ($uninstalllang as $ulang) {
+            $controller->uninstall_language($ulang);
+        }
+
     }
 }
 
@@ -158,10 +167,13 @@ echo html_writer::start_tag('form', array('id' => 'uninstallform', 'action' => $
 echo html_writer::start_tag('fieldset');
 echo html_writer::label(get_string('installedlangs', 'tool_langimport'), 'menuuninstalllang');
 echo html_writer::empty_tag('br');
-echo html_writer::select($installedlangs, 'uninstalllang', '', false, array('size' => 15));
+echo html_writer::select($installedlangs, 'uninstalllang[]', '', false, array('size' => 15, 'multiple' => 'multiple'));
 echo html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
 echo html_writer::empty_tag('br');
-echo html_writer::empty_tag('input', array('type' => 'submit', 'value' => get_string('uninstall', 'tool_langimport')));
+echo html_writer::empty_tag('input', array('id' => 'languninstallbutton',
+                                         'type' => 'submit',
+                                         'value' => get_string('uninstall', 'tool_langimport'))
+                            );
 echo html_writer::end_tag('fieldset');
 echo html_writer::end_tag('form');
 if ($remote) {
@@ -198,5 +210,13 @@ if (!empty($options)) {
 echo html_writer::end_tag('tr');
 echo html_writer::end_tag('table');
 echo $OUTPUT->box_end();
+
+$uninstallurl = new moodle_url('/admin/tool/langimport/index.php');
+$PAGE->requires->strings_for_js(array('uninstallconfirm', 'uninstall', 'selectlangs', 'noenglishuninstall'),
+                                'tool_langimport');
+$PAGE->requires->yui_module('moodle-core-languninstallconfirm',
+                            'Y.M.core.languninstallconfirm.init',
+                             array(array('uninstallUrl' => $uninstallurl->out()))
+                            );
 echo $OUTPUT->footer();
 die();
index fba12f9..65a752d 100644 (file)
@@ -30,6 +30,7 @@ $string['langimportdisabled'] = 'Language import feature has been disabled. You
 $string['langpackinstalled'] = 'Language pack \'{$a}\' was successfully installed';
 $string['langpackinstalledevent'] = 'Language pack installed';
 $string['langpackremoved'] = 'Language pack \'{$a}\' was uninstalled';
+$string['langpacknotremoved'] = 'An error has occurred, language pack \'{$a}\' is not completely uninstalled, please check file permissions';
 $string['langpackremovedevent'] = 'Language pack uninstalled';
 $string['langpackupdateskipped'] = 'Update of \'{$a}\' language pack skipped';
 $string['langpackuptodate'] = 'Language pack \'{$a}\' is up-to-date';
@@ -38,11 +39,12 @@ $string['langpackupdatedevent'] = 'Language pack updated';
 $string['langupdatecomplete'] = 'Language pack update completed';
 $string['missingcfglangotherroot'] = 'Missing configuration value $CFG->langotherroot';
 $string['missinglangparent'] = 'Missing parent language <em>{$a->parent}</em> of <em>{$a->lang}</em>.';
+$string['noenglishuninstall'] = 'English language pack can not be uninstalled';
 $string['nolangupdateneeded'] = 'All your language packs are up to date, no update is needed';
 $string['pluginname'] = 'Language packs';
 $string['purgestringcaches'] = 'Purge string caches';
 $string['remotelangnotavailable'] = 'Because Moodle cannot connect to download.moodle.org, it is not possible for language packs to be installed automatically. Please download the appropriate ZIP file(s) from <a href="https://download.moodle.org/langpack/">download.moodle.org/langpack</a>, copy them to your {$a} directory and unzip them manually.';
-$string['uninstall'] = 'Uninstall selected language pack';
-$string['uninstallconfirm'] = 'You are about to completely uninstall language pack {$a}, are you sure?';
+$string['selectlangs'] = 'Select languages to unistall!';
+$string['uninstall'] = 'Uninstall selected language packs';
+$string['uninstallconfirm'] = 'You are about to completely uninstall these language packs: <strong>{$a}</strong>. Are you sure?';
 $string['updatelangs'] = 'Update all installed language packs';
-
index 24522d9..7aeed67 100644 (file)
@@ -28,7 +28,7 @@ $tasks = array(
     array(
         'classname' => '\logstore_legacy\task\cleanup_task',
         'blocking' => 0,
-        'minute' => '*',
+        'minute' => 'R',
         'hour' => '5',
         'day' => '*',
         'dayofweek' => '*',
index 0cf0cdd..e1917a7 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2015051100; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2015070700; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires = 2015050500; // Requires this Moodle version.
 $plugin->component = 'logstore_legacy'; // Full name of the plugin (used for diagnostics).
index f51c812..fa6cc9d 100644 (file)
@@ -28,7 +28,7 @@ $tasks = array(
     array(
         'classname' => '\logstore_standard\task\cleanup_task',
         'blocking' => 0,
-        'minute' => '*',
+        'minute' => 'R',
         'hour' => '4',
         'day' => '*',
         'dayofweek' => '*',
index b164b2a..3d6cb31 100644 (file)
@@ -24,6 +24,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version = 2015051100; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->version = 2015070700; // The current plugin version (Date: YYYYMMDDXX).
 $plugin->requires = 2015050500; // Requires this Moodle version.
 $plugin->component = 'logstore_standard'; // Full name of the plugin (used for diagnostics).
index b565ae1..482d39e 100644 (file)
@@ -35,7 +35,7 @@ navigation_node::override_active_url(new moodle_url('/admin/tool/phpunit/index.p
 admin_externalpage_setup('toolphpunitwebrunner');
 
 if (!$CFG->debugdeveloper) {
-    error('Not available on production sites, sorry.');
+    print_error('notlocalisederrormessage', 'error', '', null, 'Not available on production sites, sorry.');
 }
 
 core_php_time_limit::raise(60*30);
index 38a8f06..f4aa617 100644 (file)
@@ -63,6 +63,8 @@ echo '<form action="upgradesettings.php" method="post" id="adminsettings">';
 echo '<div>';
 echo '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
 echo '<input type="hidden" name="return" value="'.$return.'" />';
+// HACK to prevent browsers from automatically inserting the user's password into the wrong fields.
+echo prevent_form_autofill_password();
 echo '<fieldset>';
 echo '<div class="clearer"><!-- --></div>';
 echo $newsettingshtml;
index 9121c6e..147ef60 100644 (file)
@@ -58,6 +58,11 @@ class auth_plugin_db extends auth_plugin_base {
     function user_login($username, $password) {
         global $CFG, $DB;
 
+        if ($this->is_configured() === false) {
+            debugging(get_string('auth_notconfigured', 'auth', $this->authtype));
+            return false;
+        }
+
         $extusername = core_text::convert($username, 'utf-8', $this->config->extencoding);
         $extpassword = core_text::convert($password, 'utf-8', $this->config->extencoding);
 
@@ -105,7 +110,7 @@ class auth_plugin_db extends auth_plugin_base {
 
             $authdb = $this->db_init();
 
-            $rs = $authdb->Execute("SELECT {$this->config->fieldpass} AS userpass
+            $rs = $authdb->Execute("SELECT {$this->config->fieldpass}
                                       FROM {$this->config->table}
                                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'");
             if (!$rs) {
@@ -120,7 +125,7 @@ class auth_plugin_db extends auth_plugin_base {
             }
 
             $fields = array_change_key_case($rs->fields, CASE_LOWER);
-            $fromdb = $fields['userpass'];
+            $fromdb = $fields[strtolower($this->config->fieldpass)];
             $rs->Close();
             $authdb->Close();
 
@@ -144,8 +149,13 @@ class auth_plugin_db extends auth_plugin_base {
      * Connect to external database.
      *
      * @return ADOConnection
+     * @throws moodle_exception
      */
     function db_init() {
+        if ($this->is_configured() === false) {
+            throw new moodle_exception('auth_dbcantconnect', 'auth_db');
+        }
+
         // Connect to the external database (forcing new connection).
         $authdb = ADONewConnection($this->config->type);
         if (!empty($this->config->debugauthdb)) {
@@ -207,18 +217,21 @@ class auth_plugin_db extends auth_plugin_base {
         if ($selectfields) {
             $select = array();
             foreach ($selectfields as $localname=>$externalname) {
-                $select[] = "$externalname AS $localname";
+                $select[] = "$externalname";
             }
             $select = implode(', ', $select);
             $sql = "SELECT $select
                       FROM {$this->config->table}
                      WHERE {$this->config->fielduser} = '".$this->ext_addslashes($extusername)."'";
+
             if ($rs = $authdb->Execute($sql)) {
                 if (!$rs->EOF) {
-                    $fields_obj = $rs->FetchObj();
-                    $fields_obj = (object)array_change_key_case((array)$fields_obj , CASE_LOWER);
-                    foreach ($selectfields as $localname=>$externalname) {
-                        $result[$localname] = core_text::convert($fields_obj->{strtolower($localname)}, $this->config->extencoding, 'utf-8');
+                    $fields = $rs->FetchRow();
+                    // Convert the associative array to an array of its values so we don't have to worry about the case of its keys.
+                    $fields = array_values($fields);
+                    foreach (array_keys($selectfields) as $index => $localname) {
+                        $value = $fields[$index];
+                        $result[$localname] = core_text::convert($value, $this->config->extencoding, 'utf-8');
                      }
                  }
                  $rs->Close();
@@ -477,15 +490,15 @@ class auth_plugin_db extends auth_plugin_base {
         $authdb = $this->db_init();
 
         // Fetch userlist.
-        $rs = $authdb->Execute("SELECT {$this->config->fielduser} AS username
+        $rs = $authdb->Execute("SELECT {$this->config->fielduser}
                                   FROM {$this->config->table} ");
 
         if (!$rs) {
             print_error('auth_dbcantconnect','auth_db');
         } else if (!$rs->EOF) {
             while ($rec = $rs->FetchRow()) {
-                $rec = (object)array_change_key_case((array)$rec , CASE_LOWER);
-                array_push($result, $rec->username);
+                $rec = array_change_key_case((array)$rec, CASE_LOWER);
+                array_push($result, $rec[strtolower($this->config->fielduser)]);
             }
         }
 
@@ -661,6 +674,18 @@ class auth_plugin_db extends auth_plugin_base {
         return ($this->config->passtype === 'internal');
     }
 
+    /**
+     * Returns false if this plugin is enabled but not configured.
+     *
+     * @return bool
+     */
+    public function is_configured() {
+        if (!empty($this->config->type)) {
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Indicates if moodle should automatically update internal user
      * records with data from external sources using the information
index 5e20dbf..96d9bba 100644 (file)
@@ -534,6 +534,7 @@ class auth_plugin_ldap extends auth_plugin_base {
      *
      * @param object $user new user object
      * @param boolean $notify print notice with link and terminate
+     * @return boolean success
      */
     function user_signup($user, $notify=true) {
         global $CFG, $DB, $PAGE, $OUTPUT;
@@ -889,7 +890,7 @@ class auth_plugin_ldap extends auth_plugin_base {
 
                 foreach ($users as $user) {
                     echo "\t"; print_string('auth_dbupdatinguser', 'auth_db', array('name'=>$user->username, 'id'=>$user->id));
-                    if (!$this->update_user_record($user->username, $updatekeys)) {
+                    if (!$this->update_user_record($user->username, $updatekeys, true)) {
                         echo ' - '.get_string('skipped');
                     }
                     echo "\n";
@@ -987,8 +988,11 @@ class auth_plugin_ldap extends auth_plugin_base {
      *
      * @param string $username username
      * @param boolean $updatekeys true to update the local record with the external LDAP values.
+     * @param bool $triggerevent set false if user_updated event should not be triggered.
+     *             This will not affect user_password_updated event triggering.
+     * @return stdClass|bool updated user record or false if there is no new info to update.
      */
-    function update_user_record($username, $updatekeys = false) {
+    function update_user_record($username, $updatekeys = false, $triggerevent = false) {
         global $CFG, $DB;
 
         // Just in case check text case
@@ -1030,7 +1034,7 @@ class auth_plugin_ldap extends auth_plugin_base {
                         }
                     }
                 }
-                user_update_user($newuser, false, false);
+                user_update_user($newuser, false, $triggerevent);
             }
         } else {
             return false;
@@ -1638,7 +1642,7 @@ class auth_plugin_ldap extends auth_plugin_base {
 
         if (($_SERVER['REQUEST_METHOD'] === 'GET'         // Only on initial GET of loginpage
              || ($_SERVER['REQUEST_METHOD'] === 'POST'
-                 && (get_referer() != strip_querystring(qualified_me()))))
+                 && (get_local_referer() != strip_querystring(qualified_me()))))
                                                           // Or when POSTed from another place
                                                           // See MDL-14071
             && !empty($this->config->ntlmsso_enabled)     // SSO enabled
@@ -1649,13 +1653,15 @@ class auth_plugin_ldap extends auth_plugin_base {
 
             // First, let's remember where we were trying to get to before we got here
             if (empty($SESSION->wantsurl)) {
-                $SESSION->wantsurl = (array_key_exists('HTTP_REFERER', $_SERVER) &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->wwwroot &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->wwwroot.'/' &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/' &&
-                                      $_SERVER['HTTP_REFERER'] != $CFG->httpswwwroot.'/login/index.php' &&
-                                      clean_param($_SERVER['HTTP_REFERER'], PARAM_LOCALURL) != '')
-                    ? $_SERVER['HTTP_REFERER'] : NULL;
+                $SESSION->wantsurl = null;
+                $referer = get_local_referer(false);
+                if ($referer &&
+                        $referer != $CFG->wwwroot &&
+                        $referer != $CFG->wwwroot . '/' &&
+                        $referer != $CFG->httpswwwroot . '/login/' &&
+                        $referer != $CFG->httpswwwroot . '/login/index.php') {
+                    $SESSION->wantsurl = $referer;
+                }
             }
 
             // Now start the whole NTLM machinery.
@@ -1680,7 +1686,7 @@ class auth_plugin_ldap extends auth_plugin_base {
         // we don't want to use at all. As we can't get rid of it, just point
         // $SESSION->wantsurl to $CFG->wwwroot (after all, we came from there).
         if (empty($SESSION->wantsurl)
-            && (get_referer() == $CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php')) {
+            && (get_local_referer() == $CFG->httpswwwroot.'/auth/ldap/ntlmsso_finish.php')) {
 
             $SESSION->wantsurl = $CFG->wwwroot;
         }
index 59a1f9d..ba82adc 100644 (file)
@@ -136,9 +136,25 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $auth = get_auth_plugin('ldap');
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 5 users created with 2 users having roles.
+        $this->assertCount(7, $events);
+        foreach ($events as $index => $event) {
+            $usercreatedindex = array(0, 2, 4, 5, 6);
+            $roleassignedindex = array (1, 3);
+            if (in_array($index, $usercreatedindex)) {
+                $this->assertInstanceOf('\core\event\user_created', $event);
+            }
+            if (in_array($index, $roleassignedindex)) {
+                $this->assertInstanceOf('\core\event\role_assigned', $event);
+            }
+        }
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(2, $DB->count_records('role_assignments'));
         $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$creatorrole->id)));
@@ -150,9 +166,15 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $this->delete_ldap_user($connection, $topdn, 1);
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, no new event.
+        $this->assertCount(0, $events);
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
         $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
@@ -166,9 +188,17 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $auth = get_auth_plugin('ldap');
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 1 user got updated.
+        $this->assertCount(1, $events);
+        $event = reset($events);
+        $this->assertInstanceOf('\core\event\user_updated', $event);
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(0, $DB->count_records('user', array('auth'=>'nologin', 'username'=>'username1')));
         $this->assertEquals(1, $DB->count_records('user', array('auth'=>'ldap', 'suspended'=>'1', 'username'=>'username1')));
@@ -179,9 +209,17 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $this->create_ldap_user($connection, $topdn, 1);
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 1 user got updated.
+        $this->assertCount(1, $events);
+        $event = reset($events);
+        $this->assertInstanceOf('\core\event\user_updated', $event);
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
         $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
@@ -191,9 +229,17 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $DB->set_field('user', 'auth', 'nologin', array('username'=>'username1'));
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 1 user got updated.
+        $this->assertCount(1, $events);
+        $event = reset($events);
+        $this->assertInstanceOf('\core\event\user_updated', $event);
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
         $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
@@ -208,9 +254,19 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $this->delete_ldap_user($connection, $topdn, 1);
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 2 events role_unassigned and user_deleted.
+        $this->assertCount(2, $events);
+        $event = array_pop($events);
+        $this->assertInstanceOf('\core\event\user_deleted', $event);
+        $event = array_pop($events);
+        $this->assertInstanceOf('\core\event\role_unassigned', $event);
+
         $this->assertEquals(5, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(0, $DB->count_records('user', array('username'=>'username1')));
         $this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
@@ -221,9 +277,19 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $this->create_ldap_user($connection, $topdn, 1);
 
         ob_start();
+        $sink = $this->redirectEvents();
         $auth->sync_users(true);
+        $events = $sink->get_events();
+        $sink->close();
         ob_end_clean();
 
+        // Check events, 2 events role_assigned and user_created.
+        $this->assertCount(2, $events);
+        $event = array_pop($events);
+        $this->assertInstanceOf('\core\event\role_assigned', $event);
+        $event = array_pop($events);
+        $this->assertInstanceOf('\core\event\user_created', $event);
+
         $this->assertEquals(6, $DB->count_records('user', array('auth'=>'ldap')));
         $this->assertEquals(1, $DB->count_records('user', array('username'=>'username1')));
         $this->assertEquals(0, $DB->count_records('user', array('suspended'=>1)));
diff --git a/auth/ldap/upgrade.txt b/auth/ldap/upgrade.txt
new file mode 100644 (file)
index 0000000..8ac9073
--- /dev/null
@@ -0,0 +1,4 @@
+This files describes API changes in the auth_ldap code.
+=== 2.9.1 ===
+* auth_plugin_ldap::update_user_record() accepts an additional (optional) param
+  to trigger update event.
index ef38337..4e5c92a 100644 (file)
@@ -10,7 +10,7 @@
 
     // Support for WAYFless URLs.
     $target = optional_param('target', '', PARAM_LOCALURL);
-    if (!empty($target)) {
+    if (!empty($target) && empty($SESSION->wantsurl)) {
         $SESSION->wantsurl = $target;
     }
 
index afd1d40..05d82fa 100644 (file)
@@ -713,11 +713,21 @@ abstract class info {
      * that we can guarantee does not happen from within building the modinfo
      * object.
      *
-     * @param string $info Info string
+     * @param \renderable|string $inforenderable Info string or renderable
      * @param int|\stdClass $courseorid
      * @return string Correctly formatted info string
      */
-    public static function format_info($info, $courseorid) {
+    public static function format_info($inforenderable, $courseorid) {
+        global $PAGE;
+
+        // Use renderer if required.
+        if (is_string($inforenderable)) {
+            $info = $inforenderable;
+        } else {
+            $renderer = $PAGE->get_renderer('core', 'availability');
+            $info = $renderer->render($inforenderable);
+        }
+
         // Don't waste time if there are no special tags.
         if (strpos($info, '<AVAILABILITY_') === false) {
             return $info;
diff --git a/availability/classes/multiple_messages.php b/availability/classes/multiple_messages.php
new file mode 100644 (file)
index 0000000..0ba70a7
--- /dev/null
@@ -0,0 +1,70 @@
+<?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/>.
+
+/**
+ * Represents multiple availability messages.
+ *
+ * These are messages like 'Not available until <date>'. This class includes
+ * multiple messages so that they can be rendered into a combined display, e.g.
+ * using bulleted lists.
+ *
+ * The tree structure of this object matches that of the availability
+ * restrictions.
+ *
+ * @package core_availability
+ * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Represents multiple availability messages.
+ *
+ * These are messages like 'Not available until <date>'. This class includes
+ * multiple messages so that they can be rendered into a combined display, e.g.
+ * using bulleted lists.
+ *
+ * The tree structure of this object matches that of the availability
+ * restrictions.
+ *
+ * @package core_availability
+ * @copyright 2015 Andrew Nicols <andrew@nicols.co.uk>
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_availability_multiple_messages implements renderable {
+    /** @var bool True if this object represents the root of the tree */
+    public $root;
+    /** @var bool True if items use the AND operator (false = OR) */
+    public $andoperator;
+    /** @var bool True if this part of the tree is marked 'hide entirely' for non-matching users */
+    public $treehidden;
+    /** @var array Array of child items (may be string or this type) */
+    public $items;
+
+    /**
+     * Constructor.
+     *
+     * @param bool $root True if this object represents the root of the tree
+     * @param bool $andoperator True if items use the AND operator (false = OR)
+     * @param bool $treehidden True if this part of the tree is marked 'hide entirely' for non-matching users
+     * @param array $items Array of items (may be string or this type)
+     */
+    public function __construct($root, $andoperator, $treehidden, array $items) {
+        $this->root = $root;
+        $this->andoperator = $andoperator;
+        $this->treehidden = $treehidden;
+        $this->items = $items;
+    }
+}
index 8849320..ef15c58 100644 (file)
@@ -471,11 +471,10 @@ class tree extends tree_node {
      * @param result $result Result object if this is a student display, else null
      * @param bool $root True if this is the root item
      * @param bool $hidden Staff display; true if this tree has show=false (from parent)
+     * @return string|renderable Information to render
      */
     protected function get_full_information_recursive(
             $not, info $info, result $result = null, $root, $hidden = false) {
-        global $PAGE;
-
         // Get list of children - either full list, or those which are shown.
         $children = $this->children;
         $staff = true;
@@ -550,8 +549,7 @@ class tree extends tree_node {
         }
 
         // Format output for display.
-        $renderer = $PAGE->get_renderer('core', 'availability');
-        return $renderer->multiple_messages($root, $andoperator, $treehidden, $items);
+        return new \core_availability_multiple_messages($root, $andoperator, $treehidden, $items);
     }
 
     /**
diff --git a/availability/condition/completion/tests/behat/conditional_bug.feature b/availability/condition/completion/tests/behat/conditional_bug.feature
new file mode 100644 (file)
index 0000000..b7f6fec
--- /dev/null
@@ -0,0 +1,60 @@
+@availability @availability_completion
+Feature: Confirm that conditions on completion no longer cause a bug
+  In order to use completion conditions
+  As a teacher
+  I need it to not break when I set up certain conditions on some modules
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | format |
+      | Course 1 | C1        | topics |
+    And the following "users" exist:
+      | username |
+      | teacher1 |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | C1     | editingteacher |
+    And the following config values are set as admin:
+      | enableavailability | 1 |
+      | enablecompletion   | 1 |
+
+  @javascript
+  Scenario: Multiple completion conditions on glossary
+    # Set up course.
+    Given I log in as "teacher1"
+    And I follow "Course 1"
+    And I navigate to "Edit settings" node in "Course administration"
+    And I expand all fieldsets
+    And I set the field "Enable completion tracking" to "Yes"
+    And I press "Save and display"
+    And I turn editing mode on
+
+    # Add a couple of Pages with manual completion.
+    And I add a "Page" to section "1" and I fill the form with:
+      | Name         | Page1 |
+      | Page content | x     |
+    And I add a "Page" to section "1" and I fill the form with:
+      | Name         | Page2 |
+      | Page content | x     |
+
+    # Add a Glossary.
+    When I add a "Glossary" to section "1"
+    And I set the following fields to these values:
+      | Name | TestGlossary |
+    And I expand all fieldsets
+
+    # Add restrictions to the previous Pages being complete.
+    And I press "Add restriction..."
+    And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
+    And I set the field "Activity or resource" to "Page1"
+    And I press "Add restriction..."
+    And I click on "Activity completion" "button" in the "Add restriction..." "dialogue"
+    And I set the field with xpath "//div[@class='availability-item'][preceding-sibling::div]//select[@name='cm']" to "Page2"
+    And I press "Save and return to course"
+    And I should see "Not available unless:" in the ".activity.glossary" "css_element"
+    And I should see "The activity Page1 is marked complete" in the ".activity.glossary" "css_element"
+    And I should see "The activity Page2 is marked complete" in the ".activity.glossary" "css_element"
+    And I follow "TestGlossary"
+
+    # Behat will automatically check there is no error on this page.
+    Then I should see "TestGlossary"
index 8a4bd5b..440683c 100644 (file)
@@ -228,7 +228,9 @@ class condition extends \core_availability\condition {
                         WHERE
                             gi.courseid = ?', array($userid, $courseid));
                 foreach ($rs as $record) {
-                    if (is_null($record->finalgrade)) {
+                    // This function produces division by zero error warnings when rawgrademax and rawgrademin
+                    // are equal. Below change does not affect function behavior, just avoids the warning.
+                    if (is_null($record->finalgrade) || $record->rawgrademax == $record->rawgrademin) {
                         // No grade = false.
                         $cachedgrades[$record->id] = false;
                     } else {
@@ -249,7 +251,9 @@ class condition extends \core_availability\condition {
                 // Just get current grade.
                 $record = $DB->get_record('grade_grades', array(
                     'userid' => $userid, 'itemid' => $gradeitemid));
-                if ($record && !is_null($record->finalgrade)) {
+                // This function produces division by zero error warnings when rawgrademax and rawgrademin
+                // are equal. Below change does not affect function behavior, just avoids the warning.
+                if ($record && !is_null($record->finalgrade) && $record->rawgrademax != $record->rawgrademin) {
                     $score = (($record->finalgrade - $record->rawgrademin) * 100) /
                         ($record->rawgrademax - $record->rawgrademin);
                 } else {
index b098e81..20fff45 100644 (file)
@@ -40,22 +40,25 @@ class core_availability_renderer extends plugin_renderer_base {
      *
      * This function will not be called unless there are at least two messages.
      *
-     * @param bool $root True if this is a root-level list for an activity
-     * @param bool $andoperator True if the messages are being combined as AND
-     * @param bool $roothidden True if the root level should use 'hidden' message
-     * @param array $messages Messages to render
+     * @param core_availability_multiple_messages $renderable Multiple messages
      * @return string Combined HTML
      */
-    public function multiple_messages($root, $andoperator, $roothidden, array $messages) {
+    public function render_core_availability_multiple_messages(
+            core_availability_multiple_messages $renderable) {
         // Get initial message.
-        $out = get_string('list_' . ($root ? 'root_' : '') .
-                ($andoperator ? 'and' : 'or') . ($roothidden ? '_hidden' : ''),
+        $out = get_string('list_' . ($renderable->root ? 'root_' : '') .
+                ($renderable->andoperator ? 'and' : 'or') . ($renderable->treehidden ? '_hidden' : ''),
                 'availability');
 
         // Make the list.
         $out .= html_writer::start_tag('ul');
-        foreach ($messages as $message) {
-            $out .= html_writer::tag('li', $message);
+        foreach ($renderable->items as $item) {
+            if (is_string($item)) {
+                $str = $item;
+            } else {
+                $str = $this->render($item);
+            }
+            $out .= html_writer::tag('li', $str);
         }
         $out .= html_writer::end_tag('ul');
         return $out;
index 74c3a63..de05104 100644 (file)
@@ -49,7 +49,11 @@ class core_availability_component_testcase extends advanced_testcase {
         // fail, but it's obvious when running test at least.
         $pluginmanager = core_plugin_manager::instance();
         $list = $pluginmanager->get_enabled_plugins('availability');
-        $this->assertEquals(array('completion', 'date', 'grade', 'group', 'grouping', 'profile'),
-                array_keys($list));
+        $this->assertArrayHasKey('completion', $list);
+        $this->assertArrayHasKey('date', $list);
+        $this->assertArrayHasKey('grade', $list);
+        $this->assertArrayHasKey('group', $list);
+        $this->assertArrayHasKey('grouping', $list);
+        $this->assertArrayHasKey('profile', $list);
     }
 }
index b19d62c..ebabc08 100644 (file)
@@ -341,9 +341,15 @@ class tree_testcase extends \advanced_testcase {
      * @param int $userid User id
      */
     protected function get_available_results($structure, \core_availability\info $info, $userid) {
+        global $PAGE;
         $tree = new tree($structure);
         $result = $tree->check_available(false, $info, true, $userid);
-        return array($result->is_available(), $tree->get_result_information($info, $result));
+        $information = $tree->get_result_information($info, $result);
+        if (!is_string($information)) {
+            $renderer = $PAGE->get_renderer('core', 'availability');
+            $information = $renderer->render($information);
+        }
+        return array($result->is_available(), $information);
     }
 
     /**
@@ -389,6 +395,8 @@ class tree_testcase extends \advanced_testcase {
      * Tests the get_full_information() function.
      */
     public function test_get_full_information() {
+        global $PAGE;
+        $renderer = $PAGE->get_renderer('core', 'availability');
         // Setup.
         $info = new \core_availability\mock_info();
 
@@ -417,7 +425,7 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => 3)));
         $tree = new tree($structure);
         $this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Test intro messages before list. First, OR message.
         $structure->c = array(
@@ -426,13 +434,13 @@ class tree_testcase extends \advanced_testcase {
         );
         $tree = new tree($structure);
         $this->assertRegExp('~Not available unless any of:.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Now, OR message when not shown.
         $structure->show = false;
         $tree = new tree($structure);
         $this->assertRegExp('~hidden.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // AND message.
         $structure->op = '&';
@@ -440,11 +448,11 @@ class tree_testcase extends \advanced_testcase {
         $structure->showc = array(false, false);
         $tree = new tree($structure);
         $this->assertRegExp('~Not available unless:.*<ul>~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on items.
         $this->assertRegExp('~1.*hidden.*2.*hidden~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on child tree and items.
         $structure->c[1] = tree::get_nested_json(array(
@@ -452,11 +460,11 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => '3'))), tree::OP_AND);
         $tree = new tree($structure);
         $this->assertRegExp('~1.*hidden.*All of \(hidden.*2.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
         $structure->c[1]->op = '|';
         $tree = new tree($structure);
         $this->assertRegExp('~1.*hidden.*Any of \(hidden.*2.*3~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Hidden markers on single-item display, AND and OR.
         $structure->showc = array(false);
@@ -480,7 +488,7 @@ class tree_testcase extends \advanced_testcase {
                 self::mock(array('m' => '2'))), tree::OP_AND);
         $tree = new tree($structure);
         $this->assertRegExp('~Not available \(hidden.*1.*2~',
-                $tree->get_full_information($info));
+                $renderer->render($tree->get_full_information($info)));
 
         // Single item tree containing single item.
         unset($structure->c[0]->c[1]);
index cd124e5..e270f9e 100644 (file)
@@ -110,7 +110,7 @@ class backup_controller extends base_controller {
 
         // By default there is no progress reporter. Interfaces that wish to
         // display progress must set it.
-        $this->progress = new \core\progress\null();
+        $this->progress = new \core\progress\none();
 
         // Instantiate the output_controller singleton and active it if interactive and inmediate
         $oc = output_controller::get_instance();
index 3992649..8c72c58 100644 (file)
@@ -114,7 +114,7 @@ class restore_controller extends base_controller {
         if ($progress) {
             $this->progress = $progress;
         } else {
-            $this->progress = new \core\progress\null();
+            $this->progress = new \core\progress\none();
         }
         $this->progress->start_progress('Constructing restore_controller');
 
index f24abce..89c7534 100644 (file)
@@ -939,6 +939,7 @@ class backup_gradebook_structure_step extends backup_structure_step {
     }
 
     protected function define_structure() {
+        global $CFG, $DB;
 
         // are we including user info?
         $userinfo = $this->get_setting_value('users');
@@ -999,6 +1000,13 @@ class backup_gradebook_structure_step extends backup_structure_step {
         $gradebook->add_child($grade_settings);
         $grade_settings->add_child($grade_setting);
 
+        // Add attribute with gradebook calculation freeze date if needed.
+        $gradebookcalculationfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
+        if ($gradebookcalculationfreeze) {
+            $gradebook->add_attributes(array('calculations_freeze'));
+            $gradebook->get_attribute('calculations_freeze')->set_value($gradebookcalculationfreeze);
+        }
+
         // Define sources
 
         //Include manual, category and the course grade item
@@ -1023,7 +1031,18 @@ class backup_gradebook_structure_step extends backup_structure_step {
 
         $letter->set_source_table('grade_letters', array('contextid' => backup::VAR_CONTEXTID));
 
-        $grade_setting->set_source_table('grade_settings', array('courseid' => backup::VAR_COURSEID));
+        // Set the grade settings source, forcing the inclusion of minmaxtouse if not present.
+        $settings = array();
+        $rs = $DB->get_recordset('grade_settings', array('courseid' => $this->get_courseid()));
+        foreach ($rs as $record) {
+            $settings[$record->name] = $record;
+        }
+        $rs->close();
+        if (!isset($settings['minmaxtouse'])) {
+            $settings['minmaxtouse'] = (object) array('name' => 'minmaxtouse', 'value' => $CFG->grade_minmaxtouse);
+        }
+        $grade_setting->set_source_array($settings);
+
 
         // Annotations (both as final as far as they are going to be exported in next steps)
         $grade_item->annotate_ids('scalefinal', 'scaleid'); // Straight as scalefinal because it's > 0
index c9038cf..587015d 100644 (file)
@@ -142,6 +142,18 @@ class restore_gradebook_structure_step extends restore_structure_step {
     }
 
     protected function process_gradebook($data) {
+        // For non-merge restore types:
+        // Unset 'gradebook_calculations_freeze_' in the course and replace with the one from the backup.
+        $target = $this->get_task()->get_target();
+        if ($target == backup::TARGET_CURRENT_DELETING || $target == backup::TARGET_EXISTING_DELETING) {
+            set_config('gradebook_calculations_freeze_' . $this->get_courseid(), null);
+        }
+        if (!empty($data['calculations_freeze'])) {
+            if ($target == backup::TARGET_NEW_COURSE || $target == backup::TARGET_CURRENT_DELETING ||
+                    $target == backup::TARGET_EXISTING_DELETING) {
+                set_config('gradebook_calculations_freeze_' . $this->get_courseid(), $data['calculations_freeze']);
+            }
+        }
     }
 
     protected function process_grade_item($data) {
@@ -323,13 +335,23 @@ class restore_gradebook_structure_step extends restore_structure_step {
 
         $data->courseid = $this->get_courseid();
 
+        $target = $this->get_task()->get_target();
+        if ($data->name == 'minmaxtouse' &&
+                ($target == backup::TARGET_CURRENT_ADDING || $target == backup::TARGET_EXISTING_ADDING)) {
+            // We never restore minmaxtouse during merge.
+            return;
+        }
+
         if (!$DB->record_exists('grade_settings', array('courseid' => $data->courseid, 'name' => $data->name))) {
             $newitemid = $DB->insert_record('grade_settings', $data);
         } else {
             $newitemid = $data->id;
         }
 
-        $this->set_mapping('grade_setting', $oldid, $newitemid);
+        if (!empty($oldid)) {
+            // In rare cases (minmaxtouse), it is possible that there wasn't any ID associated with the setting.
+            $this->set_mapping('grade_setting', $oldid, $newitemid);
+        }
     }
 
     /**
@@ -451,9 +473,104 @@ class restore_gradebook_structure_step extends restore_structure_step {
         }
         $rs->close();
 
+        // Check what to do with the minmaxtouse setting.
+        $this->check_minmaxtouse();
+
+        // Freeze gradebook calculations if needed.
+        $this->gradebook_calculation_freeze();
+
         // Restore marks items as needing update. Update everything now.
         grade_regrade_final_grades($this->get_courseid());
     }
+
+    /**
+     * Freeze gradebook calculation if needed.
+     *
+     * This is similar to various upgrade scripts that check if the freeze is needed.
+     */
+    protected function gradebook_calculation_freeze() {
+        global $CFG;
+        $gradebookcalculationsfreeze = get_config('core', 'gradebook_calculations_freeze_' . $this->get_courseid());
+        preg_match('/(\d{8})/', $this->get_task()->get_info()->moodle_release, $matches);
+        $backupbuild = (int)$matches[1];
+
+        // Extra credits need adjustments only for backups made between 2.8 release (20141110) and the fix release (20150619).
+        if (!$gradebookcalculationsfreeze && $backupbuild >= 20141110 && $backupbuild < 20150619) {
+            require_once($CFG->libdir . '/db/upgradelib.php');
+            upgrade_extra_credit_weightoverride($this->get_courseid());
+        }
+        // Calculated grade items need recalculating for backups made between 2.8 release (20141110) and the fix release (20150627).
+        if (!$gradebookcalculationsfreeze && $backupbuild >= 20141110 && $backupbuild < 20150627) {
+            require_once($CFG->libdir . '/db/upgradelib.php');
+            upgrade_calculated_grade_items($this->get_courseid());
+        }
+    }
+
+    /**
+     * Checks what should happen with the course grade setting minmaxtouse.
+     *
+     * This is related to the upgrade step at the time the setting was added.
+     *
+     * @see MDL-48618
+     * @return void
+     */
+    protected function check_minmaxtouse() {
+        global $CFG, $DB;
+        require_once($CFG->libdir . '/gradelib.php');
+
+        $userinfo = $this->task->get_setting_value('users');
+        $settingname = 'minmaxtouse';
+        $courseid = $this->get_courseid();
+        $minmaxtouse = $DB->get_field('grade_settings', 'value', array('courseid' => $courseid, 'name' => $settingname));
+        $version28start = 2014111000.00;
+        $version28last = 2014111006.05;
+        $version29start = 2015051100.00;
+        $version29last = 2015060400.02;
+
+        $target = $this->get_task()->get_target();
+        if ($minmaxtouse === false &&
+                ($target != backup::TARGET_CURRENT_ADDING && $target != backup::TARGET_EXISTING_ADDING)) {
+            // The setting was not found because this setting did not exist at the time the backup was made.
+            // And we are not restoring as merge, in which case we leave the course as it was.
+            $version = $this->get_task()->get_info()->moodle_version;
+
+            if ($version < $version28start) {
+                // We need to set it to use grade_item, but only if the site-wide setting is different. No need to notice them.
+                if ($CFG->grade_minmaxtouse != GRADE_MIN_MAX_FROM_GRADE_ITEM) {
+                    grade_set_setting($courseid, $settingname, GRADE_MIN_MAX_FROM_GRADE_ITEM);
+                }
+
+            } else if (($version >= $version28start && $version < $version28last) ||
+                    ($version >= $version29start && $version < $version29last)) {
+                // They should be using grade_grade when the course has inconsistencies.
+
+                $sql = "SELECT gi.id
+                          FROM {grade_items} gi
+                          JOIN {grade_grades} gg
+                            ON gg.itemid = gi.id
+                         WHERE gi.courseid = ?
+                           AND (gi.itemtype != ? AND gi.itemtype != ?)
+                           AND (gg.rawgrademax != gi.grademax OR gg.rawgrademin != gi.grademin)";
+
+                // The course can only have inconsistencies when we restore the user info,
+                // we do not need to act on existing grades that were not restored as part of this backup.
+                if ($userinfo && $DB->record_exists_sql($sql, array($courseid, 'course', 'category'))) {
+
+                    // Display the notice as we do during upgrade.
+                    set_config('show_min_max_grades_changed_' . $courseid, 1);
+
+                    if ($CFG->grade_minmaxtouse != GRADE_MIN_MAX_FROM_GRADE_GRADE) {
+                        // We need set the setting as their site-wise setting is not GRADE_MIN_MAX_FROM_GRADE_GRADE.
+                        // If they are using the site-wide grade_grade setting, we only want to notice them.
+                        grade_set_setting($courseid, $settingname, GRADE_MIN_MAX_FROM_GRADE_GRADE);
+                    }
+                }
+
+            } else {
+                // This should never happen because from now on minmaxtouse is always saved in backups.
+            }
+        }
+    }
 }
 
 /**
@@ -1311,6 +1428,15 @@ class restore_process_categories_and_questions extends restore_execution_step {
  * as needed, rebuilding course cache and other friends
  */
 class restore_section_structure_step extends restore_structure_step {
+    /** @var array Cache: Array of id => course format */
+    private static $courseformats = array();
+
+    /**
+     * Resets a static cache of course formats. Required for unit testing.
+     */
+    public static function reset_caches() {
+        self::$courseformats = array();
+    }
 
     protected function define_structure() {
         global $CFG;
@@ -1483,14 +1609,13 @@ class restore_section_structure_step extends restore_structure_step {
 
     public function process_course_format_options($data) {
         global $DB;
-        static $courseformats = array();
         $courseid = $this->get_courseid();
-        if (!array_key_exists($courseid, $courseformats)) {
+        if (!array_key_exists($courseid, self::$courseformats)) {
             // It is safe to have a static cache of course formats because format can not be changed after this point.
-            $courseformats[$courseid] = $DB->get_field('course', 'format', array('id' => $courseid));
+            self::$courseformats[$courseid] = $DB->get_field('course', 'format', array('id' => $courseid));
         }
         $data = (array)$data;
-        if ($courseformats[$courseid] === $data['format']) {
+        if (self::$courseformats[$courseid] === $data['format']) {
             // Import section format options only if both courses (the one that was backed up
             // and the one we are restoring into) have same formats.
             $params = array(
index 5d2d6e0..787db96 100644 (file)
@@ -105,7 +105,7 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
                       'numdaystocomplete' => 2);
         $courseobject->update_section_format_options($data);
 
-        $this->backup_and_restore($course, $course);
+        $this->backup_and_restore($course, $course, backup::TARGET_EXISTING_ADDING);
 
         $sectionoptions = $courseobject->get_format_options(1);
         $this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
@@ -126,37 +126,49 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
 
         $this->resetAfterTest(true);
         $this->setAdminUser();
-        $CFG->enableavailability = true;
-        $CFG->enablecompletion = true;
 
-        // Create a course with some availability data set.
+        // Create a source course using the test_cs2_options format.
         $generator = $this->getDataGenerator();
         $course = $generator->create_course(
             array('format' => 'test_cs2_options', 'numsections' => 3,
                 'enablecompletion' => COMPLETION_ENABLED),
             array('createsections' => true));
+
+        // Create a target course using test_cs_options format.
         $newcourse = $generator->create_course(
             array('format' => 'test_cs_options', 'numsections' => 3,
                 'enablecompletion' => COMPLETION_ENABLED),
             array('createsections' => true));
 
+        // Set section 2 to have both options, and a name.
         $courseobject = format_base::instance($course->id);
         $section = $DB->get_record('course_sections',
-            array('course' => $course->id, 'section' => 1), '*', MUST_EXIST);
-
+            array('course' => $course->id, 'section' => 2), '*', MUST_EXIST);
         $data = array('id' => $section->id,
             'numdaystocomplete' => 2,
-            'secondparameter' => 8);
+            'secondparameter' => 8
+        );
         $courseobject->update_section_format_options($data);
-        // Backup and restore it.
-        $this->backup_and_restore($course, $newcourse);
+        $DB->set_field('course_sections', 'name', 'Frogs', array('id' => $section->id));
+
+        // Backup and restore to the new course using 'add to existing' so it
+        // keeps the current (test_cs_options) format.
+        $this->backup_and_restore($course, $newcourse, backup::TARGET_EXISTING_ADDING);
 
+        // Check that the section contains the options suitable for the new
+        // format and that even the one with the same name as from the old format
+        // has NOT been set.
         $newcourseobject = format_base::instance($newcourse->id);
-        $sectionoptions = $newcourseobject->get_format_options(1);
+        $sectionoptions = $newcourseobject->get_format_options(2);
         $this->assertArrayHasKey('numdaystocomplete', $sectionoptions);
-        $this->assertArrayHasKey('secondparameter', $sectionoptions);
+        $this->assertArrayNotHasKey('secondparameter', $sectionoptions);
         $this->assertEquals(0, $sectionoptions['numdaystocomplete']);
-        $this->assertEquals(0, $sectionoptions['secondparameter']);
+
+        // However, the name should have been changed, as this does not depend
+        // on the format.
+        $modinfo = get_fast_modinfo($newcourse->id);
+        $section = $modinfo->get_section_info(2);
+        $this->assertEquals('Frogs', $section->name);
     }
 
     /**
@@ -164,9 +176,11 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
      *
      * @param stdClass $srccourse Course object to backup
      * @param stdClass $dstcourse Course object to restore into
+     * @param int $target Target course mode (backup::TARGET_xx)
      * @return int ID of newly restored course
      */
-    protected function backup_and_restore($srccourse, $dstcourse = null) {
+    protected function backup_and_restore($srccourse, $dstcourse = null,
+            $target = backup::TARGET_NEW_COURSE) {
         global $USER, $CFG;
 
         // Turn off file logging, otherwise it can't delete the file (Windows).
@@ -190,7 +204,7 @@ class core_backup_moodle2_course_format_testcase extends advanced_testcase {
         }
         $rc = new restore_controller($backupid, $newcourseid,
                 backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
-                backup::TARGET_NEW_COURSE);
+                $target);
 
         $this->assertTrue($rc->execute_precheck());
         $rc->execute_plan();
@@ -226,6 +240,12 @@ class format_test_cs_options extends format_topics {
 class format_test_cs2_options extends format_test_cs_options {
     public function section_format_options($foreditform = false) {
         return array(
+            'numdaystocomplete' => array(
+                 'type' => PARAM_INT,
+                 'label' => 'Test days',
+                 'element_type' => 'text',
+                 'default' => 0,
+             ),
             'secondparameter' => array(
                 'type' => PARAM_INT,
                 'label' => 'Test Parmater',
index bd00c32..880e75d 100644 (file)
@@ -123,7 +123,7 @@ abstract class restore_dbops {
 
         // Set up progress tracking (indeterminate).
         if (!$progress) {
-            $progress = new \core\progress\null();
+            $progress = new \core\progress\none();
         }
         $progress->start_progress('Loading inforef.xml file');
 
@@ -430,7 +430,7 @@ abstract class restore_dbops {
 
         // Set up progress tracking (indeterminate).
         if (!$progress) {
-            $progress = new \core\progress\null();
+            $progress = new \core\progress\none();
         }
         $progress->start_progress('Loading users into temporary table');
 
index 0bb8323..d0e2237 100644 (file)
@@ -185,11 +185,11 @@ class restore_ui extends base_ui {
      * there are long-running tasks even though there is no restore controller
      * in use.
      *
-     * @return \core\progress\null
+     * @return \core\progress\none
      */
     public function get_progress_reporter() {
         if (!$this->progressreporter) {
-            $this->progressreporter = new \core\progress\null();
+            $this->progressreporter = new \core\progress\none();
         }
         return $this->progressreporter;
     }
index 5bb40a1..2917c72 100644 (file)
@@ -144,11 +144,11 @@ abstract class restore_ui_independent_stage {
      * in use. There is a similar function in restore_ui. but that class is not
      * used on some stages.
      *
-     * @return \core\progress\null
+     * @return \core\progress\none
      */
     public function get_progress_reporter() {
         if (!$this->progressreporter) {
-            $this->progressreporter = new \core\progress\null();
+            $this->progressreporter = new \core\progress\none();
         }
         return $this->progressreporter;
     }
index 7548fae..ed0780b 100644 (file)
@@ -48,7 +48,7 @@ if (!in_array($sortby, array('name', 'dateissued'))) {
 }
 
 if ($sorthow != 'ASC' && $sorthow != 'DESC') {
-    $sorthow = 'ACS';
+    $sorthow = 'ASC';
 }
 
 if ($page < 0) {
diff --git a/blocks/blog_tags/tests/behat/blogtag.feature b/blocks/blog_tags/tests/behat/blogtag.feature
new file mode 100644 (file)
index 0000000..88bf5b9
--- /dev/null
@@ -0,0 +1,51 @@
+@block @block_blog_tag @core_blog @core_tag
+Feature: Adding blog tag block
+  In order to search blog post by tag
+  As a user
+  I need to be able to use block blog tag
+
+  Scenario: Adding block blog tag to the course
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Blog tags" block
+
+    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I follow "Blog about this Course"
+    And I set the following fields to these values:
+      | Entry title                                 | Blog post from teacher    |
+      | Blog entry body                             | Teacher blog post content |
+      | Other tags (enter tags separated by commas) | Cats, dogs                |
+    And I press "Save changes"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I navigate to "Course blogs" node in "Current course > c1 > Participants"
+    And I follow "Blog about this Course"
+    And I set the following fields to these values:
+      | Entry title                                 | Blog post from student    |
+      | Blog entry body                             | Student blog post content |
+      | Other tags (enter tags separated by commas) | DOGS, mice                |
+    And I press "Save changes"
+    And I follow "c1"
+    Then I should see "Cats" in the "Blog tags" "block"
+    And I should see "dogs" in the "Blog tags" "block"
+    And I should see "mice" in the "Blog tags" "block"
+    And I click on "Cats" "link" in the "Blog tags" "block"
+    And I should see "Blog post from teacher"
+    And I should see "Teacher blog post content"
+    And I log out
index 74cc088..2a92c9f 100644 (file)
@@ -115,7 +115,7 @@ class block_navigation_renderer extends plugin_renderer_base {
             } else if ($item->action instanceof action_link) {
                 //TODO: to be replaced with something else
                 $link = $item->action;
-                $link->text = $icon.$link->text;
+                $link->text = $icon.html_writer::span($link->text, 'item-content-wrap');
                 $link->attributes = array_merge($link->attributes, $attributes);
                 $content = $this->output->render($link);
             } else if ($item->action instanceof moodle_url) {
index 2838651..68a13ac 100644 (file)
@@ -23,6 +23,8 @@
  */
 
  class block_rss_client extends block_base {
+    /** The maximum time in seconds that cron will wait between attempts to retry failing RSS feeds. */
+    const CLIENT_MAX_SKIPTIME = 43200; // 60 * 60 * 12 seconds.
 
     function init() {
         $this->title = get_string('pluginname', 'block_rss_client');
     }
 
     /**
-     * cron - goes through all feeds and retrieves them with the cache
-     * duration set to 0 in order to force the retrieval of the item and
-     * refresh the cache
+     * cron - goes through all the feeds. If the feed has a skipuntil value
+     * that is less than the current time cron will attempt to retrieve it
+     * with the cache duration set to 0 in order to force the retrieval of
+     * the item and refresh the cache.
      *
-     * @return boolean true if all feeds were retrieved succesfully
+     * If a feed fails then the skipuntil time of that feed is set to be
+     * later than the next expected cron time. The amount of time will
+     * increase each time the fetch fails until the maximum is reached.
+     *
+     * If a feed that has been failing is successfully retrieved it will
+     * go back to being handled as though it had never failed.
+     *
+     * CRON should therefor process requests for permanently broken RSS
+     * feeds infrequently, and temporarily unavailable feeds will be tried
+     * less often until they become available again.
+     *
+     * @return boolean Always returns true
      */
     function cron() {
         global $CFG, $DB;
         require_once($CFG->libdir.'/simplepie/moodle_simplepie.php');
 
+        // Get the legacy cron time, strangely the cron property of block_base
+        // does not seem to get set. This means we must retrive it here.
+        $this->cron = $DB->get_field('block', 'cron', array('name' => 'rss_client'));
+
         // We are going to measure execution times
         $starttime =  microtime();
-
-        // And we have one initial $status
-        $status = true;
+        $starttimesec = time();
 
         // Fetch all site feeds.
         $rs = $DB->get_recordset('block_rss_client');
         mtrace('');
         foreach ($rs as $rec) {
             mtrace('    ' . $rec->url . ' ', '');
+
+            // Skip feed if it failed recently.
+            if ($starttimesec < $rec->skipuntil) {
+                mtrace('skipping until ' . userdate($rec->skipuntil));
+                continue;
+            }
+
             // Fetch the rss feed, using standard simplepie caching
             // so feeds will be renewed only if cache has expired
             core_php_time_limit::raise(60);
             $feed->init();
 
             if ($feed->error()) {
-                mtrace('Error: could not load/find the RSS feed');
-                $status = false;
+                // Skip this feed (for an ever-increasing time if it keeps failing).
+                $rec->skiptime = $this->calculate_skiptime($rec->skiptime);
+                $rec->skipuntil = time() + $rec->skiptime;
+                $DB->update_record('block_rss_client', $rec);
+                mtrace("Error: could not load/find the RSS feed - skipping for {$rec->skiptime} seconds.");
             } else {
                 mtrace ('ok');
+                // It worked this time, so reset the skiptime.
+                if ($rec->skiptime > 0) {
+                    $rec->skiptime = 0;
+                    $rec->skipuntil = 0;
+                    $DB->update_record('block_rss_client', $rec);
+                }
+                // Only increase the counter when a feed is sucesfully refreshed.
+                $counter ++;
             }
-            $counter ++;
         }
         $rs->close();
 
         // Show times
         mtrace($counter . ' feeds refreshed (took ' . microtime_diff($starttime, microtime()) . ' seconds)');
 
-        // And return $status
-        return $status;
+        return true;
+    }
+
+    /**
+     * Calculates a new skip time for a record based on the current skip time.
+     *
+     * @param int $currentskip The curreent skip time of a record.
+     * @return int A new skip time that should be set.
+     */
+    protected function calculate_skiptime($currentskip) {
+        // The default time to skiptime.
+        $newskiptime = $this->cron * 1.1;
+        if ($currentskip > 0) {
+            // Double the last time.
+            $newskiptime = $currentskip * 2;
+        }
+        if ($newskiptime > self::CLIENT_MAX_SKIPTIME) {
+            // Do not allow the skip time to increase indefinatly.
+            $newskiptime = self::CLIENT_MAX_SKIPTIME;
+        }
+        return $newskiptime;
     }
 }
 
index f81d650..7a7e9cb 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="blocks/rss_client/db" VERSION="20120122" COMMENT="XMLDB file for Moodle rss_client block"
+<XMLDB PATH="blocks/rss_client/db" VERSION="20150717" COMMENT="XMLDB file for Moodle rss_client block"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
 >
@@ -13,6 +13,8 @@
         <FIELD NAME="description" TYPE="text" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="shared" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="url" TYPE="char" LENGTH="255" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="skiptime" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="How many seconds skip this feed for (increases every time it fails, resets to 0 when it succeeds)"/>
+        <FIELD NAME="skipuntil" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Do not query this RSS feed again until this time"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" />
diff --git a/blocks/rss_client/db/upgrade.php b/blocks/rss_client/db/upgrade.php
new file mode 100644 (file)
index 0000000..afc2b80
--- /dev/null
@@ -0,0 +1,54 @@
+<?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/>.
+
+/**
+ * Database upgrades for the RSS block.
+ *
+ * @package   block_rss_client
+ * @copyright 2014 Davo Smith
+ * @author    Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL
+ */
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Upgrade the block_rss_client database.
+ *
+ * @param int $oldversion The version number of the plugin that was installed.
+ * @return boolean
+ */
+function xmldb_block_rss_client_upgrade($oldversion) {
+    global $DB;
+    $dbman = $DB->get_manager();
+
+    if ($oldversion < 2015071700) {
+        // Support for skipping RSS feeds for a while when they fail.
+        $table = new xmldb_table('block_rss_client');
+        // How many seconds we are currently ignoring this RSS feed for (due to an error).
+        $field = new xmldb_field('skiptime', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'url');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+        // When to next update this RSS feed.
+        $field = new xmldb_field('skipuntil', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0', 'skiptime');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+        upgrade_block_savepoint(true, 2015071700, 'rss_client');
+    }
+
+    return true;
+}
diff --git a/blocks/rss_client/tests/cron_test.php b/blocks/rss_client/tests/cron_test.php
new file mode 100644 (file)
index 0000000..9069c28
--- /dev/null
@@ -0,0 +1,141 @@
+<?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/>.
+
+/**
+ * PHPunit tests for rss client cron.
+ *
+ * @package    block_rss_client
+ * @copyright  2015 University of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+defined('MOODLE_INTERNAL') || die();
+require_once(dirname(dirname(__DIR__)) . '/moodleblock.class.php');
+require_once(dirname(__DIR__) . '/block_rss_client.php');
+
+/**
+ * Class for the PHPunit tests for rss client cron.
+ *
+ * @package    block_rss_client
+ * @copyright  2015 Universit of Nottingham
+ * @author     Neill Magill <neill.magill@nottingham.ac.uk>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class block_rss_client_cron_testcase extends advanced_testcase {
+    /**
+     * Test that when a record has a skipuntil time that is greater
+     * than the current time the attempt is skipped.
+     */
+    public function test_skip() {
+        global $DB;
+        $this->resetAfterTest();
+        // Create a RSS feed record with a skip until time set to the future.
+        $record = (object) array(
+            'userid' => 1,
+            'title' => 'Skip test feed',
+            'preferredtitle' => '',
+            'description' => 'A feed to test the skip time.',
+            'shared' => 0,
+            'url' => 'http://example.com/rss',
+            'skiptime' => 330,
+            'skipuntil' => time() + 300,
+        );
+        $DB->insert_record('block_rss_client', $record);
+
+        $block = new block_rss_client();
+        ob_start();
+        // Silence SimplePie php notices.
+        @$block->cron();
+        $cronoutput = ob_get_clean();
+        $this->assertContains('skipping until ' . userdate($record->skipuntil), $cronoutput);
+        $this->assertContains('0 feeds refreshed (took ', $cronoutput);
+    }
+
+    /**
+     * Test that when a feed has an error the skip time is increaed correctly.
+     */
+    public function test_error() {
+        global $DB;
+        $this->resetAfterTest();
+        $time = time();
+        // A record that has failed before.
+        $record = (object) array(
+            'userid' => 1,
+            'title' => 'Skip test feed',
+            'preferredtitle' => '',
+            'description' => 'A feed to test the skip time.',
+            'shared' => 0,
+            'url' => 'http://example.com/rss',
+            'skiptime' => 330,
+            'skipuntil' => $time - 300,
+        );
+        $record->id = $DB->insert_record('block_rss_client', $record);
+
+        // A record that has not failed before.
+        $record2 = (object) array(
+            'userid' => 1,
+            'title' => 'Skip test feed',
+            'preferredtitle' => '',
+            'description' => 'A feed to test the skip time.',
+            'shared' => 0,
+            'url' => 'http://example.com/rss2',
+            'skiptime' => 0,
+            'skipuntil' => 0,
+        );
+        $record2->id = $DB->insert_record('block_rss_client', $record2);
+
+        // A record that is near the maximum wait time.
+        $record3 = (object) array(
+            'userid' => 1,
+            'title' => 'Skip test feed',
+            'preferredtitle' => '',
+            'description' => 'A feed to test the skip time.',
+            'shared' => 0,
+            'url' => 'http://example.com/rss3',
+            'skiptime' => block_rss_client::CLIENT_MAX_SKIPTIME - 5,
+            'skipuntil' => $time - 1,
+        );
+        $record3->id = $DB->insert_record('block_rss_client', $record3);
+
+        // Run the cron.
+        $block = new block_rss_client();
+        ob_start();
+        // Silence SimplePie php notices.
+        @$block->cron();
+        $cronoutput = ob_get_clean();
+        $skiptime1 = $record->skiptime * 2;
+        $message1 = 'http://example.com/rss Error: could not load/find the RSS feed - skipping for ' . $skiptime1 . ' seconds.';
+        $this->assertContains($message1, $cronoutput);
+        $skiptime2 = 330; // Assumes that the cron time in the version file is 300.
+        $message2 = 'http://example.com/rss2 Error: could not load/find the RSS feed - skipping for ' . $skiptime2 . ' seconds.';
+        $this->assertContains($message2, $cronoutput);
+        $skiptime3 = block_rss_client::CLIENT_MAX_SKIPTIME;
+        $message3 = 'http://example.com/rss3 Error: could not load/find the RSS feed - skipping for ' . $skiptime3 . ' seconds.';
+        $this->assertContains($message3, $cronoutput);
+        $this->assertContains('0 feeds refreshed (took ', $cronoutput);
+
+        // Test that the records have been correctly updated.
+        $newrecord = $DB->get_record('block_rss_client', array('id' => $record->id));
+        $this->assertAttributeEquals($skiptime1, 'skiptime', $newrecord);
+        $this->assertAttributeGreaterThanOrEqual($time + $skiptime1, 'skipuntil', $newrecord);
+        $newrecord2 = $DB->get_record('block_rss_client', array('id' => $record2->id));
+        $this->assertAttributeEquals($skiptime2, 'skiptime', $newrecord2);
+        $this->assertAttributeGreaterThanOrEqual($time + $skiptime2, 'skipuntil', $newrecord2);
+        $newrecord3 = $DB->get_record('block_rss_client', array('id' => $record3->id));
+        $this->assertAttributeEquals($skiptime3, 'skiptime', $newrecord3);
+        $this->assertAttributeGreaterThanOrEqual($time + $skiptime3, 'skipuntil', $newrecord3);
+    }
+}
index d1979d3..2e2cea9 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2015051100;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2015071700;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2015050500;        // Requires this Moodle version
 $plugin->component = 'block_rss_client'; // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 300;               // Set min time between cron executions to 300 secs (5 mins)
index eb8b42a..bc057b5 100644 (file)
@@ -14,7 +14,6 @@
 .block_settings .block_tree .tree_item {padding-left: 21px;margin:3px 0px;text-align:left;}
 
 .block_settings .block_tree .tree_item.branch {background-image: url([[pix:t/expanded]]);background-position: 0 10%;background-repeat: no-repeat;}
-.block_settings .block_tree .root_node.leaf {padding-left:0px;}
 .block_settings .block_tree .active_tree_node {font-weight:bold;}
 .jsenabled .block_settings .block_tree .tree_item.branch {cursor:pointer;}
 .jsenabled .block_settings .block_tree .tree_item.emptybranch {background-image: url([[pix:t/collapsed_empty]]);background-position: 0 10%;background-repeat: no-repeat;}
@@ -29,7 +28,6 @@
 .dir-rtl .block_settings .block_tree li ul {padding-left:0;padding-right: 18px;}
 .dir-rtl .block_settings .block_tree .tree_item {padding-right: 21px; padding-left: 0; text-align:right;}
 .dir-rtl .block_settings .block_tree .tree_item.branch {background-position: center right;}
-.dir-rtl .block_settings .block_tree .root_node.leaf {padding-right:0px;}
 .dir-rtl .block_settings .block_tree li.item_with_icon > p img { right: 0; left: auto;}
 .jsenabled .block_settings .block_tree .tree_item.branch.loadingbranch {background-image:url([[pix:i/loading_small]]);}
 
index e28c75b..cd26cbc 100644 (file)
@@ -47,7 +47,7 @@ class block_site_main_menu extends block_list {
             return $this->content;
         }
 
-        $course = $this->page->course;
+        $course = get_site();
         require_once($CFG->dirroot.'/course/lib.php');
         $context = context_course::instance($course->id);
         $isediting = $this->page->user_is_editing() && has_capability('moodle/course:manageactivities', $context);
@@ -73,8 +73,9 @@ class block_site_main_menu extends block_list {
                         $attrs['title'] = $cm->modfullname;
                         $attrs['class'] = $cm->extraclasses . ' activity-action';
                         if ($cm->onclick) {
-                            $attrs['id'] = html_writer::random_id('onclick');
-                            $OUTPUT->add_action_handler(new component_action('click', $cm->onclick), $attrs['id']);
+                            // Get on-click attribute value if specified and decode the onclick - it
+                            // has already been encoded for display.
+                            $attrs['onclick'] = htmlspecialchars_decode($cm->onclick);
                         }
                         if (!$cm->visible) {
                             $attrs['class'] .= ' dimmed';
@@ -161,8 +162,9 @@ class block_site_main_menu extends block_list {
                         $attrs['title'] = $mod->modfullname;
                         $attrs['class'] = $mod->extraclasses . ' activity-action';
                         if ($mod->onclick) {
-                            $attrs['id'] = html_writer::random_id('onclick');
-                            $OUTPUT->add_action_handler(new component_action('click', $mod->onclick), $attrs['id']);
+                            // Get on-click attribute value if specified and decode the onclick - it
+                            // has already been encoded for display.
+                            $attrs['onclick'] = htmlspecialchars_decode($mod->onclick);
                         }
                         if (!$mod->visible) {
                             $attrs['class'] .= ' dimmed';
diff --git a/blocks/site_main_menu/tests/behat/add_url.feature b/blocks/site_main_menu/tests/behat/add_url.feature
new file mode 100644 (file)
index 0000000..f391d7b
--- /dev/null
@@ -0,0 +1,18 @@
+@block @block_main_menu
+Feature: Add URL to main menu block
+  In order to add helpful resources for students
+  As a admin
+  I need to add URLs to the main menu block and check it works.
+
+  @javascript
+  Scenario: Add a URL in menu block and ensure it appears
+    Given I log in as "admin"
+    And I am on site homepage
+    And I navigate to "Turn editing on" node in "Front page settings"
+    When I add a "URL" to section "0" and I fill the form with:
+      | Name | google |
+      | Description | gooooooooogle |
+      | External URL | http://www.google.com |
+      | id_display | In pop-up |
+    Then "google" "link" should exist in the "Main menu" "block"
+    And "Add an activity or resource" "link" should exist in the "Main menu" "block"
diff --git a/blocks/tag_youtube/db/install.php b/blocks/tag_youtube/db/install.php
new file mode 100644 (file)
index 0000000..0572762
--- /dev/null
@@ -0,0 +1,36 @@
+<?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/>.
+
+/**
+ * Tag Youtube block installation.
+ *
+ * @package    block_tag_youtube
+ * @copyright  2015 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Sets the install values for the tag_youtube entry in the block table.
+ *
+ * @return void
+ */
+function xmldb_block_tag_youtube_install() {
+    global $DB;
+
+    // Disable this block by default.
+    $DB->set_field('block', 'visible', 0, array('name' => 'tag_youtube'));
+}
+
diff --git a/blocks/tag_youtube/tests/block_tag_youtube_test.php b/blocks/tag_youtube/tests/block_tag_youtube_test.php
new file mode 100644 (file)
index 0000000..6434c44
--- /dev/null
@@ -0,0 +1,51 @@
+<?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/>.
+
+/**
+ * Block Tag Youtube tests
+ *
+ * @package    block_tag_youtube
+ * @category   test
+ * @copyright  2015 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Block Tag Youtube test class.
+ *
+ * @package   block_tag_youtube
+ * @category  test
+ * @copyright 2015 Jun Pataleta
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_block_tag_youtube_testcase extends advanced_testcase {
+
+    /**
+     * Testing the tag youtube block's initial state after a new installation.
+     *
+     * @return void
+     */
+    public function test_after_install() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+
+        // Assert that tag_youtube entry exists and that its visible attribute is set to 0 (disabled).
+        $this->assertTrue($DB->record_exists('block', array('name' => 'tag_youtube', 'visible' => 0)));
+    }
+}
diff --git a/blocks/tags/tests/behat/coursetags.feature b/blocks/tags/tests/behat/coursetags.feature
new file mode 100644 (file)
index 0000000..c5f9c55
--- /dev/null
@@ -0,0 +1,64 @@
+@block @block_tags @core_tag
+Feature: Block tags displaying course tags
+  In order to tag courses
+  As a user
+  I need to be able to use the block tags
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+      | student2 | Student | 2 | student2@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+      | student2 | c1     | student        |
+    And I log in as "admin"
+    And I set the following administration settings values:
+      | Show course tags | 1 |
+    And I log out
+
+  Scenario: Add Tags block to tag courses in a course
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Tags" block
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "more..." "link" in the "Tags" "block"
+    And I should not see "Neverusedtag"
+    And I follow "c1"
+    And I set the field "coursetag_new_tag" to "Dogs, Mice"
+    And I press "Add"
+    And I should see "Dogs" in the "Tags" "block"
+    And I should see "Mice" in the "Tags" "block"
+    And I log out
+    And I log in as "student2"
+    And I follow "Course 1"
+    And I should see "Dogs" in the "Tags" "block"
+    And I set the field "coursetag_new_tag" to "Cats, Dogs"
+    And I press "Add"
+    And I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I click on "more..." "link" in the "Tags" "block"
+    And "Cats" "link" should appear before "Dogs" "link"
+    And "Dogs" "link" should appear before "Mice" "link"
+    And I follow "My tags"
+    And I should see "Dogs"
+    And I should see "Cats"
+    And I should not see "Mice"
+    And I follow "All tags"
+    And I follow "Popularity"
+    And "Mice" "link" should appear before "Dogs" "link"
+    And I should not see "Neverusedtag"
+    And I log out
diff --git a/blocks/tags/tests/behat/tagcloud.feature b/blocks/tags/tests/behat/tagcloud.feature
new file mode 100644 (file)
index 0000000..2851ae7
--- /dev/null
@@ -0,0 +1,57 @@
+@block @block_tags @core_tag
+Feature: Block tags displaying tag cloud
+  In order to view system tags
+  As a user
+  I need to be able to use the block tags
+
+  Background:
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@example.com |
+      | student1 | Student | 1 | student1@example.com |
+    And the following "courses" exist:
+      | fullname  | shortname |
+      | Course 1  | c1        |
+    And the following "tags" exist:
+      | name         | tagtype  |
+      | Neverusedtag | official |
+    And the following "course enrolments" exist:
+      | user     | course | role           |
+      | teacher1 | c1     | editingteacher |
+      | student1 | c1     | student        |
+    And I log in as "teacher1"
+    And I follow "Preferences" in the user menu
+    And I follow "Edit profile"
+    And I expand all fieldsets
+    And I set the field "Enter tags separated by commas" to "Dogs, Cats"
+    And I press "Update profile"
+    And I log out
+
+  Scenario: Add Tags block on a front page
+    When I log in as "admin"
+    And I am on site homepage
+    And I follow "Turn editing on"
+    And I add the "Tags" block
+    And I log out
+    And I am on site homepage
+    Then I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "Dogs" "link" in the "Tags" "block"
+    And I should see "Log in to the site" in the ".breadcrumb" "css_element"
+
+  Scenario: Add Tags block in a course
+    When I log in as "teacher1"
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add the "Tags" block
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    Then I should see "Dogs" in the "Tags" "block"
+    And I should see "Cats" in the "Tags" "block"
+    And I should not see "Neverusedtag" in the "Tags" "block"
+    And I click on "Dogs" "link" in the "Tags" "block"
+    And I should see "Users tagged with \"Dogs\": 1"
+    And I should see "Teacher 1"
+    And I log out
index 4b1fd5a..363714c 100644 (file)
@@ -24,7 +24,6 @@ Feature: Allowed blocks controls
     Then I should see "Activities" in the "Activities" "block"
     And I should see "Course completion status" in the "Course completion status" "block"
 
-  @javascript
   Scenario: Blocks can not be added when the admin restricts the permissions
     Given I log in as "admin"
     And I set the following system permissions of "Teacher" role:
index bd38cad..dfb5c8f 100644 (file)
@@ -24,7 +24,6 @@ Feature: Blogs can be set to be only visible by the author.
     And I press "Save changes"
     And I log out
 
-  @javascript
   Scenario: A student can not see another student's blog entries.
     Given I log in as "testuser"
     And I follow "Course 1"
index eca785f..c25369e 100644 (file)
@@ -354,7 +354,7 @@ class cache_helper {
     protected static function ensure_ready_for_stats($store, $definition, $mode = cache_store::MODE_APPLICATION) {
         // This function is performance-sensitive, so exit as quickly as possible
         // if we do not need to do anything.
-        if (isset(self::$stats[$definition][$store])) {
+        if (isset(self::$stats[$definition]['stores'][$store])) {
             return;
         }
         if (!array_key_exists($definition, self::$stats)) {
@@ -368,7 +368,7 @@ class cache_helper {
                     )
                 )
             );
-        } else if (!array_key_exists($store, self::$stats[$definition])) {
+        } else if (!array_key_exists($store, self::$stats[$definition]['stores'])) {
             self::$stats[$definition]['stores'][$store] = array(
                 'hits' => 0,
                 'misses' => 0,
index 514a4bd..e044c91 100644 (file)
@@ -341,8 +341,8 @@ class cachestore_file extends cache_store implements cache_is_key_aware, cache_i
             $maxtime = cache::now() - $ttl;
         }
         $readfile = false;
-        if ($this->prescan && array_key_exists($key, $this->keys)) {
-            if (!$ttl || $this->keys[$filename] >= $maxtime && file_exists($file)) {
+        if ($this->prescan && array_key_exists($filename, $this->keys)) {
+            if ((!$ttl || $this->keys[$filename] >= $maxtime) && file_exists($file)) {
                 $readfile = true;
             } else {
                 $this->delete($key);
index be321f2..887c545 100644 (file)
@@ -44,4 +44,34 @@ class cachestore_file_test extends cachestore_tests {
     protected function get_class_name() {
         return 'cachestore_file';
     }
+
+    /**
+     * Testing cachestore_file::get with prescan enabled and with
+     * deleting the cache between the prescan and the call to get.
+     *
+     * The deleting of cache simulates some other process purging
+     * the cache.
+     */
+    public function test_cache_get_with_prescan_and_purge() {
+        global $CFG;
+
+        $definition = cache_definition::load_adhoc(cache_store::MODE_REQUEST, 'cachestore_file', 'phpunit_test');
+        $name = 'File test';
+
+        $path = make_cache_directory('cachestore_file_test');
+        $cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
+        $cache->initialise($definition);
+
+        $cache->set('testing', 'value');
+
+        $path  = make_cache_directory('cachestore_file_test');
+        $cache = new cachestore_file($name, array('path' => $path, 'prescan' => true));
+        $cache->initialise($definition);
+
+        // Let's pretend that some other process purged caches.
+        remove_dir($CFG->cachedir.'/cachestore_file_test', true);
+        make_cache_directory('cachestore_file_test');
+
+        $cache->get('testing');
+    }
 }
\ No newline at end of file
index 7c5d2ef..0d6daa7 100644 (file)
@@ -1713,6 +1713,21 @@ class core_cache_testcase extends advanced_testcase {
             'staticacceleration' => true,
             'staticaccelerationsize' => 4,
         ));
+        $instance->phpunit_add_definition('phpunit/simpledataarea1', array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'simpledataarea1',
+            'staticacceleration' => true,
+            'simpledata' => false
+        ));
+        $instance->phpunit_add_definition('phpunit/simpledataarea2', array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'simpledataarea2',
+            'staticacceleration' => true,
+            'simpledata' => true
+        ));
+
         $cache = cache::make('phpunit', 'accelerated');
         $this->assertInstanceOf('cache_phpunit_application', $cache);
 
@@ -1839,5 +1854,176 @@ class core_cache_testcase extends advanced_testcase {
         $this->assertTrue($cache->set('a', 'A'));
         $this->assertEquals('A', $cache->phpunit_static_acceleration_get('a'));
         $this->assertEquals('A', $cache->get('a'));
+
+        // Setting simpledata to false objects are cloned when retrieving data.
+        $cache = cache::make('phpunit', 'simpledataarea1');
+        $notreallysimple = new stdClass();
+        $notreallysimple->name = 'a';
+        $cache->set('a', $notreallysimple);
+        $returnedinstance1 = $cache->get('a');
+        $returnedinstance2 = $cache->get('a');
+        $returnedinstance1->name = 'b';
+        $this->assertEquals('a', $returnedinstance2->name);
+
+        // Setting simpledata to true we assume that data does not contain references.
+        $cache = cache::make('phpunit', 'simpledataarea2');
+        $notreallysimple = new stdClass();
+        $notreallysimple->name = 'a';
+        $cache->set('a', $notreallysimple);
+        $returnedinstance1 = $cache->get('a');
+        $returnedinstance2 = $cache->get('a');
+        $returnedinstance1->name = 'b';
+        $this->assertEquals('b', $returnedinstance2->name);
+    }
+
+    public function test_performance_debug() {
+        global $CFG;
+        $this->resetAfterTest(true);
+        $CFG->perfdebug = 15;
+
+        $instance = cache_config_testing::instance();
+        $applicationid = 'phpunit/applicationperf';
+        $instance->phpunit_add_definition($applicationid, array(
+            'mode' => cache_store::MODE_APPLICATION,
+            'component' => 'phpunit',
+            'area' => 'applicationperf'
+        ));
+        $sessionid = 'phpunit/sessionperf';
+        $instance->phpunit_add_definition($sessionid, array(
+            'mode' => cache_store::MODE_SESSION,
+            'component' => 'phpunit',
+            'area' => 'sessionperf'
+        ));
+        $requestid = 'phpunit/requestperf';
+        $instance->phpunit_add_definition($requestid, array(
+            'mode' => cache_store::MODE_REQUEST,
+            'component' => 'phpunit',
+            'area' => 'requestperf'
+        ));
+
+        $application = cache::make('phpunit', 'applicationperf');
+        $session = cache::make('phpunit', 'sessionperf');
+        $request = cache::make('phpunit', 'requestperf');
+
+        // Check that no stats are recorded for these definitions yet.
+        $stats = cache_helper::get_stats();
+        $this->assertArrayNotHasKey($applicationid, $stats);
+        $this->assertArrayHasKey($sessionid, $stats);       // Session cache sets a key on construct.
+        $this->assertArrayNotHasKey($requestid, $stats);
+
+        // Check that stores register misses.
+        $this->assertFalse($application->get('missMe'));
+        $this->assertFalse($application->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($session->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+        $this->assertFalse($request->get('missMe'));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(1, $endstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register sets.
+        $this->assertTrue($application->set('setMe1', 1));
+        $this->assertTrue($application->set('setMe2', 2));
+        $this->assertTrue($session->set('setMe1', 1));
+        $this->assertTrue($session->set('setMe2', 2));
+        $this->assertTrue($session->set('setMe3', 3));
+        $this->assertTrue($request->set('setMe1', 1));
+        $this->assertTrue($request->set('setMe2', 2));
+        $this->assertTrue($request->set('setMe3', 3));
+        $this->assertTrue($request->set('setMe4', 4));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register hits.
+        $this->assertEquals($application->get('setMe1'), 1);
+        $this->assertEquals($application->get('setMe2'), 2);
+        $this->assertEquals($session->get('setMe1'), 1);
+        $this->assertEquals($session->get('setMe2'), 2);
+        $this->assertEquals($session->get('setMe3'), 3);
+        $this->assertEquals($request->get('setMe1'), 1);
+        $this->assertEquals($request->get('setMe2'), 2);
+        $this->assertEquals($request->get('setMe3'), 3);
+        $this->assertEquals($request->get('setMe4'), 4);
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
+
+        $startstats = cache_helper::get_stats();
+
+        // Check that stores register through get_many.
+        $application->get_many(array('setMe1', 'setMe2'));
+        $session->get_many(array('setMe1', 'setMe2', 'setMe3'));
+        $request->get_many(array('setMe1', 'setMe2', 'setMe3', 'setMe4'));
+
+        $endstats = cache_helper::get_stats();
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['misses'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['misses']);
+        $this->assertEquals(2, $endstats[$applicationid]['stores']['cachestore_file']['hits'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['hits']);
+        $this->assertEquals(0, $endstats[$applicationid]['stores']['cachestore_file']['sets'] -
+            $startstats[$applicationid]['stores']['cachestore_file']['sets']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['misses'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['misses']);
+        $this->assertEquals(3, $endstats[$sessionid]['stores']['cachestore_session']['hits'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['hits']);
+        $this->assertEquals(0, $endstats[$sessionid]['stores']['cachestore_session']['sets'] -
+            $startstats[$sessionid]['stores']['cachestore_session']['sets']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['misses'] -
+            $startstats[$requestid]['stores']['cachestore_static']['misses']);
+        $this->assertEquals(4, $endstats[$requestid]['stores']['cachestore_static']['hits'] -
+            $startstats[$requestid]['stores']['cachestore_static']['hits']);
+        $this->assertEquals(0, $endstats[$requestid]['stores']['cachestore_static']['sets'] -
+            $startstats[$requestid]['stores']['cachestore_static']['sets']);
     }
 }
index 3951df8..5a40c69 100644 (file)
@@ -2174,8 +2174,8 @@ class calendar_event {
      * Pass in a object containing the event properties and this function will
      * insert it into the database and deal with any associated files
      *
-     * @see add_event()
-     * @see update_event()
+     * @see self::create()
+     * @see self::update()
      *
      * @param stdClass $data object of event
      * @param bool $checkcapability if moodle should check calendar managing capability or not
@@ -2391,7 +2391,7 @@ class calendar_event {
      * This function deletes an event, any associated events if $deleterepeated=true,
      * and cleans up any files associated with the events.
      *
-     * @see delete_event()
+     * @see self::delete()
      *
      * @param bool $deleterepeated  delete event repeatedly
      * @return bool succession of deleting event
index 345f9e7..fab8aa5 100644 (file)
@@ -41,8 +41,6 @@
 require_once('../config.php');
 require_once($CFG->dirroot.'/calendar/lib.php');
 
-require_sesskey();
-
 $var = required_param('var', PARAM_ALPHA);
 $return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_LOCALURL);
 $courseid = optional_param('id', -1, PARAM_INT);
@@ -51,6 +49,12 @@ if ($courseid != -1) {
 } else {
     $return = new moodle_url($return);
 }
+
+if (!confirm_sesskey()) {
+    // Do not call require_sesskey() since this page may be accessed without session (for example by bots).
+    redirect($return);
+}
+
 $url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out_as_local_url(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
 $PAGE->set_url($url);
 $PAGE->set_context(context_system::instance());
index 0b662bf..7403355 100644 (file)
@@ -343,95 +343,6 @@ class core_cohort_cohortlib_testcase extends advanced_testcase {
         $this->assertTrue(cohort_is_member($cohort->id, $user->id));
     }
 
-    public function test_cohort_get_visible_list() {
-        global $DB;
-
-        $this->resetAfterTest();
-
-        $category1 = $this->getDataGenerator()->create_category();
-        $category2 = $this->getDataGenerator()->create_category();
-
-        $course1 = $this->getDataGenerator()->create_course(array('category'=>$category1->id));
-        $course2 = $this->getDataGenerator()->create_course(array('category'=>$category2->id));
-        $course3 = $this->getDataGenerator()->create_course();
-
-        $cohort1 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category1->id)->id));
-        $cohort2 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_coursecat::instance($category2->id)->id));
-        $cohort3 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_system::instance()->id));
-        $cohort4 = $this->getDataGenerator()->create_cohort(array('contextid'=>context_system::instance()->id));
-
-        $user1 = $this->getDataGenerator()->create_user();
-        $user2 = $this->getDataGenerator()->create_user();
-        $user3 = $this->getDataGenerator()->create_user();
-        $user4 = $this->getDataGenerator()->create_user();
-        $user5 = $this->getDataGenerator()->create_user();
-
-        $manualenrol = enrol_get_plugin('manual');
-        $enrol1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'manual'));
-        $enrol2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'));
-
-        $manualenrol->enrol_user($enrol1, $user1->id);
-        $manualenrol->enrol_user($enrol1, $user3->id);
-        $manualenrol->enrol_user($enrol1, $user4->id);
-        $manualenrol->enrol_user($enrol2, $user2->id);
-
-        cohort_add_member($cohort1->id, $user1->id);
-        cohort_add_member($cohort3->id, $user1->id);
-        cohort_add_member($cohort1->id, $user3->id);
-        cohort_add_member($cohort2->id, $user2->id);
-
-        // Cohort1 (Cat1) has 2 users total, 2 user enrolled in course1, 0 users enrolled in course2.
-        // Cohort2 (Cat2) has 1 users total, 0 user enrolled in course1, 1 users enrolled in course2.
-        // Cohort3 (Syst) has 1 users total, 1 user enrolled in course1, 0 users enrolled in course2.
-        // Cohort4 (Syst) has 0 users total.
-
-        $list = cohort_get_visible_list($course1);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(2, count($list));
-        $this->assertNotEmpty($list[$cohort1->id]);
-        $this->assertRegExp('/\(2\)$/', $list[$cohort1->id]);
-        $this->assertNotEmpty($list[$cohort3->id]);
-        $this->assertRegExp('/\(1\)$/', $list[$cohort3->id]);
-
-        $list = cohort_get_visible_list($course1, false);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(3, count($list));
-        $this->assertNotEmpty($list[$cohort1->id]);
-        $this->assertRegExp('/\(2\)$/', $list[$cohort1->id]);
-        $this->assertNotEmpty($list[$cohort3->id]);
-        $this->assertRegExp('/\(1\)$/', $list[$cohort3->id]);
-        $this->assertNotEmpty($list[$cohort4->id]);
-        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
-
-        $list = cohort_get_visible_list($course2);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(1, count($list));
-        $this->assertNotEmpty($list[$cohort2->id]);
-        $this->assertRegExp('/\(1\)$/', $list[$cohort2->id]);
-
-        $list = cohort_get_visible_list($course2, false);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(3, count($list));
-        $this->assertNotEmpty($list[$cohort2->id]);
-        $this->assertRegExp('/\(1\)$/', $list[$cohort2->id]);
-        $this->assertNotEmpty($list[$cohort3->id]);
-        $this->assertRegExp('/[^\)]$/', $list[$cohort3->id]);
-        $this->assertNotEmpty($list[$cohort4->id]);
-        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
-
-        $list = cohort_get_visible_list($course3);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(0, count($list));
-
-        $list = cohort_get_visible_list($course3, false);
-        $this->assertDebuggingCalled();
-        $this->assertEquals(2, count($list));
-        $this->assertNotEmpty($list[$cohort3->id]);
-        $this->assertRegExp('/[^\)]$/', $list[$cohort3->id]);
-        $this->assertNotEmpty($list[$cohort4->id]);
-        $this->assertRegExp('/[^\)]$/', $list[$cohort4->id]);
-    }
-
     public function test_cohort_get_cohorts() {
         global $DB;
 
index a87abd3..3275587 100644 (file)
@@ -168,7 +168,7 @@ abstract class completion_criteria extends data_object {
         global $CFG, $COMPLETION_CRITERIA_TYPES;
 
         if (!isset($params['criteriatype']) || !isset($COMPLETION_CRITERIA_TYPES[$params['criteriatype']])) {
-            error('invalidcriteriatype', 'completion');
+            print_error('invalidcriteriatype', 'completion');
         }
 
         $class = 'completion_criteria_'.$COMPLETION_CRITERIA_TYPES[$params['criteriatype']];
index 5125a88..6790dbf 100644 (file)
@@ -27,7 +27,8 @@
 
 require_once(__DIR__ . '/../../../lib/behat/behat_base.php');
 
-use Behat\Behat\Context\Step\Given as Given,
+use Behat\Behat\Context\Step\Given,
+    Behat\Behat\Context\Step\Then,
     Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException;
 
 /**
@@ -56,7 +57,7 @@ class behat_completion extends behat_base {
 
         return array(
             new Given('I go to the current course activity completion report'),
-            new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
+            new Then('"' . $this->escape($xpath) . '" "xpath_element" should exist')
         );
     }
 
@@ -75,7 +76,7 @@ class behat_completion extends behat_base {
             "/descendant::img[contains(@title, $titleliteral)]";
         return array(
             new Given('I go to the current course activity completion report'),
-            new Given('I hover "' . $this->escape($xpath) . '" "xpath_element"')
+            new Then('"' . $this->escape($xpath) . '" "xpath_element" should exist')
         );
 
         return $steps;
index bdb434a..26e49e4 100644 (file)
@@ -1,13 +1,7 @@
 {
-    "repositories" : [
-        {
-            "type": "vcs",
-            "url": "https://github.com/moodlehq/moodle-behat-extension"
-        }
-    ],
     "require-dev": {
-        "phpunit/phpunit": "3.7.*",
-        "phpunit/dbUnit": "1.2.*",
+        "phpunit/phpunit": "4.7.*",
+        "phpunit/dbUnit": "1.4.*",
         "moodlehq/behat-extension": "1.30.0"
     }
 }
index 3a23f08..719ac08 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "a8ea9182a53569160119098b9cb80407",
+    "hash": "a85d8c9e61ccba5e235093157021f7b5",
     "packages": [],
     "packages-dev": [
         {
         },
         {
             "name": "doctrine/annotations",
-            "version": "v1.2.4",
+            "version": "v1.2.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/doctrine/annotations.git",
-                "reference": "b5202eb9e83f8db52e0e58867e0a46e63be8332e"
+                "reference": "f4a91702ca3cd2e568c3736aa031ed00c3752af4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/doctrine/annotations/zipball/b5202eb9e83f8db52e0e58867e0a46e63be8332e",
-                "reference": "b5202eb9e83f8db52e0e58867e0a46e63be8332e",
+                "url": "https://api.github.com/repos/doctrine/annotations/zipball/f4a91702ca3cd2e568c3736aa031ed00c3752af4",
+                "reference": "f4a91702ca3cd2e568c3736aa031ed00c3752af4",
                 "shasum": ""
             },
             "require": {
                 "docblock",
                 "parser"
             ],
-            "time": "2014-12-23 22:40:37"
+            "time": "2015-06-17 12:21:22"
         },
         {
             "name": "doctrine/cache",
             ],
             "time": "2014-12-20 21:24:13"
         },
+        {
+            "name": "doctrine/instantiator",
+            "version": "1.0.5",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/doctrine/instantiator.git",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3,<8.0-DEV"
+            },
+            "require-dev": {
+                "athletic/athletic": "~0.1.8",
+                "ext-pdo": "*",
+                "ext-phar": "*",
+                "phpunit/phpunit": "~4.0",
+                "squizlabs/php_codesniffer": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Marco Pivetta",
+                    "email": "ocramius@gmail.com",
+                    "homepage": "http://ocramius.github.com/"
+                }
+            ],
+            "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+            "homepage": "https://github.com/doctrine/instantiator",
+            "keywords": [
+                "constructor",
+                "instantiate"
+            ],
+            "time": "2015-06-14 21:17:01"
+        },
         {
             "name": "doctrine/lexer",
             "version": "v1.0.1",
                     "Moodle\\BehatExtension": "src/"
                 }
             },
+            "notification-url": "https://packagist.org/downloads/",
             "license": [
                 "GPLv3"
             ],
             "keywords": [
                 "BDD",
                 "Behat",
-                "Moodle"
+                "moodle"
             ],
-            "support": {
-                "source": "https://github.com/moodlehq/moodle-behat-extension/tree/v1.30.0"
-            },
             "time": "2015-05-15 02:00:06"
         },
+        {
+            "name": "phpdocumentor/reflection-docblock",
+            "version": "2.0.4",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.0"
+            },
+            "suggest": {
+                "dflydev/markdown": "~1.0",
+                "erusev/parsedown": "~1.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "phpDocumentor": [
+                        "src/"
+                    ]
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Mike van Riel",
+                    "email": "mike.vanriel@naenius.com"
+                }
+            ],
+            "time": "2015-02-03 12:10:50"
+        },
+        {
+            "name": "phpspec/prophecy",
+            "version": "v1.4.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/phpspec/prophecy.git",
+                "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+                "reference": "3132b1f44c7bf2ec4c7eb2d3cb78fdeca760d373",
+                "shasum": ""
+            },
+            "require": {
+                "doctrine/instantiator": "^1.0.2",
+                "phpdocumentor/reflection-docblock": "~2.0",
+                "sebastian/comparator": "~1.1"
+            },
+            "require-dev": {
+                "phpspec/phpspec": "~2.0"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.4.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Prophecy\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                },
+                {
+                    "name": "Marcello Duarte",
+                    "email": "marcello.duarte@gmail.com"
+                }
+            ],
+            "description": "Highly opinionated mocking framework for PHP 5.3+",
+            "homepage": "https://github.com/phpspec/prophecy",
+            "keywords": [
+                "Double",
+                "Dummy",
+                "fake",
+                "mock",
+                "spy",
+                "stub"
+            ],
+            "time": "2015-04-27 22:15:08"
+        },
         {
             "name": "phpunit/dbunit",
-            "version": "1.2.3",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/dbunit.git",
-                "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d"
+                "reference": "1afe25c90834ec499f007f48dd73767fdec3bf4f"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/8386782a2d55153e44a06eb1a9d13d6ed35d9c2d",
-                "reference": "8386782a2d55153e44a06eb1a9d13d6ed35d9c2d",
+                "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/1afe25c90834ec499f007f48dd73767fdec3bf4f",
+                "reference": "1afe25c90834ec499f007f48dd73767fdec3bf4f",
                 "shasum": ""
             },
             "require": {
                 "ext-pdo": "*",
                 "ext-simplexml": "*",
                 "php": ">=5.3.3",
-                "phpunit/phpunit": ">=3.7.0@stable"
+                "phpunit/phpunit": "~4.0",
+                "symfony/yaml": "~2.1"
             },
             "bin": [
-                "dbunit.php"
+                "composer/bin/dbunit"
             ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "1.3.x-dev"
                 }
             },
             "autoload": {
                 "testing",
                 "xunit"
             ],
-            "time": "2013-03-01 11:50:46"
+            "time": "2015-05-21 21:11:02"
         },
         {
             "name": "phpunit/php-code-coverage",
-            "version": "1.2.18",
+            "version": "2.2.2",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
-                "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b"
+                "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
-                "reference": "fe2466802556d3fe4e4d1d58ffd3ccfd0a19be0b",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2d7c03c0e4e080901b8f33b2897b0577be18a13c",
+                "reference": "2d7c03c0e4e080901b8f33b2897b0577be18a13c",
                 "shasum": ""
             },
             "require": {
                 "php": ">=5.3.3",
-                "phpunit/php-file-iterator": ">=1.3.0@stable",
-                "phpunit/php-text-template": ">=1.2.0@stable",
-                "phpunit/php-token-stream": ">=1.1.3,<1.3.0"
+                "phpunit/php-file-iterator": "~1.3",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-token-stream": "~1.3",
+                "sebastian/environment": "^1.3.2",
+                "sebastian/version": "~1.0"
             },
             "require-dev": {
-                "phpunit/phpunit": "3.7.*@dev"
+                "ext-xdebug": ">=2.1.4",
+                "phpunit/phpunit": "~4"
             },
             "suggest": {
                 "ext-dom": "*",
-                "ext-xdebug": ">=2.0.5"
+                "ext-xdebug": ">=2.2.1",
+                "ext-xmlwriter": "*"
             },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2.x-dev"
+                    "dev-master": "2.2.x-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHP/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 "testing",
                 "xunit"
             ],
-            "time": "2014-09-02 10:13:14"
+            "time": "2015-08-04 03:42:39"
         },
         {
             "name": "phpunit/php-file-iterator",
-            "version": "1.4.0",
+            "version": "1.4.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
-                "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb"
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a923bb15680d0089e2316f7a4af8f437046e96bb",
-                "reference": "a923bb15680d0089e2316f7a4af8f437046e96bb",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
+                "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
                 "shasum": ""
             },
             "require": {
                 "filesystem",
                 "iterator"
             ],
-            "time": "2015-04-02 05:19:05"
+            "time": "2015-06-21 13:08:43"
         },
         {
             "name": "phpunit/php-text-template",
-            "version": "1.2.0",
+            "version": "1.2.1",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-text-template.git",
-                "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a"
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
-                "reference": "206dfefc0ffe9cebf65c413e3d0e809c82fbf00a",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
+                "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "autoload": {
                 "classmap": [
-                    "Text/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
+                    "email": "sebastian@phpunit.de",
                     "role": "lead"
                 }
             ],
             "keywords": [
                 "template"
             ],
-            "time": "2014-01-30 17:20:04"
+            "time": "2015-06-21 13:50:34"
         },
         {
             "name": "phpunit/php-timer",
-            "version": "1.0.5",
+            "version": "1.0.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-timer.git",
-                "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c"
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
-                "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
+                "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
                 "shasum": ""
             },
             "require": {
             "type": "library",
             "autoload": {
                 "classmap": [
-                    "PHP/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
             "keywords": [
                 "timer"
             ],
-            "time": "2013-08-02 07:42:54"
+            "time": "2015-06-21 08:01:12"
         },
         {
             "name": "phpunit/php-token-stream",
-            "version": "1.2.2",
+            "version": "1.4.3",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/php-token-stream.git",
-                "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32"
+                "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ad4e1e23ae01b483c16f600ff1bebec184588e32",
-                "reference": "ad4e1e23ae01b483c16f600ff1bebec184588e32",
+                "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
+                "reference": "7a9b0969488c3c54fd62b4d504b3ec758fd005d9",
                 "shasum": ""
             },
             "require": {
                 "ext-tokenizer": "*",
                 "php": ">=5.3.3"
             },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "1.2-dev"
+                    "dev-master": "1.4-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHP/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
             "authors": [
                 {
                     "name": "Sebastian Bergmann",
-                    "email": "sb@sebastian-bergmann.de",
-                    "role": "lead"
+                    "email": "sebastian@phpunit.de"
                 }
             ],
             "description": "Wrapper around PHP's tokenizer extension.",
             "keywords": [
                 "tokenizer"
             ],
-            "time": "2014-03-03 05:10:30"
+            "time": "2015-06-19 03:43:16"
         },
         {
             "name": "phpunit/phpunit",
-            "version": "3.7.38",
+            "version": "4.7.7",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit.git",
-                "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6"
+                "reference": "9b97f9d807b862c2de2a36e86690000801c85724"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/38709dc22d519a3d1be46849868aa2ddf822bcf6",
-                "reference": "38709dc22d519a3d1be46849868aa2ddf822bcf6",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9b97f9d807b862c2de2a36e86690000801c85724",
+                "reference": "9b97f9d807b862c2de2a36e86690000801c85724",
                 "shasum": ""
             },
             "require": {
-                "ext-ctype": "*",
                 "ext-dom": "*",
                 "ext-json": "*",
                 "ext-pcre": "*",
                 "ext-reflection": "*",
                 "ext-spl": "*",
                 "php": ">=5.3.3",
-                "phpunit/php-code-coverage": "~1.2",
-                "phpunit/php-file-iterator": "~1.3",
-                "phpunit/php-text-template": "~1.1",
-                "phpunit/php-timer": "~1.0",
-                "phpunit/phpunit-mock-objects": "~1.2",
-                "symfony/yaml": "~2.0"
-            },
-            "require-dev": {
-                "pear-pear.php.net/pear": "1.9.4"
+                "phpspec/prophecy": "~1.3,>=1.3.1",
+                "phpunit/php-code-coverage": "~2.1",
+                "phpunit/php-file-iterator": "~1.4",
+                "phpunit/php-text-template": "~1.2",
+                "phpunit/php-timer": ">=1.0.6",
+                "phpunit/phpunit-mock-objects": "~2.3",
+                "sebastian/comparator": "~1.1",
+                "sebastian/diff": "~1.2",
+                "sebastian/environment": "~1.2",
+                "sebastian/exporter": "~1.2",
+                "sebastian/global-state": "~1.0",
+                "sebastian/version": "~1.0",
+                "symfony/yaml": "~2.1|~3.0"
             },
             "suggest": {
                 "phpunit/php-invoker": "~1.1"
             },
             "bin": [
-                "composer/bin/phpunit"
+                "phpunit"
             ],
             "type": "library",
             "extra": {
                 "branch-alias": {
-                    "dev-master": "3.7.x-dev"
+                    "dev-master": "4.7.x-dev"
                 }
             },
             "autoload": {
                 "classmap": [
-                    "PHPUnit/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                "",
-                "../../symfony/yaml/"
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 }
             ],
             "description": "The PHP Unit Testing framework.",
-            "homepage": "http://www.phpunit.de/",
+            "homepage": "https://phpunit.de/",
             "keywords": [
                 "phpunit",
                 "testing",
                 "xunit"
             ],
-            "time": "2014-10-17 09:04:17"
+            "time": "2015-07-13 11:28:34"
         },
         {
             "name": "phpunit/phpunit-mock-objects",
-            "version": "1.2.3",
+            "version": "2.3.6",
             "source": {
                 "type": "git",
                 "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
-                "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875"
+                "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875",
-                "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875",
+                "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
+                "reference": "18dfbcb81d05e2296c0bcddd4db96cade75e6f42",
                 "shasum": ""
             },
             "require": {
+                "doctrine/instantiator": "~1.0,>=1.0.2",
                 "php": ">=5.3.3",
-                "phpunit/php-text-template": ">=1.1.1@stable"
+                "phpunit/php-text-template": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
             },
             "suggest": {
                 "ext-soap": "*"
             },
             "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.3.x-dev"
+                }
+            },
             "autoload": {
                 "classmap": [
-                    "PHPUnit/"
+                    "src/"
                 ]
             },
             "notification-url": "https://packagist.org/downloads/",
-            "include-path": [
-                ""
-            ],
             "license": [
                 "BSD-3-Clause"
             ],
                 "mock",
                 "xunit"
             ],
-            "time": "2013-01-13 10:24:48"
+            "time": "2015-07-10 06:54:24"
         },
         {
             "name": "psr/log",
             ],
             "time": "2012-12-21 11:40:51"
         },
+        {
+            "name": "sebastian/comparator",
+            "version": "1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/comparator.git",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
+                "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/diff": "~1.2",
+                "sebastian/exporter": "~1.2"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Jeff Welch",
+                    "email": "whatthejeff@gmail.com"
+                },
+                {
+                    "name": "Volker Dusch",
+                    "email": "github@wallbash.com"
+                },
+                {
+                    "name": "Bernhard Schussek",
+                    "email": "bschussek@2bepublished.at"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides the functionality to compare PHP values for equality",
+            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "keywords": [
+                "comparator",
+                "compare",
+                "equality"
+            ],
+            "time": "2015-07-26 15:48:44"
+        },
+        {
+            "name": "sebastian/diff",
+            "version": "1.3.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/diff.git",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.2"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Kore Nordmann",
+                    "email": "mail@kore-nordmann.de"
+                },
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Diff implementation",
+            "homepage": "http://www.github.com/sebastianbergmann/diff",
+            "keywords": [
+                "diff"
+            ],
+            "time": "2015-02-22 15:13:53"
+        },
+        {
+            "name": "sebastian/environment",
+            "version": "1.3.2",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/environment.git",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.3.x-dev"
+                }
+            },
+            "autoload": {
+                "classmap": [
+                    "src/"
+                ]
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Sebastian Bergmann",
+                    "email": "sebastian@phpunit.de"
+                }
+            ],
+            "description": "Provides functionality to handle HHVM/PHP environments",
+            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "keywords": [
+                "Xdebug",
+                "environment",
+                "hhvm"
+            ],
+            "time": "2015-08-03 06:14:51"
+        },
+        {
+            "name": "sebastian/exporter",
+            "version": "1.2.1",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sebastianbergmann/exporter.git",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
+                "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "sebastian/recursion-context": "~1.0"
+            },
+            "require-dev": {
+                "phpunit/phpunit": "~4.4"
+            },
+            "type": "library",
+            "extra": {