Merge branch 'MDL-43774_master' of https://github.com/totara/openbadges
authorSam Hemelryk <sam@moodle.com>
Mon, 10 Feb 2014 02:21:41 +0000 (15:21 +1300)
committerSam Hemelryk <sam@moodle.com>
Mon, 10 Feb 2014 02:21:41 +0000 (15:21 +1300)
244 files changed:
admin/cli/backup.php [new file with mode: 0644]
admin/webservice/service_user_settings.php
auth/db/tests/db_test.php
auth/ldap/tests/plugin_test.php
auth/tests/behat/behat_auth.php
auth/tests/behat/login.feature
backup/backup.php
backup/controller/backup_controller.class.php
backup/controller/base_controller.class.php
backup/controller/restore_controller.class.php
backup/import.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_stepslib.php
backup/restore.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/dbops/backup_structure_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_helper.class.php
backup/util/includes/backup_includes.php
backup/util/includes/restore_includes.php
backup/util/plan/backup_plan.class.php
backup/util/plan/base_plan.class.php
backup/util/plan/base_task.class.php
backup/util/plan/restore_plan.class.php
backup/util/plan/restore_structure_step.class.php
backup/util/structure/backup_structure_processor.class.php
backup/util/ui/restore_ui.class.php
backup/util/ui/restore_ui_stage.class.php
backup/util/xml/parser/progressive_parser.class.php
badges/mybadges.php
badges/tests/badgeslib_test.php
blocks/comments/tests/events_test.php
config-dist.php
course/edit.php
course/format/lib.php
course/format/renderer.php
course/lib.php
course/rest.php
course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js
course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js
course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js
course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js
course/yui/src/dragdrop/js/resource.js
course/yui/src/toolboxes/js/resource.js
enrol/database/tests/sync_test.php
enrol/manual/externallib.php
files/tests/externallib_test.php
grade/grading/form/guide/backup/moodle2/backup_gradingform_guide_plugin.class.php
grade/grading/form/guide/backup/moodle2/restore_gradingform_guide_plugin.class.php
grade/tests/edittreelib_test.php
grade/tests/externallib_test.php
group/clientlib.js
install/lang/zh_tw/error.php
lang/en/auth.php
lang/en/badges.php
lang/en/dbtransfer.php
lang/en/moodle.php
lib/behat/form_field/behat_form_select.php
lib/classes/event/user_login_failed.php [new file with mode: 0644]
lib/classes/plugin_manager.php
lib/classes/progress/base.php [moved from backup/util/progress/core_backup_progress.class.php with 78% similarity]
lib/classes/progress/display.php [moved from backup/util/progress/core_backup_display_progress.class.php with 86% similarity]
lib/classes/progress/display_if_slow.php [moved from backup/util/progress/core_backup_display_progress_if_slow.class.php with 82% similarity]
lib/classes/progress/null.php [moved from backup/util/progress/core_backup_null_progress.class.php with 88% similarity]
lib/classes/update/deployer.php
lib/db/install.xml
lib/db/services.php
lib/db/upgrade.php
lib/dml/mssql_native_moodle_database.php
lib/dml/oci_native_moodle_database.php
lib/dml/pgsql_native_moodle_database.php
lib/dml/sqlite3_pdo_moodle_database.php
lib/dml/sqlsrv_native_moodle_database.php
lib/dtl/database_exporter.php
lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-debug.js
lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-min.js
lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms.js
lib/form/yui/src/shortforms/js/shortforms.js
lib/formslib.php
lib/grade/grade_item.php
lib/javascript-static.js
lib/moodlelib.php
lib/outputactions.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/outputrequirementslib.php
lib/pdflib.php
lib/setup.php
lib/tablelib.php
lib/tests/accesslib_test.php
lib/tests/authlib_test.php
lib/tests/behat/behat_hooks.php
lib/tests/code_test.php [deleted file]
lib/tests/completionlib_test.php
lib/tests/coursecatlib_test.php
lib/tests/messagelib_test.php
lib/tests/moodlelib_test.php
lib/tests/progress_test.php [moved from backup/util/progress/tests/progress_test.php with 81% similarity]
lib/tests/upgradelib_test.php
lib/upgrade.txt
lib/upgradelib.php
lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js
lib/yui/build/moodle-core-dock/moodle-core-dock-min.js
lib/yui/build/moodle-core-dock/moodle-core-dock.js
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-notification-alert/moodle-core-notification-alert-debug.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js
lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-debug.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-min.js
lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js
lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js
lib/yui/src/dock/js/dock.js
lib/yui/src/dock/js/tabheightmanager.js
lib/yui/src/dragdrop/js/dragdrop.js
lib/yui/src/notification/js/alert.js
lib/yui/src/notification/js/confirm.js
lib/yui/src/notification/js/dialogue.js
lib/yui/src/notification/js/exception.js
lib/yuilib/readme_moodle.txt
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/editpdf/classes/document_services.php
mod/assign/feedback/editpdf/classes/pdf.php
mod/assign/gradingtable.php
mod/assign/locallib.php
mod/assign/submission/comments/tests/events_test.php
mod/assign/tests/behat/quickgrading.feature [new file with mode: 0644]
mod/assign/tests/externallib_test.php
mod/assign/upgrade.txt
mod/assign/version.php
mod/book/tests/events_test.php
mod/chat/view.php
mod/choice/tests/events_test.php
mod/choice/view.php
mod/data/edit.php
mod/data/tests/lib_test.php
mod/feedback/analysis.php
mod/feedback/analysis_course.php
mod/feedback/complete.php
mod/feedback/complete_guest.php
mod/feedback/delete_completed.php
mod/feedback/delete_item.php
mod/feedback/delete_template.php
mod/feedback/edit.php
mod/feedback/edit_item.php
mod/feedback/import.php
mod/feedback/index.php
mod/feedback/mapcourse.php
mod/feedback/print.php
mod/feedback/show_entries.php
mod/feedback/show_entries_anonym.php
mod/feedback/show_nonrespondents.php
mod/feedback/tests/events_test.php
mod/feedback/use_templ.php
mod/feedback/view.php
mod/forum/classes/post_form.php
mod/forum/lib.php
mod/forum/tests/externallib_test.php
mod/forum/tests/lib_test.php
mod/forum/unsubscribeall.php
mod/forum/view.php
mod/glossary/deleteentry.php
mod/glossary/edit.php
mod/glossary/editcategories.php
mod/glossary/export.php
mod/glossary/exportentry.php
mod/glossary/import.php
mod/glossary/tests/events_test.php
mod/glossary/view.php
mod/lesson/import.php
mod/lti/grade.php
mod/page/view.php
mod/quiz/attempt.php
mod/quiz/locallib.php
mod/quiz/report/default.php
mod/quiz/report/statistics/classes/calculated.php
mod/quiz/report/statistics/classes/calculator.php
mod/quiz/report/statistics/lang/en/quiz_statistics.php
mod/quiz/report/statistics/report.php
mod/quiz/report/statistics/statistics_form.php
mod/quiz/report/statistics/statistics_table.php
mod/quiz/report/statistics/tests/fixtures/mdl_question_states.csv
mod/quiz/report/statistics/tests/statistics_test.php
mod/quiz/report/statistics/tests/stats_from_steps_walkthrough_test.php
mod/quiz/review.php
mod/quiz/startattempt.php
mod/quiz/styles.css
mod/quiz/summary.php
mod/scorm/tests/event_test.php
mod/survey/view.php
mod/wiki/pagelib.php
mod/wiki/tests/events_test.php
question/classes/statistics/questions/calculated.php
question/classes/statistics/questions/calculated_for_subquestion.php
question/classes/statistics/questions/calculator.php
question/preview.php
question/type/essay/db/upgradelib.php
question/type/essay/tests/upgradelibnewqe_test.php
question/upgrade.txt
question/yui/build/moodle-question-preview/moodle-question-preview-debug.js [new file with mode: 0644]
question/yui/build/moodle-question-preview/moodle-question-preview-min.js [new file with mode: 0644]
question/yui/build/moodle-question-preview/moodle-question-preview.js [new file with mode: 0644]
question/yui/src/preview/build.json [new file with mode: 0644]
question/yui/src/preview/js/preview.js [moved from question/preview.js with 63% similarity]
question/yui/src/preview/meta/preview.json [new file with mode: 0644]
repository/filepicker.js
repository/tests/repositorylib_test.php
theme/anomaly/style/general.css
theme/base/style/core.css
theme/base/style/dock.css
theme/bootstrapbase/config.php
theme/bootstrapbase/javascript/dock.js [new file with mode: 0644]
theme/bootstrapbase/less/moodle.less
theme/bootstrapbase/less/moodle/blocks.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/dock.less [new file with mode: 0644]
theme/bootstrapbase/less/moodle/modules.less
theme/bootstrapbase/style/moodle.css
theme/boxxie/style/core.css
theme/canvas/style/core.css
theme/clean/config.php
theme/formal_white/style/formal_white.css
theme/formfactor/style/mods.css
theme/magazine/style/core.css
theme/standard/style/admin.css
theme/standard/style/core.css
theme/standard/style/grade.css
theme/standard/style/modules.css
theme/standard/style/question.css
theme/upgrade.txt
theme/yui_combo.php
theme/yui_image.php
version.php

diff --git a/admin/cli/backup.php b/admin/cli/backup.php
new file mode 100644 (file)
index 0000000..2770c03
--- /dev/null
@@ -0,0 +1,119 @@
+<?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 allows to do backup.
+ *
+ * @package    core
+ * @subpackage cli
+ * @copyright  2013 Lancaster University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', 1);
+
+require(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(array(
+    'courseid' => false,
+    'courseshortname' => '',
+    'destination' => '',
+    'help' => false,
+    ), array('h' => 'help'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help'] || !($options['courseid'] || $options['courseshortname'])) {
+    $help = <<<EOL
+Perform backup of the given course.
+
+Options:
+--courseid=INTEGER          Course ID for backup.
+--courseshortname=STRING    Course shortname for backup.
+--destination=STRING        Path where to store backup file. If not set the backup
+                            will be stored within the course backup file area.
+-h, --help                  Print out this help.
+
+Example:
+\$sudo -u www-data /usr/bin/php admin/cli/backup.php --courseid=2 --destination=/moodle/backup/\n
+EOL;
+
+    echo $help;
+    die;
+}
+
+$admin = get_admin();
+if (!$admin) {
+    mtrace("Error: No admin account was found");
+    die;
+}
+
+// Do we need to store backup somewhere else?
+$dir = rtrim($options['destination'], '/');
+if (!empty($dir)) {
+    if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
+        mtrace("Destination directory does not exists or not writable.");
+        die;
+    }
+}
+
+// Check that the course exists.
+if ($options['courseid']) {
+    $course = $DB->get_record('course', array('id' => $options['courseid']), '*', MUST_EXIST);
+} else if ($options['courseshortname']) {
+    $course = $DB->get_record('course', array('shortname' => $options['courseshortname']), '*', MUST_EXIST);
+}
+
+cli_heading('Performing backup...');
+$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
+                            backup::INTERACTIVE_YES, backup::MODE_GENERAL, $admin->id);
+// Set the default filename.
+$format = $bc->get_format();
+$type = $bc->get_type();
+$id = $bc->get_id();
+$users = $bc->get_plan()->get_setting('users')->get_value();
+$anonymised = $bc->get_plan()->get_setting('anonymize')->get_value();
+$filename = backup_plan_dbops::get_default_backup_filename($format, $type, $id, $users, $anonymised);
+$bc->get_plan()->get_setting('filename')->set_value($filename);
+
+// Execution.
+$bc->finish_ui();
+$bc->execute_plan();
+$results = $bc->get_results();
+$file = $results['backup_destination']; // May be empty if file already moved to target location.
+
+// Do we need to store backup somewhere else?
+if (!empty($dir)) {
+    if ($file) {
+        mtrace("Writing " . $dir.'/'.$filename);
+        if ($file->copy_content_to($dir.'/'.$filename)) {
+            $file->delete();
+            mtrace("Backup completed.");
+        } else {
+            mtrace("Destination directory does not exist or is not writable. Leaving the backup in the course backup file area.");
+        }
+    }
+} else {
+    mtrace("Backup completed, the new file is listed in the backup area of the given course");
+}
+$bc->destroy();
+exit(0);
\ No newline at end of file
index 18d0ef5..8a87fdb 100644 (file)
@@ -68,7 +68,7 @@ if ($usersettingsform->is_cancelled()) {
     //TODO: assign capability
 
     //display successful notification
-    $notification = $OUTPUT->notification(get_string('usersettingssaved', 'webservice'), 'success');
+    $notification = $OUTPUT->notification(get_string('usersettingssaved', 'webservice'), 'notifysuccess');
 }
 
 echo $OUTPUT->header();
index 97a1eca..9fccdd2 100644 (file)
@@ -28,11 +28,17 @@ defined('MOODLE_INTERNAL') || die();
 
 
 class auth_db_testcase extends advanced_testcase {
+    /** @var string Original error log */
+    protected $oldlog;
 
     protected function init_auth_database() {
         global $DB, $CFG;
         require_once("$CFG->dirroot/auth/db/auth.php");
 
+        // Discard error logs from AdoDB.
+        $this->oldlog = ini_get('error_log');
+        ini_set('error_log', "$CFG->dataroot/testlog.log");
+
         $dbman = $DB->get_manager();
 
         set_config('extencoding', 'utf-8', 'auth/db');
@@ -133,6 +139,8 @@ class auth_db_testcase extends advanced_testcase {
         $dbman = $DB->get_manager();
         $table = new xmldb_table('auth_db_users');
         $dbman->drop_table($table);
+
+        ini_set('error_log', $this->oldlog);
     }
 
     public function test_plugin() {
index 955b81e..13cdcc0 100644 (file)
@@ -268,10 +268,8 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
         $sink->close();
 
         // Check that the event is valid.
-        $this->assertCount(2, $events);
-        $event = $events[0];
-        $this->assertInstanceOf('\core\event\user_updated', $event);
-        $event = $events[1];
+        $this->assertCount(1, $events);
+        $event = reset($events);
         $this->assertInstanceOf('\core\event\user_loggedin', $event);
         $this->assertEquals('user', $event->objecttable);
         $this->assertEquals('2', $event->objectid);
index d8ada6b..2ac377c 100644 (file)
@@ -48,12 +48,45 @@ class behat_auth extends behat_base {
      */
     public function i_log_in_as($username) {
 
-        return array(new Given('I am on homepage'),
+        // Running this step using the API rather than a chained step because
+        // we need to see if the 'Log in' link is available or we need to click
+        // the dropdown to expand the navigation bar before.
+        $this->getSession()->visit($this->locate_path('/'));
+
+        // Generic steps (we will prefix them later expanding the navigation dropdown if necessary).
+        $steps = array(
             new Given('I follow "' . get_string('login') . '"'),
             new Given('I fill in "' . get_string('username') . '" with "' . $this->escape($username) . '"'),
             new Given('I fill in "' . get_string('password') . '" with "'. $this->escape($username) . '"'),
             new Given('I press "' . get_string('login') . '"')
         );
+
+        // If Javascript is disabled we have enough with these steps.
+        if (!$this->running_javascript()) {
+            return $steps;
+        }
+
+        // Wait for the homepage to be ready.
+        $this->getSession()->wait(self::TIMEOUT * 1000, self::PAGE_READY_JS);
+
+        // Checking if we need to click the navbar button to show the navigation menu, it
+        // is hidden by default when using clean theme and a medium or small size screen size.
+
+        // The DOM and the JS should be all ready and loaded. Running without spinning
+        // as this is a widely used step and we can not spend time here trying to see
+        // a DOM node that is not always there (at the moment clean is not even the
+        // default theme...).
+        $navbuttonjs = "return (
+            Y.one('.btn-navbar') &&
+            Y.one('.btn-navbar').getComputedStyle('display') !== 'none'
+        )";
+
+        // Adding an extra click we need to show the 'Log in' link.
+        if ($this->getSession()->getDriver()->evaluateScript($navbuttonjs)) {
+            array_unshift($steps, new Given('I click on ".btn-navbar" "css_element"'));
+        }
+
+        return $steps;
     }
 
     /**
index f4c4e40..ed50101 100644 (file)
@@ -4,8 +4,14 @@ Feature: Authentication
   As a user
   I need to log into the system
 
-  Scenario: Log in with the predefined admin user
+  Scenario: Log in with the predefined admin user with Javascript disabled
     Given I log in as "admin"
+    Then I should see "You are logged in as Admin User"
+
+  @javascript
+  Scenario: Log in with the predefined admin user with Javascript enabled
+    Given I log in as "admin"
+    Then I should see "You are logged in as Admin User"
 
   Scenario: Log in as an existing admin user filling the form
     Given the following "users" exists:
index 0f3bf7a..688726d 100644 (file)
@@ -97,7 +97,7 @@ echo $OUTPUT->header();
 
 // Prepare a progress bar which can display optionally during long-running
 // operations while setting up the UI.
-$slowprogress = new core_backup_display_progress_if_slow(get_string('preparingui', 'backup'));
+$slowprogress = new \core\progress\display_if_slow(get_string('preparingui', 'backup'));
 
 $previous = optional_param('previous', false, PARAM_BOOL);
 if ($backup->get_stage() == backup_ui::STAGE_SCHEMA && !$previous) {
@@ -121,7 +121,7 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
     // Display an extra backup step bar so that we can show the 'processing' step first.
     echo html_writer::start_div('', array('id' => 'executionprogress'));
     echo $renderer->progress_bar($backup->get_progress_bar());
-    $backup->get_controller()->set_progress(new core_backup_display_progress());
+    $backup->get_controller()->set_progress(new \core\progress\display());
 
     // Prepare logger and add to end of chain.
     $logger = new core_backup_html_logger($CFG->debugdeveloper ? backup::LOG_DEBUG : backup::LOG_INFO);
index 345831c..cd124e5 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_backup_null_progress();
+        $this->progress = new \core\progress\null();
 
         // Instantiate the output_controller singleton and active it if interactive and inmediate
         $oc = output_controller::get_instance();
index 4b017b7..0998796 100644 (file)
@@ -24,7 +24,7 @@
  */
 abstract class base_controller extends backup implements loggable {
     /**
-     * @var core_backup_progress Progress reporting object.
+     * @var \core\progress\base Progress reporting object.
      */
     protected $progress;
 
@@ -37,7 +37,7 @@ abstract class base_controller extends backup implements loggable {
      * Gets the progress reporter, which can be used to report progress within
      * the backup or restore process.
      *
-     * @return core_backup_progress Progress reporting object
+     * @return \core\progress\base Progress reporting object
      */
     public function get_progress() {
         return $this->progress;
@@ -46,9 +46,9 @@ abstract class base_controller extends backup implements loggable {
     /**
      * Sets the progress reporter.
      *
-     * @param core_backup_progress $progress Progress reporting object
+     * @param \core\progress\base $progress Progress reporting object
      */
-    public function set_progress(core_backup_progress $progress) {
+    public function set_progress(\core\progress\base $progress) {
         $this->progress = $progress;
     }
 
@@ -82,4 +82,4 @@ abstract class base_controller extends backup implements loggable {
     public function log($message, $level, $a = null, $depth = null, $display = false) {
         backup_helper::log($message, $level, $a, $depth, $display, $this->logger);
     }
-}
\ No newline at end of file
+}
index f4d3958..822992c 100644 (file)
@@ -70,10 +70,10 @@ class restore_controller extends base_controller {
      * @param int $mode backup::MODE_[ GENERAL | HUB | IMPORT | SAMESITE ]
      * @param int $userid
      * @param int $target backup::TARGET_[ NEW_COURSE | CURRENT_ADDING | CURRENT_DELETING | EXISTING_ADDING | EXISTING_DELETING ]
-     * @param core_backup_progress $progress Optional progress monitor
+     * @param \core\progress\base $progress Optional progress monitor
      */
     public function __construct($tempdir, $courseid, $interactive, $mode, $userid, $target,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
         $this->tempdir = $tempdir;
         $this->courseid = $courseid;
         $this->interactive = $interactive;
@@ -111,7 +111,7 @@ class restore_controller extends base_controller {
         if ($progress) {
             $this->progress = $progress;
         } else {
-            $this->progress = new core_backup_null_progress();
+            $this->progress = new \core\progress\null();
         }
         $this->progress->start_progress('Constructing restore_controller');
 
index 7a32057..b2f34ab 100644 (file)
@@ -95,7 +95,7 @@ if ($backup->get_stage() == backup_ui::STAGE_FINAL) {
     echo $renderer->progress_bar($backup->get_progress_bar());
 
     // Start the progress display - we split into 2 chunks for backup and restore.
-    $progress = new core_backup_display_progress();
+    $progress = new \core\progress\display();
     $progress->start_progress('', 2);
     $backup->get_controller()->set_progress($progress);
 
index 46862a7..4e79cc5 100644 (file)
@@ -1748,13 +1748,13 @@ class backup_zip_contents extends backup_execution_step implements file_progress
         // Start tracking progress if necessary.
         if (!$this->startedprogress) {
             $reporter->start_progress('extract_file_to_dir', ($max == file_progress::INDETERMINATE)
-                    ? core_backup_progress::INDETERMINATE : $max);
+                    ? \core\progress\base::INDETERMINATE : $max);
             $this->startedprogress = true;
         }
 
         // Pass progress through to whatever handles it.
         $reporter->progress(($progress == file_progress::INDETERMINATE)
-                ? core_backup_progress::INDETERMINATE : $progress);
+                ? \core\progress\base::INDETERMINATE : $progress);
      }
 }
 
index cba5771..1045d94 100644 (file)
@@ -3564,7 +3564,7 @@ class restore_create_question_files extends restore_execution_step {
 
         // Track progress, as this task can take a long time.
         $progress = $this->task->get_progress();
-        $progress->start_progress($this->get_name(), core_backup_progress::INDETERMINATE);
+        $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
 
         // Let's process only created questions
         $questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
index 9612163..3ac6f8d 100644 (file)
@@ -34,7 +34,7 @@ echo $OUTPUT->header();
 
 // Prepare a progress bar which can display optionally during long-running
 // operations while setting up the UI.
-$slowprogress = new core_backup_display_progress_if_slow(get_string('preparingui', 'backup'));
+$slowprogress = new \core\progress\display_if_slow(get_string('preparingui', 'backup'));
 
 // Overall, allow 10 units of progress.
 $slowprogress->start_progress('', 10);
@@ -90,7 +90,7 @@ $slowprogress->end_progress();
 
 if (!$restore->is_independent()) {
     // Use a temporary (disappearing) progress bar to show the precheck progress if any.
-    $precheckprogress = new core_backup_display_progress_if_slow(get_string('preparingdata', 'backup'));
+    $precheckprogress = new \core\progress\display_if_slow(get_string('preparingdata', 'backup'));
     $restore->get_controller()->set_progress($precheckprogress);
     if ($restore->get_stage() == restore_ui::STAGE_PROCESS && !$restore->requires_substage()) {
         try {
@@ -99,7 +99,7 @@ if (!$restore->is_independent()) {
             // Show the current restore state (header with bolded item).
             echo $renderer->progress_bar($restore->get_progress_bar());
             // Start displaying the actual progress bar percentage.
-            $restore->get_controller()->set_progress(new core_backup_display_progress());
+            $restore->get_controller()->set_progress(new \core\progress\display());
             // Prepare logger.
             $logger = new core_backup_html_logger($CFG->debugdeveloper ? backup::LOG_DEBUG : backup::LOG_INFO);
             $restore->get_controller()->add_logger($logger);
index d9715d1..a6cfb55 100644 (file)
@@ -350,10 +350,10 @@ abstract class backup_controller_dbops extends backup_dbops {
      * to track progress in processing (in case this task takes a long time).
      *
      * @param string $backupid Backup ID
-     * @param core_backup_progress $progress Optional progress monitor
+     * @param \core\progress\base $progress Optional progress monitor
      */
     public static function get_moodle_backup_information($backupid,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
 
         // Start tracking progress if required (for load_controller).
         if ($progress) {
index 61a903a..6202906 100644 (file)
@@ -133,9 +133,9 @@ abstract class backup_structure_dbops extends backup_dbops {
      *
      * @param string $backupid Backup ID
      * @param string $itemname Item name
-     * @param core_backup_progress $progress Progress tracker
+     * @param \core\progress\base $progress Progress tracker
      */
-    public static function move_annotations_to_final($backupid, $itemname, core_backup_progress $progress) {
+    public static function move_annotations_to_final($backupid, $itemname, \core\progress\base $progress) {
         global $DB;
         $progress->start_progress('move_annotations_to_final');
         $rs = $DB->get_recordset('backup_ids_temp', array('backupid' => $backupid, 'itemname' => $itemname));
index 00fb8f3..079b63c 100644 (file)
@@ -112,10 +112,10 @@ abstract class restore_dbops {
      *
      * @param string $restoreid Restore id
      * @param string $inforeffile File path
-     * @param core_backup_progress $progress Progress tracker
+     * @param \core\progress\base $progress Progress tracker
      */
     public static function load_inforef_to_tempids($restoreid, $inforeffile,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
 
         if (!file_exists($inforeffile)) { // Shouldn't happen ever, but...
             throw new backup_helper_exception('missing_inforef_xml_file', $inforeffile);
@@ -123,7 +123,7 @@ abstract class restore_dbops {
 
         // Set up progress tracking (indeterminate).
         if (!$progress) {
-            $progress = new core_backup_null_progress();
+            $progress = new \core\progress\null();
         }
         $progress->start_progress('Loading inforef.xml file');
 
@@ -419,10 +419,10 @@ abstract class restore_dbops {
      *
      * @param string $restoreid Restore id
      * @param string $usersfile File path
-     * @param core_backup_progress $progress Progress tracker
+     * @param \core\progress\base $progress Progress tracker
      */
     public static function load_users_to_tempids($restoreid, $usersfile,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
 
         if (!file_exists($usersfile)) { // Shouldn't happen ever, but...
             throw new backup_helper_exception('missing_users_xml_file', $usersfile);
@@ -430,7 +430,7 @@ abstract class restore_dbops {
 
         // Set up progress tracking (indeterminate).
         if (!$progress) {
-            $progress = new core_backup_null_progress();
+            $progress = new \core\progress\null();
         }
         $progress->start_progress('Loading users into temporary table');
 
@@ -861,13 +861,13 @@ abstract class restore_dbops {
      * @param int|null $olditemid
      * @param int|null $forcenewcontextid explicit value for the new contextid (skip mapping)
      * @param bool $skipparentitemidctxmatch
-     * @param core_backup_progress $progress Optional progress reporter
+     * @param \core\progress\base $progress Optional progress reporter
      * @return array of result object
      */
     public static function send_files_to_pool($basepath, $restoreid, $component, $filearea,
             $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null,
             $forcenewcontextid = null, $skipparentitemidctxmatch = false,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
         global $DB, $CFG;
 
         $backupinfo = backup_general_helper::get_backup_information(basename($basepath));
@@ -1084,10 +1084,10 @@ abstract class restore_dbops {
      * @param string $basepath Base path of unzipped backup
      * @param string $restoreid Restore ID
      * @param int $userid Default userid for files
-     * @param core_backup_progress $progress Object used for progress tracking
+     * @param \core\progress\base $progress Object used for progress tracking
      */
     public static function create_included_users($basepath, $restoreid, $userid,
-            core_backup_progress $progress) {
+            \core\progress\base $progress) {
         global $CFG, $DB;
         $progress->start_progress('Creating included users');
 
@@ -1481,10 +1481,10 @@ abstract class restore_dbops {
      * @param int $courseid Course id
      * @param int $userid User id
      * @param bool $samesite True if restore is to same site
-     * @param core_backup_progress $progress Progress reporter
+     * @param \core\progress\base $progress Progress reporter
      */
     public static function precheck_included_users($restoreid, $courseid, $userid, $samesite,
-            core_backup_progress $progress) {
+            \core\progress\base $progress) {
         global $CFG, $DB;
 
         // To return any problem found
@@ -1570,10 +1570,10 @@ abstract class restore_dbops {
      * @param int $courseid Course id
      * @param int $userid User id
      * @param bool $samesite True if restore is to same site
-     * @param core_backup_progress $progress Optional progress tracker
+     * @param \core\progress\base $progress Optional progress tracker
      */
     public static function process_included_users($restoreid, $courseid, $userid, $samesite,
-            core_backup_progress $progress = null) {
+            \core\progress\base $progress = null) {
         global $DB;
 
         // Just let precheck_included_users() to do all the hard work
index c2d59aa..963e471 100644 (file)
@@ -46,9 +46,9 @@ abstract class backup_helper {
      * progress reports.
      *
      * @param string $backupid Backup id
-     * @param core_backup_progress $progress Optional progress reporting object
+     * @param \core\progress\base $progress Optional progress reporting object
      */
-    static public function clear_backup_dir($backupid, core_backup_progress $progress = null) {
+    static public function clear_backup_dir($backupid, \core\progress\base $progress = null) {
         global $CFG;
         if (!self::delete_dir_contents($CFG->tempdir . '/backup/' . $backupid, '', $progress)) {
             throw new backup_helper_exception('cannot_empty_backup_temp_dir');
@@ -63,9 +63,9 @@ abstract class backup_helper {
      * progress reports.
      *
      * @param string $backupid Backup id
-     * @param core_backup_progress $progress Optional progress reporting object
+     * @param \core\progress\base $progress Optional progress reporting object
      */
-     static public function delete_backup_dir($backupid, core_backup_progress $progress = null) {
+     static public function delete_backup_dir($backupid, \core\progress\base $progress = null) {
          global $CFG;
          self::clear_backup_dir($backupid, $progress);
          return rmdir($CFG->tempdir . '/backup/' . $backupid);
@@ -81,9 +81,9 @@ abstract class backup_helper {
      *
      * @param string $dir Directory to delete
      * @param string $excludedir Exclude this directory
-     * @param core_backup_progress $progress Optional progress reporting object
+     * @param \core\progress\base $progress Optional progress reporting object
      */
-    static public function delete_dir_contents($dir, $excludeddir='', core_backup_progress $progress = null) {
+    static public function delete_dir_contents($dir, $excludeddir='', \core\progress\base $progress = null) {
         global $CFG;
 
         if ($progress) {
@@ -154,9 +154,9 @@ abstract class backup_helper {
      * progress reports.
      *
      * @param int $deletefrom Time to delete from
-     * @param core_backup_progress $progress Optional progress reporting object
+     * @param \core\progress\base $progress Optional progress reporting object
      */
-    static public function delete_old_backup_dirs($deletefrom, core_backup_progress $progress = null) {
+    static public function delete_old_backup_dirs($deletefrom, \core\progress\base $progress = null) {
         global $CFG;
 
         $status = true;
@@ -213,12 +213,12 @@ abstract class backup_helper {
      *
      * @param int $backupid
      * @param string $filepath zip file containing the backup
-     * @param core_backup_progress $progress Optional progress monitor
+     * @param \core\progress\base $progress Optional progress monitor
      * @return stored_file if created, null otherwise
      *
      * @throws moodle_exception in case of any problems
      */
-    static public function store_backup_file($backupid, $filepath, core_backup_progress $progress = null) {
+    static public function store_backup_file($backupid, $filepath, \core\progress\base $progress = null) {
         global $CFG;
 
         // First of all, get some information from the backup_controller to help us decide
index 2a4f222..87d237d 100644 (file)
@@ -72,10 +72,6 @@ require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/core_backup_html_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress_if_slow.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/setting_dependency.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/base_setting.class.php');
 require_once($CFG->dirroot . '/backup/util/settings/backup_setting.class.php');
index fd223c5..ec0f8eb 100644 (file)
@@ -61,10 +61,6 @@ require_once($CFG->dirroot . '/backup/util/loggers/file_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/core_backup_html_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/database_logger.class.php');
 require_once($CFG->dirroot . '/backup/util/loggers/output_indented_logger.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_null_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress.class.php');
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_display_progress_if_slow.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/backup_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/factories/restore_factory.class.php');
 require_once($CFG->dirroot . '/backup/util/helper/backup_helper.class.php');
index 3d537d2..2fcb469 100644 (file)
@@ -91,7 +91,7 @@ class backup_plan extends base_plan implements loggable {
      * Gets the progress reporter, which can be used to report progress within
      * the backup or restore process.
      *
-     * @return core_backup_progress Progress reporting object
+     * @return \core\progress\base Progress reporting object
      */
     public function get_progress() {
         return $this->controller->get_progress();
index c465b2e..07f3805 100644 (file)
@@ -185,7 +185,7 @@ abstract class base_plan implements checksumable, executable {
      * Gets the progress reporter, which can be used to report progress within
      * the backup or restore process.
      *
-     * @return core_backup_progress Progress reporting object
+     * @return \core\progress\base Progress reporting object
      */
     public abstract function get_progress();
 
index e167b89..19cb072 100644 (file)
@@ -126,7 +126,7 @@ abstract class base_task implements checksumable, executable, loggable {
      * Gets the progress reporter, which can be used to report progress within
      * the backup or restore process.
      *
-     * @return core_backup_progress Progress reporting object
+     * @return \core\progress\base Progress reporting object
      */
     public function get_progress() {
         return $this->plan->get_progress();
index e173e0e..6f5b5b5 100644 (file)
@@ -98,7 +98,7 @@ class restore_plan extends base_plan implements loggable {
      * Gets the progress reporter, which can be used to report progress within
      * the backup or restore process.
      *
-     * @return core_backup_progress Progress reporting object
+     * @return \core\progress\base Progress reporting object
      */
     public function get_progress() {
         return $this->controller->get_progress();
index 300f92d..e4685b0 100644 (file)
@@ -103,7 +103,7 @@ abstract class restore_structure_step extends restore_step {
 
         // Set up progress tracking.
         $progress = $this->get_task()->get_progress();
-        $progress->start_progress($this->get_name(), core_backup_progress::INDETERMINATE);
+        $progress->start_progress($this->get_name(), \core\progress\base::INDETERMINATE);
         $xmlparser->set_progress($progress);
 
         // And process it, dispatch to target methods in step will start automatically
@@ -229,7 +229,7 @@ abstract class restore_structure_step extends restore_step {
         // the execute() method here, which does set up progress like this.)
         $progress = $this->get_task()->get_progress();
         if (!$progress->is_in_progress_section() ||
-                $progress->get_current_max() !== core_backup_progress::INDETERMINATE) {
+                $progress->get_current_max() !== \core\progress\base::INDETERMINATE) {
             $progress = null;
         }
 
index 88cfeca..2cc41dc 100644 (file)
@@ -38,7 +38,7 @@ class backup_structure_processor extends base_processor {
     protected $vars;   // array of backup::VAR_XXX => helper value pairs to be used by source specifications
 
     /**
-     * @var core_backup_progress Progress tracker (null if none)
+     * @var \core\progress\base Progress tracker (null if none)
      */
     protected $progress;
 
@@ -46,9 +46,9 @@ class backup_structure_processor extends base_processor {
      * Constructor.
      *
      * @param xml_writer $writer XML writer to save data
-     * @param core_backup_progress $progress Progress tracker (optional)
+     * @param c\core\progress\base$progress Progress tracker (optional)
      */
-    public function __construct(xml_writer $writer, core_backup_progress $progress = null) {
+    public function __construct(xml_writer $writer, \core\progress\base $progress = null) {
         $this->writer = $writer;
         $this->progress = $progress;
         $this->vars = array();
index 251cbc1..cd74972 100644 (file)
@@ -51,7 +51,7 @@ class restore_ui extends base_ui {
     protected $stage = null;
 
     /**
-     * @var core_backup_progress Progress indicator (where there is no controller)
+     * @var \core\progress\base Progress indicator (where there is no controller)
      */
     protected $progressreporter = null;
 
@@ -146,11 +146,11 @@ class restore_ui extends base_ui {
      * there are long-running tasks even though there is no restore controller
      * in use.
      *
-     * @return core_backup_null_progress
+     * @return \core\progress\null
      */
     public function get_progress_reporter() {
         if (!$this->progressreporter) {
-            $this->progressreporter = new core_backup_null_progress();
+            $this->progressreporter = new \core\progress\null();
         }
         return $this->progressreporter;
     }
@@ -158,9 +158,9 @@ class restore_ui extends base_ui {
     /**
      * Sets the progress reporter that will be returned by get_progress_reporter.
      *
-     * @param core_backup_progress $progressreporter Progress reporter
+     * @param c\core\progress\base$progressreporter Progress reporter
      */
-    public function set_progress_reporter(core_backup_progress $progressreporter) {
+    public function set_progress_reporter(\core\progress\base $progressreporter) {
         $this->progressreporter = $progressreporter;
     }
 
index ef559fd..63dc00e 100644 (file)
@@ -94,7 +94,7 @@ abstract class restore_ui_stage extends base_ui_stage {
  */
 abstract class restore_ui_independent_stage {
     /**
-     * @var core_backup_progress Optional progress reporter
+     * @var \core\progress\base Optional progress reporter
      */
     private $progressreporter;
 
@@ -117,11 +117,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_backup_null_progress
+     * @return \core\progress\null
      */
     public function get_progress_reporter() {
         if (!$this->progressreporter) {
-            $this->progressreporter = new core_backup_null_progress();
+            $this->progressreporter = new \core\progress\null();
         }
         return $this->progressreporter;
     }
@@ -129,9 +129,9 @@ abstract class restore_ui_independent_stage {
     /**
      * Sets the progress reporter that will be returned by get_progress_reporter.
      *
-     * @param core_backup_progress $progressreporter Progress reporter
+     * @param \core\progress\base $progressreporter Progress reporter
      */
-    public function set_progress_reporter(core_backup_progress $progressreporter) {
+    public function set_progress_reporter(\core\progress\base $progressreporter) {
         $this->progressreporter = $progressreporter;
     }
 
@@ -272,13 +272,13 @@ class restore_ui_stage_confirm extends restore_ui_independent_stage implements f
         // Start tracking progress if necessary.
         if (!$this->startedprogress) {
             $reporter->start_progress('extract_file_to_dir',
-                    ($max == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $max);
+                    ($max == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $max);
             $this->startedprogress = true;
         }
 
         // Pass progress through to whatever handles it.
         $reporter->progress(
-                ($progress == file_progress::INDETERMINATE) ? core_backup_progress::INDETERMINATE : $progress);
+                ($progress == file_progress::INDETERMINATE) ? \core\progress\base::INDETERMINATE : $progress);
     }
 
     /**
index 882852a..a5ebc4d 100644 (file)
@@ -29,7 +29,7 @@
  * attributes and case folding and works only with UTF-8 content. It's one
  * progressive push parser because, intead of loading big crunchs of information
  * in memory, it "publishes" (pushes) small information in a "propietary array format" througt
- * the corresponding @progressive_parser_procesor, that will be the responsibe for
+ * the corresponding @progressive_parser_processor, that will be the responsibe for
  * returning information into handy formats to higher levels.
  *
  * Note that, while this progressive parser is able to process any XML file, it is
@@ -37,7 +37,7 @@
  * the expected behaviour) so information belonging to the same path can be returned in
  * different chunks if there are inner levels/paths in the middle. Be warned!
  *
- * The "propietary array format" that the parser publishes to the @progressive_parser_procesor
+ * The "propietary array format" that the parser publishes to the @progressive_parser_processor
  * is this:
  *    array (
  *        'path' => path where the tags belong to,
@@ -56,7 +56,11 @@ class progressive_parser {
     protected $xml_parser; // PHP's low level XML SAX parser
     protected $file;       // full path to file being progressively parsed | => mutually exclusive
     protected $contents;   // contents being progressively parsed          |
-    protected $procesor;   // progressive_parser_procesor to be used to publish processed information
+
+    /**
+     * @var progressive_parser_processor to be used to publish processed information
+     */
+    protected $processor;
 
     protected $level;      // level of the current tag
     protected $path;       // path of the current tag
@@ -68,7 +72,7 @@ class progressive_parser {
     protected $currtag;    // name/value/attributes of the tag being processed
 
     /**
-     * @var core_backup_progress Progress tracker called for each action
+     * @var \core\progress\base Progress tracker called for each action
      */
     protected $progress;
 
@@ -129,9 +133,9 @@ class progressive_parser {
      *
      * The caller should have already called start_progress on the progress tracker.
      *
-     * @param core_backup_progress $progress Progress tracker
+     * @param \core\progress\base $progress Progress tracker
      */
-    public function set_progress(core_backup_progress $progress) {
+    public function set_progress(\core\progress\base $progress) {
         $this->progress = $progress;
     }
 
index d944e73..4fee85d 100644 (file)
@@ -42,7 +42,14 @@ if (empty($CFG->enablebadges)) {
     print_error('badgesdisabled', 'badges');
 }
 
+$url = new moodle_url('/badges/mybadges.php');
+$PAGE->set_url($url);
+
 if (isguestuser()) {
+    $PAGE->set_context(context_system::instance());
+    echo $OUTPUT->header();
+    echo $OUTPUT->box(get_string('error:guestuseraccess', 'badges'), 'notifyproblem');
+    echo $OUTPUT->footer();
     die();
 }
 
@@ -80,9 +87,6 @@ if ($hide) {
 $context = context_user::instance($USER->id);
 require_capability('moodle/badges:manageownbadges', $context);
 
-$url = new moodle_url('/badges/mybadges.php');
-
-$PAGE->set_url($url);
 $PAGE->set_context($context);
 
 $title = get_string('mybadges', 'badges');
index 97b3271..aa074b4 100644 (file)
@@ -233,7 +233,7 @@ class core_badges_badgeslib_testcase extends advanced_testcase {
         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_OVERALL, 'badgeid' => $badge->id));
         $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY));
         $criteria_overall = award_criteria::build(array('criteriatype' => BADGE_CRITERIA_TYPE_ACTIVITY, 'badgeid' => $badge->id));
-        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_'.$this->module->id => $this->module->id));
+        $criteria_overall->save(array('agg' => BADGE_CRITERIA_AGGREGATION_ANY, 'module_'.$this->module->cmid => $this->module->cmid));
 
         // Set completion for forum activity.
         $c = new completion_info($this->course);
index cb4a1c5..1e7aae4 100644 (file)
@@ -88,7 +88,7 @@ class block_comments_events_testcase extends advanced_testcase {
         $this->assertEquals($url, $event->get_url());
 
         // Comments when block is on module (wiki) page.
-        $context = context_module::instance($this->wiki->id);
+        $context = context_module::instance($this->wiki->cmid);
         $args = new stdClass;
         $args->context   = $context;
         $args->course    = $this->course;
@@ -111,7 +111,7 @@ class block_comments_events_testcase extends advanced_testcase {
         // Checking that the event contains the expected values.
         $this->assertInstanceOf('\block_comments\event\comment_created', $event);
         $this->assertEquals($context, $event->get_context());
-        $url = new moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->id));
+        $url = new moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->cmid));
         $this->assertEquals($url, $event->get_url());
         $this->assertEventContextNotUsed($event);
     }
@@ -153,7 +153,7 @@ class block_comments_events_testcase extends advanced_testcase {
         $this->assertEquals($url, $event->get_url());
 
         // Comments when block is on module (wiki) page.
-        $context = context_module::instance($this->wiki->id);
+        $context = context_module::instance($this->wiki->cmid);
         $args = new stdClass;
         $args->context   = $context;
         $args->course    = $this->course;
@@ -177,7 +177,7 @@ class block_comments_events_testcase extends advanced_testcase {
         // Checking that the event contains the expected values.
         $this->assertInstanceOf('\block_comments\event\comment_deleted', $event);
         $this->assertEquals($context, $event->get_context());
-        $url = new moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->id));
+        $url = new moodle_url('/mod/wiki/view.php', array('id' => $this->wiki->cmid));
         $this->assertEquals($url, $event->get_url());
         $this->assertEventContextNotUsed($event);
     }
index b125d6a..1f59f8e 100644 (file)
@@ -681,9 +681,11 @@ $CFG->admin = 'admin';
 // Example:
 //   $CFG->behat_additionalfeatures = array('/home/developer/code/wipfeatures');
 //
-// You can make behat save a screenshot when a scenario fails.
+// You can make behat save several dumps when a scenario fails. The dumps currently saved are:
+// * a dump of the DOM in it's state at the time of failure; and
+// * a screenshot (JavaScript is required for the screenshot functionality, so not all browsers support this option)
 // Example:
-//   $CFG->behat_screenshots_path = '/my/path/to/save/screenshots';
+//   $CFG->behat_faildump_path = '/my/path/to/save/failure/dumps';
 //
 //=========================================================================
 // 12. DEVELOPER DATA GENERATOR
index f257f91..b88ab77 100644 (file)
@@ -45,8 +45,11 @@ if ($id) {
         print_error('cannoteditsiteform');
     }
 
-    $course = course_get_format($id)->get_course();
+    // Login to the course and retrieve also all fields defined by course format.
+    $course = get_course($id);
     require_login($course);
+    $course = course_get_format($course)->get_course();
+
     $category = $DB->get_record('course_categories', array('id'=>$course->category), '*', MUST_EXIST);
     $coursecontext = context_course::instance($course->id);
     require_capability('moodle/course:update', $coursecontext);
index 38c4a69..a68ce5a 100644 (file)
@@ -237,14 +237,21 @@ abstract class format_base {
         if ($this->course === false) {
             $this->course = get_course($this->courseid);
             $options = $this->get_format_options();
+            $dbcoursecolumns = null;
             foreach ($options as $optionname => $optionvalue) {
-                if (!isset($this->course->$optionname)) {
-                    $this->course->$optionname = $optionvalue;
-                } else {
-                    debugging('The option name '.$optionname.' in course format '.$this->format.
-                        ' is invalid because the field with the same name exists in {course} table',
-                        DEBUG_DEVELOPER);
+                if (isset($this->course->$optionname)) {
+                    // Course format options must not have the same names as existing columns in db table "course".
+                    if (!isset($dbcoursecolumns)) {
+                        $dbcoursecolumns = $DB->get_columns('course');
+                    }
+                    if (isset($dbcoursecolumns[$optionname])) {
+                        debugging('The option name '.$optionname.' in course format '.$this->format.
+                            ' is invalid because the field with the same name exists in {course} table',
+                            DEBUG_DEVELOPER);
+                        continue;
+                    }
                 }
+                $this->course->$optionname = $optionvalue;
             }
         }
         return $this->course;
index b719a13..edc7d87 100644 (file)
@@ -373,7 +373,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 if ($cancomplete && $completioninfo->is_enabled($thismod) != COMPLETION_TRACKING_NONE) {
                     $total++;
                     $completiondata = $completioninfo->get_data($thismod, true);
-                    if ($completiondata->completionstate == COMPLETION_COMPLETE) {
+                    if ($completiondata->completionstate == COMPLETION_COMPLETE ||
+                            $completiondata->completionstate == COMPLETION_COMPLETE_PASS) {
                         $complete++;
                     }
                 }
index aade33b..1ae3e94 100644 (file)
@@ -3238,11 +3238,15 @@ function include_course_ajax($course, $usedmodules = array(), $enabledmodules =
             'clicktochangeinbrackets',
             'markthistopic',
             'markedthistopic',
-            'move',
             'movesection',
+            'movecoursemodule',
+            'movecoursesection',
             'movecontent',
             'tocontent',
-            'emptydragdropregion'
+            'emptydragdropregion',
+            'afterresource',
+            'aftersection',
+            'totopofsection',
         ), 'moodle');
 
     // Include section-specific strings for formats which support sections.
index 96dbbd2..1aeca15 100644 (file)
@@ -146,7 +146,7 @@ switch($requestmethod) {
                         }
 
                         $isvisible = moveto_module($cm, $section, $beforemod);
-                        echo json_encode(array('visible' => $isvisible));
+                        echo json_encode(array('visible' => (bool) $isvisible));
                         break;
                     case 'gettitle':
                         require_capability('moodle/course:manageactivities', $modcontext);
index 1f1a6ae..d7c5e0e 100644 (file)
Binary files a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js and b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-debug.js differ
index 90f2913..8a8b5b6 100644 (file)
Binary files a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js and b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop-min.js differ
index 795c5bb..3dae51c 100644 (file)
Binary files a/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js and b/course/yui/build/moodle-course-dragdrop/moodle-course-dragdrop.js differ
index 7eb72a7..fe4ad92 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-debug.js differ
index 7a2e8e5..325324e 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes-min.js differ
index 7eb72a7..fe4ad92 100644 (file)
Binary files a/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js and b/course/yui/build/moodle-course-toolboxes/moodle-course-toolboxes.js differ
index 6523e8b..d1ff0db 100644 (file)
@@ -14,7 +14,16 @@ Y.extend(DRAGRESOURCE, M.core.dragdrop, {
         this.groups = ['resource'];
         this.samenodeclass = CSS.ACTIVITY;
         this.parentnodeclass = CSS.SECTION;
-        this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+        this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+
+        this.samenodelabel = {
+            identifier: 'afterresource',
+            component: 'moodle'
+        };
+        this.parentnodelabel = {
+            identifier: 'totopofsection',
+            component: 'moodle'
+        };
 
         // Go through all sections
         var sectionlistselector = M.course.format.get_section_selector(Y);
index 2e98bdf..b3f9e99 100644 (file)
@@ -659,26 +659,43 @@ Y.extend(RESOURCETOOLBOX, TOOLBOX, {
     },
 
     /**
-     * Set the visibility of the current resource (identified by the element) to match the hidden parameter (this is not
-     * a toggle).
+     * Set the visibility of the specified resource to match the visible parameter.
      *
-     * Only changes the visibility in the browser (no ajax update).
+     * Note: This is not a toggle function and only changes the visibility
+     * in the browser (no ajax update is performed).
      *
      * @method set_visibility_resource_ui
-     * @param {Object} args An object with 'element' being the A node containing the resource and 'visible' being the
-     * state that the visibility should be set to.
+     * @param {object} args An object containing the required information to trigger a change.
+     * @param {Node} args.element The resource to toggle
+     * @param {Boolean} args.visible The target visibility
      */
     set_visibility_resource_ui: function(args) {
         var element = args.element,
-            shouldbevisible = args.visible,
-            buttonnode = element.one(SELECTOR.SHOW),
-            visible = (buttonnode === null),
-            action = 'show';
-        if (visible) {
-            buttonnode = element.one(SELECTOR.HIDE);
-            action = 'hide';
-        }
-        if (visible !== shouldbevisible) {
+            buttonnode = element.one(SELECTOR.HIDE),
+            // By default we assume that the item is visible and we're going to hide it.
+            currentVisibility = true,
+            targetVisibility = false;
+
+        if (!buttonnode) {
+            // If the buttonnode was not found, try to find the HIDE button
+            // and change the target visibility setting to false.
+            buttonnode = element.one(SELECTOR.SHOW);
+            currentVisibility = false;
+            targetVisibility = true;
+        }
+
+        if (typeof args.visible !== 'undefined') {
+            // If we were provided with a visibility argument, use that instead.
+            targetVisibility = args.visible;
+        }
+
+        // Only trigger a change if necessary.
+        if (currentVisibility !== targetVisibility) {
+            var action = 'hide';
+            if (targetVisibility) {
+                action = 'show';
+            }
+
             this.handle_resource_dim(buttonnode, element, action);
         }
     }
index e46d1b1..9e73cc9 100644 (file)
@@ -31,9 +31,16 @@ class enrol_database_testcase extends advanced_testcase {
     protected static $users = array();
     protected static $roles = array();
 
+    /** @var string Original error log */
+    protected $oldlog;
+
     protected function init_enrol_database() {
         global $DB, $CFG;
 
+        // Discard error logs from AdoDB.
+        $this->oldlog = ini_get('error_log');
+        ini_set('error_log', "$CFG->dataroot/testlog.log");
+
         $dbman = $DB->get_manager();
 
         set_config('dbencoding', 'utf-8', 'enrol_database');
@@ -160,6 +167,8 @@ class enrol_database_testcase extends advanced_testcase {
         self::$courses = null;
         self::$users = null;
         self::$roles = null;
+
+        ini_set('error_log', $this->oldlog);
     }
 
     protected function reset_enrol_database() {
index 34de18f..7cec282 100644 (file)
@@ -109,6 +109,7 @@ class enrol_manual_external extends external_api {
             }
 
             // Check manual enrolment plugin instance is enabled/exist.
+            $instance = null;
             $enrolinstances = enrol_get_instances($enrolment['courseid'], true);
             foreach ($enrolinstances as $courseenrolinstance) {
               if ($courseenrolinstance->enrol == "manual") {
index 36383fb..3922c64 100644 (file)
@@ -223,7 +223,7 @@ class core_files_externallib_testcase extends advanced_testcase {
         // Insert the information about the file.
         $contentid = $DB->insert_record('data_content', $datacontent);
         // Required information for uploading a file.
-        $context = context_module::instance($module->id);
+        $context = context_module::instance($module->cmid);
         $usercontext = context_user::instance($USER->id);
         $component = 'mod_data';
         $filearea = 'content';
@@ -251,6 +251,7 @@ class core_files_externallib_testcase extends advanced_testcase {
         $testfilelisting = core_files_external::get_files($context->id, $component, $filearea, $itemid, '/', $filename);
 
         // With the information that we have provided we should get an object exactly like the one below.
+        $coursecontext = context_course::instance($course->id);
         $testdata = array();
         $testdata['parents'] = array();
         $testdata['parents']['0'] = array('contextid' => 1,
@@ -265,43 +266,43 @@ class core_files_externallib_testcase extends advanced_testcase {
                                           'itemid' => null,
                                           'filepath' => null,
                                           'filename' => 'Miscellaneous');
-        $testdata['parents']['2'] = array('contextid' => 15,
+        $testdata['parents']['2'] = array('contextid' => $coursecontext->id,
                                           'component' => null,
                                           'filearea' => null,
                                           'itemid' => null,
                                           'filepath' => null,
                                           'filename' => 'Test course 1');
-        $testdata['parents']['3'] = array('contextid' => 20,
+        $testdata['parents']['3'] = array('contextid' => $context->id,
                                           'component' => null,
                                           'filearea' => null,
                                           'itemid' => null,
                                           'filepath' => null,
                                           'filename' => 'Mod data upload test (Database)');
-        $testdata['parents']['4'] = array('contextid' => 20,
+        $testdata['parents']['4'] = array('contextid' => $context->id,
                                           'component' => 'mod_data',
                                           'filearea' => 'content',
                                           'itemid' => null,
                                           'filepath' => null,
                                           'filename' => 'Fields');
         $testdata['files'] = array();
-        $testdata['files']['0'] = array('contextid' => 20,
+        $testdata['files']['0'] = array('contextid' => $context->id,
                                         'component' => 'mod_data',
                                         'filearea' => 'content',
-                                        'itemid' => '1',
+                                        'itemid' => $itemid,
                                         'filepath' => '/',
                                         'filename' => 'Simple4.txt',
-                                        'url' => 'http://www.example.com/moodle/pluginfile.php/20/mod_data/content/1/Simple4.txt',
+                                        'url' => 'http://www.example.com/moodle/pluginfile.php/'.$context->id.'/mod_data/content/'.$itemid.'/Simple4.txt',
                                         'isdir' => false,
                                         'timemodified' => $timemodified);
         // Make sure that they are the same.
-        $this->assertEquals($testfilelisting, $testdata);
+        $this->assertEquals($testdata, $testfilelisting);
 
         // Try again but without the context. Minus one signals the function to use other variables to obtain the context.
         $nocontext = -1;
         $modified = 0;
         // Context level and instance ID are used to determine what the context is.
         $contextlevel = 'module';
-        $instanceid = $module->id;
+        $instanceid = $module->cmid;
         $testfilelisting = core_files_external::get_files($nocontext, $component, $filearea, $itemid, '/', $filename, $modified, $contextlevel, $instanceid);
         $this->assertEquals($testfilelisting, $testdata);
     }
index 75af83a..6ca09c9 100644 (file)
@@ -65,7 +65,7 @@ class backup_gradingform_guide_plugin extends backup_gradingform_plugin {
 
         $pluginwrapper->add_child($criteria);
         $criteria->add_child($criterion);
-        $criteria->add_child($comments);
+        $pluginwrapper->add_child($comments);
         $comments->add_child($comment);
 
         // Set sources to populate the data.
index dd99814..f833404 100644 (file)
@@ -48,6 +48,11 @@ class restore_gradingform_guide_plugin extends restore_gradingform_plugin {
         $paths[] = new restore_path_element('gradingform_guide_comment',
             $this->get_pathfor('/guidecomments/guidecomment'));
 
+        // MDL-37714: Correctly locate frequent used comments in both the
+        // current and incorrect old format.
+        $paths[] = new restore_path_element('gradingform_guide_comment_legacy',
+            $this->get_pathfor('/guidecriteria/guidecomments/guidecomment'));
+
         return $paths;
     }
 
@@ -99,6 +104,20 @@ class restore_gradingform_guide_plugin extends restore_gradingform_plugin {
         $DB->insert_record('gradingform_guide_comments', $data);
     }
 
+    /**
+     * Processes comments element data
+     *
+     * @param array|stdClass $data The data to insert as a comment
+     */
+    public function process_gradingform_guide_comment_legacy($data) {
+        global $DB;
+
+        $data = (object)$data;
+        $data->definitionid = $this->get_new_parentid('grading_definition');
+
+        $DB->insert_record('gradingform_guide_comments', $data);
+    }
+
     /**
      * Processes filling element data
      *
index f3d768c..051c155 100644 (file)
@@ -52,7 +52,7 @@ class core_grade_edittreelib_testcase extends advanced_testcase {
         $scale = $this->getDataGenerator()->create_scale();
         $course = $this->getDataGenerator()->create_course();
         $assign = $this->getDataGenerator()->create_module('assign', array('course' => $course->id));
-        $modulecontext = context_module::instance($assign->id);
+        $modulecontext = context_module::instance($assign->cmid);
         // The generator returns a dummy object, lets get the real assign object.
         $assign = new assign($modulecontext, false, false);
         $cm = $assign->get_course_module();
index 3230236..e341297 100644 (file)
@@ -66,7 +66,7 @@ class core_grading_externallib_testcase extends externallib_advanced_testcase {
         // Create a teacher and give them capabilities.
         $coursecontext = context_course::instance($course->id);
         $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $coursecontext->id, 3);
-        $modulecontext = context_module::instance($cm->id);
+        $modulecontext = context_module::instance($cm->cmid);
         $this->assignUserCapability('mod/assign:grade', $modulecontext->id, $roleid);
 
         // Create the teacher's enrolment record.
@@ -146,7 +146,7 @@ class core_grading_externallib_testcase extends externallib_advanced_testcase {
         $DB->insert_record('gradingform_rubric_levels', $rubriclevel2);
 
         // Call the external function.
-        $cmids = array ($cm->id);
+        $cmids = array ($cm->cmid);
         $areaname = 'submissions';
         $result = core_grading_external::get_definitions($cmids, $areaname);
 
@@ -209,7 +209,7 @@ class core_grading_externallib_testcase extends externallib_advanced_testcase {
         // Create a teacher and give them capabilities.
         $coursecontext = context_course::instance($course->id);
         $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $coursecontext->id, 3);
-        $modulecontext = context_module::instance($assign->id);
+        $modulecontext = context_module::instance($assign->cmid);
         $this->assignUserCapability('mod/assign:grade', $modulecontext->id, $roleid);
 
         // Create the teacher's enrolment record.
index fef76b1..ca22495 100644 (file)
@@ -64,22 +64,6 @@ function UpdatableGroupsCombo(wwwRoot, courseId) {
         }
 
     };
-
-    // Add onchange event to groups list box.
-    // Okay, this is not working in IE. The onchange is never fired...
-    // I'm hard coding the onchange in ../index.php. Not ideal, but it works
-    // then. vyshane AT moodle DOT com.
-    /*
-    groupsComboEl = document.getElementById("groups");
-    if (groupsComboEl) {
-        groupsComboEl.setAttribute("onchange", "membersCombo.refreshMembers(this.options[this.selectedIndex].value);");
-    }
-    */
-
-    // Hide the updategroups input since AJAX will take care of this.
-    YUI().use('yui2-dom', function (Y) {
-        Y.YUI2.util.Dom.setStyle("updategroups", "display", "none");
-    });
 }
 
 
@@ -131,9 +115,10 @@ function UpdatableMembersCombo(wwwRoot, courseId) {
     };
 
     // Hide the updatemembers input since AJAX will take care of this.
-    YUI().use('yui2-dom', function (Y) {
-        Y.YUI2.util.Dom.setStyle("updatemembers", "display", "none");
-    });
+    var updatemembers = Y.one('#updatemembers');
+    if (updatemembers) {
+        updatemembers.hide();
+    }
 }
 
 /**
index d98781c..06391c3 100644 (file)
@@ -44,7 +44,7 @@ $string['cannotunzipfile'] = '無法解壓縮檔案。';
 $string['componentisuptodate'] = '元件已經是最新的了。';
 $string['dmlexceptiononinstall'] = '<p>資料庫有誤 [{$a->錯誤碼}].<br />{$a->排除故障資訊}</p>';
 $string['downloadedfilecheckfailed'] = '下載檔案檢查錯誤。';
-$string['invalidmd5'] = '無效的 md5';
+$string['invalidmd5'] = '這檢查變項是錯的,再試一次';
 $string['missingrequiredfield'] = '缺少部份必填欄位';
 $string['remotedownloaderror'] = '下載元件至伺服器失敗,檢查代理伺服器的設定、高度建議安裝PHP cURL,您必須手動下載<a href="{$a->url}">{$a->url}</a>,並且複製到伺服器"{$a->dest}" 解壓縮';
 $string['wrongdestpath'] = '錯誤的目的路徑。';
index 6e8a439..1d73f7e 100644 (file)
@@ -83,6 +83,7 @@ $string['errorminpasswordupper'] = 'Passwords must have at least {$a} upper case
 $string['errorpasswordupdate'] = 'Error updating password, password not changed';
 $string['event_user_loggedin'] = 'User has logged in';
 $string['eventuserloggedinas'] = 'User logged in as another user';
+$string['eventuserloginfailed'] = 'User login failed';
 $string['forcechangepassword'] = 'Force change password';
 $string['forcechangepasswordfirst_help'] = 'Force users to change password on their first login to Moodle.';
 $string['forcechangepassword_help'] = 'Force users to change password on their next login to Moodle.';
index 9ad8f9e..2468cbd 100644 (file)
@@ -223,6 +223,7 @@ $string['error:connectionunknownreason'] = 'The connection was unsuccessful but
 $string['error:clone'] = 'Cannot clone the badge.';
 $string['error:duplicatename'] = 'Badge with such name already exists in the system.';
 $string['error:externalbadgedoesntexist'] = 'Badge not found';
+$string['error:guestuseraccess'] = 'You are currently using guest access. To see badges you need to log in with your user account.';
 $string['error:invalidbadgeurl'] = 'Invalid badge issuer URL format.';
 $string['error:invalidcriteriatype'] = 'Invalid criteria type.';
 $string['error:invalidexpiredate'] = 'Expiry date has to be in the future.';
index ca9a20f..0bd37d5 100644 (file)
@@ -35,4 +35,5 @@ $string['checkingsourcetables'] = 'Checking source table structure';
 $string['importschemaexception'] = 'Current database structure does not match all install.xml files. <br /> {$a}';
 $string['importversionmismatchexception'] = 'Current version {$a->currentver} does not match exported version {$a->schemaver}.';
 $string['malformedxmlexception'] = 'Malformed XML found, can not continue.';
+$string['tablex'] = 'Table {$a}:';
 $string['unknowntableexception'] = 'Unknown table {$a} found in export file.';
index 1a9e518..6ad6365 100644 (file)
@@ -115,6 +115,8 @@ $string['administratorsandteachers'] = 'Administrators and teachers';
 $string['advanced'] = 'Advanced';
 $string['advancedfilter'] = 'Advanced search';
 $string['advancedsettings'] = 'Advanced settings';
+$string['afterresource'] = 'After resource "{$a}"';
+$string['aftersection'] = 'After section "{$a}"';
 $string['again'] = 'again';
 $string['aimid'] = 'AIM ID';
 $string['ajaxuse'] = 'AJAX and Javascript';
@@ -1165,6 +1167,8 @@ $string['moreinformation'] = 'More information about this error';
 $string['moreprofileinfoneeded'] = 'Please tell us more about yourself';
 $string['mostrecently'] = 'most recently';
 $string['move'] = 'Move';
+$string['movecoursemodule'] = 'Move resource';
+$string['movecoursesection'] = 'Move section';
 $string['movecontent'] = 'Move {$a}';
 $string['movecategorycontentto'] = 'Move into';
 $string['movecategorysuccess'] = 'Successfully moved category \'{$a->moved}\' into category \'{$a->to}\'';
@@ -1820,6 +1824,7 @@ $string['topicoutline'] = 'Topic outline';
 $string['topicshow'] = 'Show this topic to {$a}';
 $string['topichide'] = 'Hide this topic from {$a}';
 $string['total'] = 'Total';
+$string['totopofsection'] = 'To the top of section "{$a}"';
 $string['trackforums'] = 'Forum tracking';
 $string['trackforumsno'] = 'No: don\'t keep track of posts I have seen';
 $string['trackforumsyes'] = 'Yes: highlight new posts for me';
index dce4028..208b95a 100644 (file)
@@ -94,8 +94,23 @@ class behat_form_select extends behat_form_field {
             return;
         }
 
+        // Wrapped in try & catch as the element may disappear if an AJAX request was submitted.
+        try {
+            $multiple = $this->field->hasAttribute('multiple');
+        } catch (Exception $e) {
+            // We do not specify any specific Exception type as there are
+            // different exceptions that can be thrown by the driver and
+            // we can not control them all, also depending on the selenium
+            // version the exception type can change.
+            return;
+        }
+
+        // Wait for all the possible AJAX requests that have been
+        // already triggered by selectOption() to be finished.
+        $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
+
         // Single select sometimes needs an extra click in the option.
-        if (!$this->field->hasAttribute('multiple')) {
+        if (!$multiple) {
 
             // Using the driver direcly because Element methods are messy when dealing
             // with elements inside containers.
@@ -104,11 +119,6 @@ class behat_form_select extends behat_form_field {
                 // Wrapped in a try & catch as we can fall into race conditions
                 // and the element may not be there.
                 try {
-
-                    // Wait for all the possible AJAX requests that have been
-                    // already triggered by selectOption() to be finished.
-                    $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
                     current($optionnodes)->click();
                 } catch (Exception $e) {
                     // We continue and return as this means that the element is not there or it is not the same.
@@ -118,10 +128,6 @@ class behat_form_select extends behat_form_field {
 
         } else {
 
-            // Wait for all the possible AJAX requests that have been
-            // already triggered by selectOption() to be finished.
-            $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS);
-
             // Wrapped in a try & catch as we can fall into race conditions
             // and the element may not be there.
             try {
diff --git a/lib/classes/event/user_login_failed.php b/lib/classes/event/user_login_failed.php
new file mode 100644 (file)
index 0000000..6533a10
--- /dev/null
@@ -0,0 +1,109 @@
+<?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/>.
+
+/**
+ * User login failed event.
+ *
+ * @package    core
+ * @copyright  2014 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace core\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * User login failed event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      @type string username name of user.
+ *      @type int reason failure reason.
+ * }
+ *
+ * @package    core
+ * @copyright  2014 Rajesh Taneja <rajesh@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class user_login_failed extends \core\event\base {
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->context = \context_system::instance();
+        $this->data['crud'] = 'r';
+        $this->data['edulevel'] = self::LEVEL_OTHER;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventuserloginfailed', 'auth');
+    }
+
+    /**
+     * Returns non-localised event description with id's for admin use only.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return 'Login failed for user "' . $this->other['username'] . '" for reason id: ' . $this->other['reason'];
+    }
+
+    /**
+     * Get URL related to the action.
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        if (isset($this->data['userid'])) {
+            return new \moodle_url('/user/profile.php', array('id' => $this->data['userid']));
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Return legacy data for add_to_log().
+     *
+     * @return array
+     */
+    protected function get_legacy_logdata() {
+        return array(SITEID, 'login', 'error', 'index.php', $this->other['username']);
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception when validation does not pass.
+     * @return void
+     */
+    protected function validate_data() {
+        if (!isset($this->other['reason'])) {
+            throw new \coding_exception("other['reason'] has to be specified.");
+        } else if (!isset($this->other['username'])) {
+            throw new \coding_exception("other['username'] has to be specified.");
+        }
+    }
+
+}
index f0d7455..7ae4380 100644 (file)
@@ -662,6 +662,10 @@ class core_plugin_manager {
             return 'svn';
         }
 
+        if (is_dir($pluginroot.'/.hg')) {
+            return 'mercurial';
+        }
+
         return false;
     }
 
similarity index 78%
rename from backup/util/progress/core_backup_progress.class.php
rename to lib/classes/progress/base.php
index 7204e58..3944682 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+namespace core\progress;
+
+defined('MOODLE_INTERNAL') || die();
+
 /**
- * Base class for handling progress information during a backup and restore.
+ * Base class for handling progress information.
  *
- * Subclasses should generally override the current_progress function which
+ * Subclasses should generally override the {@link current_progress} function which
  * summarises all progress information.
  *
- * @package core_backup
+ * @package core_progress
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-abstract class core_backup_progress {
+abstract class base {
     /**
      * @var int Constant indicating that the number of progress calls is unknown.
      */
@@ -36,7 +40,7 @@ abstract class core_backup_progress {
      * frontendservertimeout config option to a lower value, such as 180
      * seconds (default for some commercial products).
      *
-     * @var int The number of seconds that can pass without progress() calls.
+     * @var int The number of seconds that can pass without {@link progress()} calls.
      */
     const TIME_LIMIT_WITHOUT_PROGRESS = 3600;
 
@@ -66,7 +70,7 @@ abstract class core_backup_progress {
     protected $currents = array();
 
     /**
-     * @var int Array of counts within parent progress entry (ignored for first)
+     * @var int[] Array of counts within parent progress entry (ignored for first)
      */
     protected $parentcounts = array();
 
@@ -76,28 +80,28 @@ abstract class core_backup_progress {
      * This can be called multiple times for nested progress sections. It must
      * be paired with calls to end_progress.
      *
-     * The progress maximum may be INDETERMINATE if the current operation has
+     * The progress maximum may be {@link self::INDETERMINATE} if the current operation has
      * an unknown number of steps. (This is default.)
      *
      * Calling this function will always result in a new display, so this
      * should not be called exceedingly frequently.
      *
-     * When it is complete by calling end_progress, each start_progress section
+     * When it is complete by calling {@link end_progress()}, each {@link start_progress} section
      * automatically adds progress to its parent, as defined by $parentcount.
      *
      * @param string $description Description to display
      * @param int $max Maximum value of progress for this section
      * @param int $parentcount How many progress points this section counts for
-     * @throws coding_exception If max is invalid
+     * @throws \coding_exception If max is invalid
      */
     public function start_progress($description, $max = self::INDETERMINATE,
             $parentcount = 1) {
         if ($max != self::INDETERMINATE && $max < 0) {
-            throw new coding_exception(
+            throw new \coding_exception(
                     'start_progress() max value cannot be negative');
         }
         if ($parentcount < 1) {
-            throw new coding_exception(
+            throw new \coding_exception(
                     'start_progress() parent progress count must be at least 1');
         }
         if (!empty($this->descriptions)) {
@@ -105,13 +109,13 @@ abstract class core_backup_progress {
             if ($prevmax !== self::INDETERMINATE) {
                 $prevcurrent = end($this->currents);
                 if ($prevcurrent + $parentcount > $prevmax) {
-                    throw new coding_exception(
+                    throw new \coding_exception(
                             'start_progress() parent progress would exceed max');
                 }
             }
         } else {
             if ($parentcount != 1) {
-                throw new coding_exception(
+                throw new \coding_exception(
                         'start_progress() progress count must be 1 when no parent');
             }
         }
@@ -125,16 +129,16 @@ abstract class core_backup_progress {
     /**
      * Marks the end of an operation that will display progress.
      *
-     * This must be paired with each start_progress call.
+     * This must be paired with each {@link start_progress} call.
      *
      * If there is a parent progress section, its progress will be increased
      * automatically to reflect the end of the child section.
      *
-     * @throws coding_exception If progress hasn't been started
+     * @throws \coding_exception If progress hasn't been started
      */
     public function end_progress() {
         if (!count($this->descriptions)) {
-            throw new coding_exception('end_progress() without start_progress()');
+            throw new \coding_exception('end_progress() without start_progress()');
         }
         array_pop($this->descriptions);
         array_pop($this->maxes);
@@ -154,29 +158,23 @@ abstract class core_backup_progress {
      * Indicates that progress has occurred.
      *
      * The progress value should indicate the total progress so far, from 0
-     * to the value supplied for $max (inclusive) in start_progress.
+     * to the value supplied for $max (inclusive) in {@link start_progress}.
      *
      * You do not need to call this function for every value. It is OK to skip
      * values. It is also OK to call this function as often as desired; it
-     * doesn't do anything if called more than once per second.
+     * doesn't update the display if called more than once per second.
      *
-     * It must be INDETERMINATE if start_progress was called with $max set to
+     * It must be INDETERMINATE if {@link start_progress} was called with $max set to
      * INDETERMINATE. Otherwise it must not be indeterminate.
      *
      * @param int $progress Progress so far
-     * @throws coding_exception If progress value is invalid
+     * @throws \coding_exception If progress value is invalid
      */
     public function progress($progress = self::INDETERMINATE) {
-        // Ignore too-frequent progress calls (more than once per second).
-        $now = $this->get_time();
-        if ($now === $this->lastprogresstime) {
-            return;
-        }
-
         // Check we are inside a progress section.
         $max = end($this->maxes);
         if ($max === false) {
-            throw new coding_exception(
+            throw new \coding_exception(
                     'progress() without start_progress');
         }
 
@@ -184,34 +182,55 @@ abstract class core_backup_progress {
         if ($progress === self::INDETERMINATE) {
             // Indeterminate progress.
             if ($max !== self::INDETERMINATE) {
-                throw new coding_exception(
+                throw new \coding_exception(
                         'progress() INDETERMINATE, expecting value');
             }
         } else {
             // Determinate progress.
             $current = end($this->currents);
             if ($max === self::INDETERMINATE) {
-                throw new coding_exception(
+                throw new \coding_exception(
                         'progress() with value, expecting INDETERMINATE');
             } else if ($progress < 0 || $progress > $max) {
-                throw new coding_exception(
+                throw new \coding_exception(
                         'progress() value out of range');
             } else if ($progress < $current) {
-                throw new coding_Exception(
+                throw new \coding_exception(
                         'progress() value may not go backwards');
             }
             $this->currents[key($this->currents)] = $progress;
         }
 
+        // Don't update progress bar too frequently (more than once per second).
+        $now = $this->get_time();
+        if ($now === $this->lastprogresstime) {
+            return;
+        }
+
         // Update progress.
         $this->count++;
         $this->lastprogresstime = $now;
 
         // Update time limit before next progress display.
-        core_php_time_limit::raise(self::TIME_LIMIT_WITHOUT_PROGRESS);
+        \core_php_time_limit::raise(self::TIME_LIMIT_WITHOUT_PROGRESS);
         $this->update_progress();
     }
 
+    /**
+     * An alternative to calling progress. This keeps track of the number of items done internally. Call this method
+     * with no parameters to increment the internal counter by one or you can use the $incby parameter to specify a positive
+     * change in progress. The internal progress counter should not exceed $max as passed to {@link start_progress} for this
+     * section.
+     *
+     * If you called {@link start_progress} with parameter INDETERMINATE then you cannot call this method.
+     *
+     * @var int $incby The positive change to apply to the internal progress counter. Defaults to 1.
+     */
+    public function increment_progress($incby = 1) {
+        $current = end($this->currents);
+        $this->progress($current + $incby);
+    }
+
     /**
      * Gets time (this is provided so that unit tests can override it).
      *
@@ -236,24 +255,25 @@ abstract class core_backup_progress {
     /**
      * Checks max value of current progress section.
      *
-     * @return int Current max value (may be core_backup_progress::INDETERMINATE)
-     * @throws coding_exception If not in a progress section
+     * @return int Current max value - may be {@link \core\progress\base::INDETERMINATE}.
+     * @throws \coding_exception If not in a progress section
      */
     public function get_current_max() {
         $max = end($this->maxes);
         if ($max === false) {
-            throw new coding_exception('Not inside progress section');
+            throw new \coding_exception('Not inside progress section');
         }
         return $max;
     }
 
     /**
+     * @throws \coding_exception
      * @return string Current progress section description
      */
     public function get_current_description() {
         $description = end($this->descriptions);
         if ($description === false) {
-            throw new coding_exception('Not inside progress section');
+            throw new \coding_exception('Not inside progress section');
         }
         return $description;
     }
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+namespace core\progress;
+
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Progress handler that uses a standard Moodle progress bar to display
  * progress. The Moodle progress bar cannot show indeterminate progress,
  * so we do extra output in addition to the bar.
  *
- * @package core_backup
+ * @package core_progress
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class core_backup_display_progress extends core_backup_progress {
+class display extends base {
     /**
      * @var int Number of wibble states (state0...stateN-1 classes in CSS)
      */
     const WIBBLE_STATES = 13;
 
     /**
-     * @var progress_bar Current progress bar.
+     * @var \progress_bar Current progress bar.
      */
     private $bar;
 
@@ -54,8 +58,8 @@ class core_backup_display_progress extends core_backup_progress {
     }
 
     /**
-     * By default, the progress section names do not display because (in backup)
-     * these are usually untranslated and incomprehensible. To make them
+     * By default, the progress section names do not display because
+     * these will probably be untranslated and incomprehensible. To make them
      * display, call this method.
      *
      * @param bool $displaynames True to display names
@@ -69,15 +73,15 @@ class core_backup_display_progress extends core_backup_progress {
      *
      * Called in constructor and in update_progress if required.
      *
-     * @throws coding_exception If already started
+     * @throws \coding_exception If already started
      */
     public function start_html() {
         if ($this->bar) {
-            throw new coding_exception('Already started');
+            throw new \coding_exception('Already started');
         }
-        $this->bar = new progress_bar();
+        $this->bar = new \progress_bar();
         $this->bar->create();
-        echo html_writer::start_div('wibbler');
+        echo \html_writer::start_div('wibbler');
     }
 
     /**
@@ -92,13 +96,13 @@ class core_backup_display_progress extends core_backup_progress {
         $this->bar = null;
 
         // End wibbler div.
-        echo html_writer::end_div();
+        echo \html_writer::end_div();
     }
 
     /**
      * When progress is updated, updates the bar.
      *
-     * @see core_backup_progress::update_progress()
+     * @see \core\progress\base::update_progress()
      */
     public function update_progress() {
         // If finished...
@@ -114,7 +118,7 @@ class core_backup_display_progress extends core_backup_progress {
             // (up to once per second).
             if (time() != $this->lastwibble) {
                 $this->lastwibble = time();
-                echo html_writer::div('', 'wibble state' . $this->currentstate);
+                echo \html_writer::div('', 'wibble state' . $this->currentstate);
 
                 // Go on to next colour.
                 $this->currentstate += $this->direction;
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+namespace core\progress;
+
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Progress handler that uses a standard Moodle progress bar to display
- * progress. Same as core_backup_display_progress, but the bar does not
+ * progress. Same as \core\progress\display, but the bar does not
  * appear until a certain time has elapsed, and disappears automatically
  * after it finishes.
  *
  * The bar can be re-used, i.e. if you end all sections it will disappear,
  * but if you start all sections, a new bar will be output.
  *
- * @package core_backup
+ * @package core_progress
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class core_backup_display_progress_if_slow extends core_backup_display_progress {
+class display_if_slow extends display {
     /**
      * @var int Waits this many seconds before displaying progress bar
      */
@@ -57,10 +61,10 @@ class core_backup_display_progress_if_slow extends core_backup_display_progress
      * Constructs the progress reporter. This will not output HTML just yet,
      * until the required delay time expires.
      *
-     * @param string $heading Text to display above bar (if it appears); '' for none
+     * @param string $heading Text to display above bar (if it appears); '' for none (default)
      * @param int $delay Delay time (default 5 seconds)
      */
-    public function __construct($heading, $delay = self::DEFAULT_DISPLAY_DELAY) {
+    public function __construct($heading = '', $delay = self::DEFAULT_DISPLAY_DELAY) {
         // Set start time based on delay.
         $this->starttime = time() + $delay;
         $this->heading = $heading;
@@ -71,16 +75,16 @@ class core_backup_display_progress_if_slow extends core_backup_display_progress
      * Starts displaying the progress bar, with optional heading and a special
      * div so it can be hidden later.
      *
-     * @see core_backup_display_progress::start_html()
+     * @see \core\progress\display::start_html()
      */
     public function start_html() {
         global $OUTPUT;
-        $this->id = 'core_backup_display_progress_if_slow' . self::$nextid;
+        $this->id = 'core_progress_display_if_slow' . self::$nextid;
         self::$nextid++;
 
         // Containing div includes a CSS class so that it can be themed if required,
         // and an id so it can be automatically hidden at end.
-        echo html_writer::start_div('core_backup_display_progress_if_slow',
+        echo \html_writer::start_div('core_progress_display_if_slow',
                 array('id' => $this->id));
 
         // Display optional heading.
@@ -96,7 +100,7 @@ class core_backup_display_progress_if_slow extends core_backup_display_progress
      * When progress is updated, after a certain time, starts actually displaying
      * the progress bar.
      *
-     * @see core_backup_progress::update_progress()
+     * @see \core\progress\base::update_progress()
      */
     public function update_progress() {
         // If we haven't started yet, consider starting.
@@ -116,12 +120,12 @@ class core_backup_display_progress_if_slow extends core_backup_display_progress
     /**
      * Finishes parent display then closes div and hides it.
      *
-     * @see core_backup_display_progress::end_html()
+     * @see \core\progress\display::end_html()
      */
     public function end_html() {
         parent::end_html();
-        echo html_writer::end_div();
-        echo html_writer::script('document.getElementById("' . $this->id .
+        echo \html_writer::end_div();
+        echo \html_writer::script('document.getElementById("' . $this->id .
                 '").style.display = "none"');
     }
 }
similarity index 88%
rename from backup/util/progress/core_backup_null_progress.class.php
rename to lib/classes/progress/null.php
index 3e02369..f332a30 100644 (file)
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
+namespace core\progress;
+
+defined('MOODLE_INTERNAL') || die();
+
 /**
  * Progress handler that ignores progress entirely.
  *
- * @package core_backup
+ * @package core_progress
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class core_backup_null_progress extends core_backup_progress {
+class null extends base {
     public function update_progress() {
         // Do nothing.
     }
index 0eccb8e..d255609 100644 (file)
@@ -189,6 +189,10 @@ class deployer {
             return 'svn';
         }
 
+        if (is_dir($pluginroot.'/.hg')) {
+            return 'mercurial';
+        }
+
         return false;
     }
 
index 4dd6833..f266669 100644 (file)
         <FIELD NAME="questionid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false"/>
         <FIELD NAME="slot" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="The position in the quiz where this question appears"/>
         <FIELD NAME="subquestion" TYPE="int" LENGTH="4" NOTNULL="true" SEQUENCE="false"/>
+        <FIELD NAME="variant" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false"/>
         <FIELD NAME="s" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
         <FIELD NAME="effectiveweight" TYPE="number" LENGTH="15" NOTNULL="false" SEQUENCE="false" DECIMALS="5"/>
         <FIELD NAME="negcovar" TYPE="int" LENGTH="2" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
index fe87360..1cb0188 100644 (file)
@@ -905,7 +905,8 @@ $services = array(
             'moodle_message_send_instantmessages',
             'core_course_get_contents',
             'core_get_component_strings',
-            'core_user_add_user_device'),
+            'core_user_add_user_device',
+            'core_calendar_get_calendar_events'),
         'enabled' => 0,
         'restrictedusers' => 0,
         'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE,
index 0fe0e43..973d4af 100644 (file)
@@ -85,7 +85,7 @@ defined('MOODLE_INTERNAL') || die();
  * @return bool always true
  */
 function xmldb_main_upgrade($oldversion) {
-    global $CFG, $USER, $DB, $OUTPUT, $SITE;
+    global $CFG, $USER, $DB, $OUTPUT, $SITE, $COURSE;
 
     require_once($CFG->libdir.'/db/upgradelib.php'); // Core Upgrade-related functions
 
@@ -316,6 +316,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->drop_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         upgrade_main_savepoint(true, 2012031500.02);
     }
 
@@ -447,6 +451,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->add_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached
         upgrade_main_savepoint(true, 2012050300.03);
     }
@@ -563,6 +571,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->add_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Add course_sections_availability to add completion & grade availability conditions
         $table = new xmldb_table('course_sections_availability');
 
@@ -1334,6 +1346,10 @@ function xmldb_main_upgrade($oldversion) {
         // Launch change of type for field format
         $dbman->change_field_type($table, $field);
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached
         upgrade_main_savepoint(true, 2012110200.00);
     }
@@ -1386,6 +1402,10 @@ function xmldb_main_upgrade($oldversion) {
             }
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached
         upgrade_main_savepoint(true, 2012110201.00);
     }
@@ -1501,6 +1521,7 @@ function xmldb_main_upgrade($oldversion) {
         if ($SITE->format !== 'site') {
             $DB->set_field('course', 'format', 'site', array('id' => $SITE->id));
             $SITE->format = 'site';
+            $COURSE->format = 'site';
         }
 
         // Main savepoint reached
@@ -1989,6 +2010,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->drop_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached.
         upgrade_main_savepoint(true, 2013040300.01);
     }
@@ -2376,6 +2401,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->add_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Define field calendartype to be added to user.
         $table = new xmldb_table('user');
         $field = new xmldb_field('calendartype', XMLDB_TYPE_CHAR, '30', null, XMLDB_NOTNULL, null, 'gregorian');
@@ -2400,6 +2429,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->add_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached.
         upgrade_main_savepoint(true, 2013091000.02);
     }
@@ -2423,6 +2456,10 @@ function xmldb_main_upgrade($oldversion) {
             $dbman->drop_field($table, $field);
         }
 
+        // Since structure of 'course' table has changed we need to re-read $SITE from DB.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // Main savepoint reached.
         upgrade_main_savepoint(true, 2013091000.03);
     }
@@ -2748,30 +2785,7 @@ function xmldb_main_upgrade($oldversion) {
     if ($oldversion < 2013102500.01) {
         // Find all fileareas that have missing root folder entry and add the root folder entry.
         if (empty($CFG->filesrootrecordsfixed)) {
-            $sql = "SELECT distinct f1.contextid, f1.component, f1.filearea, f1.itemid
-                FROM {files} f1 left JOIN {files} f2
-                    ON f1.contextid = f2.contextid
-                    AND f1.component = f2.component
-                    AND f1.filearea = f2.filearea
-                    AND f1.itemid = f2.itemid
-                    AND f2.filename = '.'
-                    AND f2.filepath = '/'
-                WHERE (f1.component <> 'user' or f1.filearea <> 'draft')
-                and f2.id is null";
-            $rs = $DB->get_recordset_sql($sql);
-            $defaults = array('filepath' => '/',
-                            'filename' => '.',
-                            'userid' => $USER->id,
-                            'filesize' => 0,
-                            'timecreated' => time(),
-                            'timemodified' => time(),
-                            'contenthash' => sha1(''));
-            foreach ($rs as $r) {
-                $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid".'/.');
-                $DB->insert_record('files', (array)$r + $defaults +
-                        array('pathnamehash' => $pathhash));
-            }
-            $rs->close();
+            upgrade_fix_missing_root_folders();
             // To skip running the same script on the upgrade to the next major release.
             set_config('filesrootrecordsfixed', 1);
         }
@@ -2968,5 +2982,19 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2014012400.00);
     }
 
+    if ($oldversion < 2014020500.00) {
+        // Define field variant to be added to question_statistics.
+        $table = new xmldb_table('question_statistics');
+        $field = new xmldb_field('variant', XMLDB_TYPE_INTEGER, '10', null, null, null, null, 'subquestion');
+
+        // Conditionally launch add field variant.
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Main savepoint reached.
+        upgrade_main_savepoint(true, 2014020500.00);
+    }
+
     return true;
 }
index 9b8e98f..1360898 100644 (file)
@@ -327,7 +327,7 @@ class mssql_native_moodle_database extends moodle_database {
         if ($result) {
             while ($row = mssql_fetch_row($result)) {
                 $tablename = reset($row);
-                if ($this->prefix !== '') {
+                if ($this->prefix !== false && $this->prefix !== '') {
                     if (strpos($tablename, $this->prefix) !== 0) {
                         continue;
                     }
index 8e99da4..fed5c85 100644 (file)
@@ -406,7 +406,7 @@ class oci_native_moodle_database extends moodle_database {
         oci_free_statement($stmt);
         $records = array_map('strtolower', $records['TABLE_NAME']);
         foreach ($records as $tablename) {
-            if ($this->prefix !== '') {
+            if ($this->prefix !== false && $this->prefix !== '') {
                 if (strpos($tablename, $this->prefix) !== 0) {
                     continue;
                 }
index 14b2cc3..717e632 100644 (file)
@@ -321,7 +321,7 @@ class pgsql_native_moodle_database extends moodle_database {
         if ($result) {
             while ($row = pg_fetch_row($result)) {
                 $tablename = reset($row);
-                if ($this->prefix !== '') {
+                if ($this->prefix !== false && $this->prefix !== '') {
                     if (strpos($tablename, $this->prefix) !== 0) {
                         continue;
                     }
index 7828eb7..fcfe446 100644 (file)
@@ -150,7 +150,7 @@ class sqlite3_pdo_moodle_database extends pdo_moodle_database {
         foreach ($rstables as $table) {
             $table = $table['name'];
             $table = strtolower($table);
-            if ($this->prefix !== '') {
+            if ($this->prefix !== false && $this->prefix !== '') {
                 if (strpos($table, $this->prefix) !== 0) {
                     continue;
                 }
index 5110a32..eb4569f 100644 (file)
@@ -380,7 +380,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
         if ($result) {
             while ($row = sqlsrv_fetch_array($result)) {
                 $tablename = reset($row);
-                if ($this->prefix !== '') {
+                if ($this->prefix !== false && $this->prefix !== '') {
                     if (strpos($tablename, $this->prefix) !== 0) {
                         continue;
                     }
index 4d9b7bb..5cdc3f2 100644 (file)
@@ -132,7 +132,7 @@ abstract class database_exporter {
         if ($this->check_schema and $errors = $this->manager->check_database_schema($this->schema)) {
             $details = '';
             foreach ($errors as $table=>$items) {
-                $details .= '<div>'.get_string('table').' '.$table.':';
+                $details .= '<div>'.get_string('tablex', 'dbtransfer', $table);
                 $details .= '<ul>';
                 foreach ($items as $item) {
                     $details .= '<li>'.$item.'</li>';
index 5da2694..b2bda07 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-debug.js and b/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-debug.js differ
index 4bdf46b..3c3331e 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-min.js and b/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms-min.js differ
index 2691a0d..87e5199 100644 (file)
Binary files a/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms.js and b/lib/form/yui/build/moodle-form-shortforms/moodle-form-shortforms.js differ
index 36e6ccf..98e4035 100644 (file)
@@ -17,8 +17,10 @@ function SHORTFORMS() {
 
 var SELECTORS = {
         COLLAPSEEXPAND: '.collapsible-actions .collapseexpand',
+        COLLAPSED: '.collapsed',
         FIELDSETCOLLAPSIBLE: 'fieldset.collapsible',
         FIELDSETLEGENDLINK: 'fieldset.collapsible .fheader',
+        FHEADER: '.fheader',
         LEGENDFTOGGLER: 'legend.ftoggler'
     },
     CSS = {
@@ -118,6 +120,11 @@ Y.extend(SHORTFORMS, Y.Base, {
         headerlink.appendChild(legendelement.get('firstChild'));
         headerlink.setAttribute('role', 'button');
         headerlink.setAttribute('aria-controls', fieldset.generateID());
+        if (legendelement.ancestor(SELECTORS.COLLAPSED)) {
+            headerlink.setAttribute('aria-expanded', 'false');
+        } else {
+            headerlink.setAttribute('aria-expanded', 'true');
+        }
         legendelement.prepend(headerlink);
 
         return this;
@@ -132,10 +139,17 @@ Y.extend(SHORTFORMS, Y.Base, {
      * @chainable
      */
     set_state: function(fieldset, collapsed) {
+        headerlink = fieldset.one(SELECTORS.FHEADER);
         if (collapsed) {
             fieldset.addClass(CSS.COLLAPSED);
+            if (headerlink) {
+                headerlink.setAttribute('aria-expanded', 'false');
+            }
         } else {
             fieldset.removeClass(CSS.COLLAPSED);
+            if (headerlink) {
+                headerlink.setAttribute('aria-expanded', 'true');
+            }
         }
         var statuselement = this.form.one('input[name=mform_isexpanded_'+fieldset.get('id')+']');
         if (!statuselement) {
index 5072b5f..f75a75c 100644 (file)
@@ -2549,7 +2549,7 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
        "\n\t\t<legend class=\"ftoggler\">{header}</legend>\n\t\t<div class=\"fcontainer clearfix\">\n\t\t";
 
     /** @var string Template used when opening a fieldset */
-    var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id} {aria-live}>";
+    var $_openFieldsetTemplate = "\n\t<fieldset class=\"{classes}\" {id}>";
 
     /** @var string Template used when closing a fieldset */
     var $_closeFieldsetTemplate = "\n\t\t</div></fieldset>";
@@ -2833,7 +2833,6 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
         $fieldsetclasses = array('clearfix');
         if (isset($this->_collapsibleElements[$header->getName()])) {
             $fieldsetclasses[] = 'collapsible';
-            $arialive = 'aria-live="polite"';
             if ($this->_collapsibleElements[$header->getName()]) {
                 $fieldsetclasses[] = 'collapsed';
             }
@@ -2845,7 +2844,6 @@ class MoodleQuickForm_Renderer extends HTML_QuickForm_Renderer_Tableless{
 
         $openFieldsetTemplate = str_replace('{id}', $id, $this->_openFieldsetTemplate);
         $openFieldsetTemplate = str_replace('{classes}', join(' ', $fieldsetclasses), $openFieldsetTemplate);
-        $openFieldsetTemplate = str_replace('{aria-live}', $arialive, $openFieldsetTemplate);
 
         $this->_html .= $openFieldsetTemplate . $header_html;
         $this->_fieldsetsOpen++;
index 4a0aa6e..8de9da4 100644 (file)
@@ -1221,7 +1221,7 @@ class grade_item extends grade_object {
 
         $transaction = $DB->start_delegated_transaction();
 
-        $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+        $sql = "SELECT DISTINCT g1.id, g1.courseid, g1.sortorder
                     FROM {grade_items} g1
                     JOIN {grade_items} g2 ON g1.courseid = g2.courseid
                 WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id AND g1.courseid = :courseid
index 066b625..c00a0ef 100644 (file)
@@ -223,10 +223,16 @@ M.util.set_user_preference = function(name, value) {
 
 /**
  * Prints a confirmation dialog in the style of DOM.confirm().
- * @param object event A YUI DOM event or null if launched manually
- * @param string message The message to show in the dialog
- * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
- * @param function fn A JS function to run if YES is clicked.
+ *
+ * @method show_confirm_dialog
+ * @param {EventFacade} e
+ * @param {Object} args
+ * @param {String} args.message The question to ask the user
+ * @param {Function} [args.callback] A callback to apply on confirmation.
+ * @param {Object} [args.scope] The scope to use when calling the callback.
+ * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
+ * @param {String} [args.cancellabel] The label to use on the cancel button.
+ * @param {String} [args.continuelabel] The label to use on the continue button.
  */
 M.util.show_confirm_dialog = function(e, args) {
     var target = e.target;
@@ -234,47 +240,35 @@ M.util.show_confirm_dialog = function(e, args) {
         e.preventDefault();
     }
 
-    YUI().use('yui2-container', 'yui2-event', function(Y) {
-        var simpledialog = new Y.YUI2.widget.SimpleDialog('confirmdialog',
-            {width: '300px',
-              fixedcenter: true,
-              modal: true,
-              visible: false,
-              draggable: false
-            }
-        );
-
-        simpledialog.setHeader(M.str.admin.confirmation);
-        simpledialog.setBody(args.message);
-        simpledialog.cfg.setProperty('icon', Y.YUI2.widget.SimpleDialog.ICON_WARN);
-
-        var handle_cancel = function() {
-            simpledialog.hide();
-        };
-
-        var handle_yes = function() {
-            simpledialog.hide();
+    YUI().use('moodle-core-notification-confirm', function(Y) {
+        var confirmationDialogue = new M.core.confirm({
+            width: '300px',
+            center: true,
+            modal: true,
+            visible: false,
+            draggable: false,
+            title: M.util.get_string('confirmation', 'admin'),
+            noLabel: M.util.get_string('cancel', 'moodle'),
+            question: args.message
+        });
 
+        // The dialogue was submitted with a positive value indication.
+        confirmationDialogue.on('complete-yes', function(e) {
+            // Handle any callbacks.
             if (args.callback) {
-                // args comes from PHP, so callback will be a string, needs to be evaluated by JS
-                var callback = null;
-                if (Y.Lang.isFunction(args.callback)) {
-                    callback = args.callback;
-                } else {
-                    callback = eval('('+args.callback+')');
+                if (!Y.Lang.isFunction(args.callback)) {
+                    Y.log('Callbacks to show_confirm_dialog must now be functions. Please update your code to pass in a function instead.',
+                            'warn', 'M.util.show_confirm_dialog');
+                    return;
                 }
 
+                var scope = e.target;
                 if (Y.Lang.isObject(args.scope)) {
-                    var sc = args.scope;
-                } else {
-                    var sc = e.target;
+                    scope = args.scope;
                 }
 
-                if (args.callbackargs) {
-                    callback.apply(sc, args.callbackargs);
-                } else {
-                    callback.apply(sc);
-                }
+                var callbackargs = args.callbackargs || [];
+                args.callback.apply(scope, callbackargs);
                 return;
             }
 
@@ -288,9 +282,7 @@ M.util.show_confirm_dialog = function(e, args) {
                 window.location = targetancestor.get('href');
 
             } else if (target.test('input')) {
-                targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
-                // We cannot use target.ancestor('form') on the previous line
-                // because of http://yuilibrary.com/projects/yui3/ticket/2531561
+                targetform = target.ancestor('form', true);
                 if (!targetform) {
                     return;
                 }
@@ -300,32 +292,26 @@ M.util.show_confirm_dialog = function(e, args) {
                 }
                 targetform.submit();
 
-            } else if (target.get('tagName').toLowerCase() == 'form') {
-                // We cannot use target.test('form') on the previous line because of
-                // http://yuilibrary.com/projects/yui3/ticket/2531561
+            } else if (target.test('form')) {
                 target.submit();
 
-            } else if (M.cfg.developerdebug) {
-                alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
+            } else {
+                Y.log("Element of type " + target.get('tagName') +
+                        " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM",
+                        'warn', 'javascript-static');
             }
-        };
+        }, this);
 
-        if (!args.cancellabel) {
-            args.cancellabel = M.str.moodle.cancel;
-        }
-        if (!args.continuelabel) {
-            args.continuelabel = M.str.moodle.yes;
+        if (args.cancellabel) {
+            confirmationDialogue.set('noLabel', args.cancellabel);
         }
 
-        var buttons = [
-            {text: args.cancellabel,   handler: handle_cancel, isDefault: true},
-            {text: args.continuelabel, handler: handle_yes}
-        ];
-
-        simpledialog.cfg.queueProperty('buttons', buttons);
+        if (args.continuelabel) {
+            confirmationDialogue.set('yesLabel', args.continuelabel);
+        }
 
-        simpledialog.render(document.body);
-        simpledialog.show();
+        confirmationDialogue.render()
+                .show();
     });
 };
 
index 05c7e16..39cfd10 100644 (file)
@@ -3397,9 +3397,7 @@ function get_user_key($script, $userid, $instance=null, $iprestriction=null, $va
  * @return bool Always returns true
  */
 function update_user_login_times() {
-    global $USER, $DB, $CFG;
-
-    require_once($CFG->dirroot.'/user/lib.php');
+    global $USER, $DB;
 
     if (isguestuser()) {
         // Do not update guest access times/ips for performance.
@@ -3425,7 +3423,9 @@ function update_user_login_times() {
     $USER->lastaccess = $user->lastaccess = $now;
     $USER->lastip = $user->lastip = getremoteaddr();
 
-    user_update_user($user, false);
+    // Note: do not call user_update_user() here because this is part of the login process,
+    //       the login event means that these fields were updated.
+    $DB->update_record('user', $user);
     return true;
 }
 
@@ -4338,16 +4338,24 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
         // Use manual if auth not set.
         $auth = empty($user->auth) ? 'manual' : $user->auth;
         if (!empty($user->suspended)) {
-            add_to_log(SITEID, 'login', 'error', 'index.php', $username);
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Suspended Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             $failurereason = AUTH_LOGIN_SUSPENDED;
+
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('userid' => $user->id,
+                    'other' => array('username' => $username, 'reason' => $failurereason)));
+            $event->trigger();
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Suspended Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             return false;
         }
         if ($auth=='nologin' or !is_enabled_auth($auth)) {
-            add_to_log(SITEID, 'login', 'error', 'index.php', $username);
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Disabled Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             // Legacy way to suspend user.
             $failurereason = AUTH_LOGIN_SUSPENDED;
+
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('userid' => $user->id,
+                    'other' => array('username' => $username, 'reason' => $failurereason)));
+            $event->trigger();
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Disabled Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             return false;
         }
         $auths = array($auth);
@@ -4355,16 +4363,27 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
     } else {
         // Check if there's a deleted record (cheaply), this should not happen because we mangle usernames in delete_user().
         if ($DB->get_field('user', 'id', array('username' => $username, 'mnethostid' => $CFG->mnet_localhost_id,  'deleted' => 1))) {
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Deleted Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             $failurereason = AUTH_LOGIN_NOUSER;
+
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
+                    'reason' => $failurereason)));
+            $event->trigger();
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Deleted Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             return false;
         }
 
         // Do not try to authenticate non-existent accounts when user creation is not disabled.
         if (!empty($CFG->authpreventaccountcreation)) {
-            add_to_log(SITEID, 'login', 'error', 'index.php', $username);
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Unknown user, can not create new accounts:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             $failurereason = AUTH_LOGIN_NOUSER;
+
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
+                    'reason' => $failurereason)));
+            $event->trigger();
+
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Unknown user, can not create new accounts:  $username  ".
+                    $_SERVER['HTTP_USER_AGENT']);
             return false;
         }
 
@@ -4380,9 +4399,14 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
     } else if ($user->id) {
         // Verify login lockout after other ways that may prevent user login.
         if (login_is_lockedout($user)) {
-            add_to_log(SITEID, 'login', 'error', 'index.php', $username);
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Login lockout:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             $failurereason = AUTH_LOGIN_LOCKOUT;
+
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('userid' => $user->id,
+                    'other' => array('username' => $username, 'reason' => $failurereason)));
+            $event->trigger();
+
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Login lockout:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             return false;
         }
     } else {
@@ -4428,14 +4452,21 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
 
         if (empty($user->id)) {
             $failurereason = AUTH_LOGIN_NOUSER;
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
+                    'reason' => $failurereason)));
+            $event->trigger();
             return false;
         }
 
         if (!empty($user->suspended)) {
             // Just in case some auth plugin suspended account.
-            add_to_log(SITEID, 'login', 'error', 'index.php', $username);
-            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Suspended Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             $failurereason = AUTH_LOGIN_SUSPENDED;
+            // Trigger login failed event.
+            $event = \core\event\user_login_failed::create(array('userid' => $user->id,
+                    'other' => array('username' => $username, 'reason' => $failurereason)));
+            $event->trigger();
+            error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Suspended Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
             return false;
         }
 
@@ -4445,7 +4476,6 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
     }
 
     // Failed if all the plugins have failed.
-    add_to_log(SITEID, 'login', 'error', 'index.php', $username);
     if (debugging('', DEBUG_ALL)) {
         error_log('[client '.getremoteaddr()."]  $CFG->wwwroot  Failed Login:  $username  ".$_SERVER['HTTP_USER_AGENT']);
     }
@@ -4453,8 +4483,16 @@ function authenticate_user_login($username, $password, $ignorelockout=false, &$f
     if ($user->id) {
         login_attempt_failed($user);
         $failurereason = AUTH_LOGIN_FAILED;
+        // Trigger login failed event.
+        $event = \core\event\user_login_failed::create(array('userid' => $user->id,
+                'other' => array('username' => $username, 'reason' => $failurereason)));
+        $event->trigger();
     } else {
         $failurereason = AUTH_LOGIN_NOUSER;
+        // Trigger login failed event.
+        $event = \core\event\user_login_failed::create(array('other' => array('username' => $username,
+                'reason' => $failurereason)));
+        $event->trigger();
     }
 
     return false;
index 12c10c3..38c6fdb 100644 (file)
@@ -93,13 +93,19 @@ class confirm_action extends component_action {
      *
      * @param string $message The message to display to the user when they are shown
      *    the confirm dialogue.
-     * @param string $callback The method to call when the user confirms the action.
+     * @param string $callback Deprecated since 2.7
      * @param string $continuelabel The string to use for he continue button
      * @param string $cancellabel The string to use for the cancel button
      */
     public function __construct($message, $callback = null, $continuelabel = null, $cancellabel = null) {
+        if ($callback !== null) {
+            debugging('The callback argument to new confirm_action() has been deprecated.' .
+                    ' If you need to use a callback, please write Javascript to use moodle-core-notification-confirmation ' .
+                    'and attach to the provided events.',
+                    DEBUG_DEVELOPER);
+        }
         parent::__construct('click', 'M.util.show_confirm_dialog', array(
-                'message' => $message, 'callback' => $callback,
+                'message' => $message,
                 'continuelabel' => $continuelabel, 'cancellabel' => $cancellabel));
     }
 }
@@ -197,4 +203,4 @@ class popup_action extends component_action {
 
         return $jsoptions;
     }
-}
\ No newline at end of file
+}
index 5157293..3b67e72 100644 (file)
@@ -1426,6 +1426,9 @@ class html_writer {
      * method. In most cases this is not an issue at all so we do not clone by default for performance
      * and memory consumption reasons.
      *
+     * Please do not use .r0/.r1 for css, as they will be removed in Moodle 2.9.
+     * @todo MDL-43902 , remove r0 and r1 from tr classes.
+     *
      * @param html_table $table data to be rendered
      * @return string HTML code
      */
index 1f39798..904708c 100644 (file)
@@ -3267,6 +3267,9 @@ EOD;
             } else {
                 $additionalclasses[] = 'empty-region-'.$region;
             }
+            if ($this->page->blocks->region_completely_docked($region, $this)) {
+                $additionalclasses[] = 'docked-region-'.$region;
+            }
         }
         foreach ($this->page->layout_options as $option => $value) {
             if ($value) {
index 0e55357..18aea45 100644 (file)
@@ -195,6 +195,15 @@ class page_requirements_manager {
         $this->YUI_config->comboBase    = $this->yui3loader->comboBase;
         $this->YUI_config->combine      = $this->yui3loader->combine;
 
+        // If we've had to patch any YUI modules between releases, we must override the YUI configuration to include them.
+        // For important information on patching YUI modules, please see http://docs.moodle.org/dev/YUI/Patching.
+        if (!empty($CFG->yuipatchedmodules) && !empty($CFG->yuipatchlevel)) {
+            $this->YUI_config->define_patched_core_modules($this->yui3loader->local_comboBase,
+                    $CFG->yui3version,
+                    $CFG->yuipatchlevel,
+                    $CFG->yuipatchedmodules);
+        }
+
         $configname = $this->YUI_config->set_config_source('lib/yui/config/yui2.js');
         $this->YUI_config->add_group('yui2', array(
             // Loader configuration for our 2in3, for now ignores $CFG->useexternalyui.
@@ -1773,6 +1782,59 @@ class YUI_config {
         }
         return $modules;
     }
+
+    /**
+     * Define YUI modules which we have been required to patch between releases.
+     *
+     * We must do this because we aggressively cache content on the browser, and we must also override use of the
+     * external CDN which will serve the true authoritative copy of the code without our patches.
+     *
+     * @param String combobase The local combobase
+     * @param String yuiversion The current YUI version
+     * @param Int patchlevel The patch level we're working to for YUI
+     * @param Array patchedmodules An array containing the names of the patched modules
+     * @return void
+     */
+    public function define_patched_core_modules($combobase, $yuiversion, $patchlevel, $patchedmodules) {
+        // The version we use is suffixed with a patchlevel so that we can get additional revisions between YUI releases.
+        $subversion = $yuiversion . '_' . $patchlevel;
+
+        if ($this->comboBase == $combobase) {
+            // If we are using the local combobase in the loader, we can add a group and still make use of the combo
+            // loader. We just need to specify a different root which includes a slightly different YUI version number
+            // to include our patchlevel.
+            $patterns = array();
+            $modules = array();
+            foreach ($patchedmodules as $modulename) {
+                // We must define the pattern and module here so that the loader uses our group configuration instead of
+                // the standard module definition. We may lose some metadata provided by upstream but this will be
+                // loaded when the module is loaded anyway.
+                $patterns[$modulename] = array(
+                    'group' => 'yui-patched',
+                );
+                $modules[$modulename] = array();
+            }
+
+            // Actually add the patch group here.
+            $this->add_group('yui-patched', array(
+                'combine' => true,
+                'root' => $subversion . '/',
+                'patterns' => $patterns,
+                'modules' => $modules,
+            ));
+
+        } else {
+            // The CDN is in use - we need to instead use the local combobase for this module and override the modules
+            // definition. We cannot use the local base - we must use the combobase because we cannot invalidate the
+            // local base in browser caches.
+            $fullpathbase = $combobase . $subversion . '/';
+            foreach ($patchedmodules as $modulename) {
+                $this->modules[$modulename] = array(
+                    'fullpath' => $fullpathbase . $modulename . '/' . $modulename . '-min.js'
+                );
+            }
+        }
+    }
 }
 
 /**
index 3b72e6e..309698e 100644 (file)
@@ -81,6 +81,9 @@ define('K_CELL_HEIGHT_RATIO', 1.25);
 /** reduction factor for small font */
 define('K_SMALL_RATIO', 2/3);
 
+/** Throw exceptions from errors so they can be caught and recovered from. */
+define('K_TCPDF_THROW_EXCEPTION_ERROR', true);
+
 require_once(dirname(__FILE__).'/tcpdf/tcpdf.php');
 
 /**
index 94115d4..1618676 100644 (file)
@@ -349,6 +349,15 @@ if (!defined('AJAX_SCRIPT')) {
 $CFG->yui2version = '2.9.0';
 $CFG->yui3version = '3.13.0';
 
+// Patching the upstream YUI release.
+// For important information on patching YUI modules, please see http://docs.moodle.org/dev/YUI/Patching.
+// If we need to patch a YUI modules between official YUI releases, the yuipatchlevel will need to be manually
+// incremented here. The module will also need to be listed in the yuipatchedmodules.
+// When upgrading to a subsequent version of YUI, these should be reset back to 0 and an empty array.
+$CFG->yuipatchlevel = 0;
+$CFG->yuipatchedmodules = array(
+);
+
 // Store settings from config.php in array in $CFG - we can use it later to detect problems and overrides.
 if (!isset($CFG->config_php_settings)) {
     $CFG->config_php_settings = (array)$CFG;
index 657c72e..8e3296d 100644 (file)
@@ -797,7 +797,7 @@ class flexible_table {
             }
             return format_text($text, $format, $options);
         } else {
-            $eci =& $this->export_class_instance();
+            $eci = $this->export_class_instance();
             return $eci->format_text($text, $format, $options, $courseid);
         }
     }
@@ -978,6 +978,9 @@ class flexible_table {
 
     /**
      * This function is not part of the public api.
+     *
+     * Please do not use .r0/.r1 for css, as they will be removed in Moodle 2.9.
+     * @todo MDL-43902 , remove r0 and r1 from tr classes.
      */
     function print_row($row, $classname = '') {
         static $suppress_lastrow = NULL;
@@ -1527,9 +1530,9 @@ class table_spreadsheet_export_format_parent extends table_default_export_format
         $filename = $filename.'.'.$this->fileextension;
         $this->define_workbook();
         // format types
-        $this->formatnormal =& $this->workbook->add_format();
+        $this->formatnormal = $this->workbook->add_format();
         $this->formatnormal->set_bold(0);
-        $this->formatheaders =& $this->workbook->add_format();
+        $this->formatheaders = $this->workbook->add_format();
         $this->formatheaders->set_bold(1);
         $this->formatheaders->set_align('center');
         // Sending HTTP headers
index 6a9f8d6..ac7cc06 100644 (file)
@@ -60,7 +60,7 @@ class core_accesslib_testcase extends advanced_testcase {
         $this->assertNotEmpty($ACCESSLIB_PRIVATE->rolepermissions);
         $this->assertNotEmpty($ACCESSLIB_PRIVATE->rolepermissions);
         $this->assertNotEmpty($ACCESSLIB_PRIVATE->accessdatabyuser);
-        accesslib_clear_all_caches(true);
+        accesslib_clear_all_caches_for_unit_testing();
         $this->assertEmpty($ACCESSLIB_PRIVATE->rolepermissions);
         $this->assertEmpty($ACCESSLIB_PRIVATE->rolepermissions);
         $this->assertEmpty($ACCESSLIB_PRIVATE->dirtycontexts);
@@ -2095,7 +2095,7 @@ class core_accesslib_testcase extends advanced_testcase {
         unassign_capability('moodle/site:accessallgroups', $allroles['teacher'], $frontpagecontext->id, true);
         unset($rc);
 
-        accesslib_clear_all_caches(false); // Must be done after assign_capability().
+        accesslib_clear_all_caches_for_unit_testing(); // Must be done after assign_capability().
 
 
         // Test role_assign(), role_unassign(), role_unassign_all() functions.
@@ -2112,7 +2112,7 @@ class core_accesslib_testcase extends advanced_testcase {
         $this->assertEquals(0, $DB->count_records('role_assignments', array('contextid'=>$context->id)));
         unset($context);
 
-        accesslib_clear_all_caches(false); // Just in case.
+        accesslib_clear_all_caches_for_unit_testing(); // Just in case.
 
 
         // Test has_capability(), get_users_by_capability(), role_switch(), reload_all_capabilities() and friends functions.
@@ -2173,7 +2173,7 @@ class core_accesslib_testcase extends advanced_testcase {
 
         assign_capability('mod/page:view', CAP_PREVENT, $allroles['guest'], $systemcontext, true);
 
-        accesslib_clear_all_caches(false); // Must be done after assign_capability().
+        accesslib_clear_all_caches_for_unit_testing(); /// Must be done after assign_capability().
 
         // Extra tests for guests and not-logged-in users because they can not be verified by cross checking
         // with get_users_by_capability() where they are ignored.
@@ -2296,7 +2296,7 @@ class core_accesslib_testcase extends advanced_testcase {
         unset($permissions);
         unset($roles);
 
-        accesslib_clear_all_caches(false); // Must be done after assign_capability().
+        accesslib_clear_all_caches_for_unit_testing(); // must be done after assign_capability().
 
         // Test time - let's set up some real user, just in case the logic for USER affects the others...
         $USER = $DB->get_record('user', array('id'=>$testusers[3]));
index be7666f..2a1a05e 100644 (file)
@@ -133,35 +133,104 @@ class core_authlib_testcase extends advanced_testcase {
         $user1 = $this->getDataGenerator()->create_user(array('username'=>'username1', 'password'=>'password1'));
         $user2 = $this->getDataGenerator()->create_user(array('username'=>'username2', 'password'=>'password2', 'suspended'=>1));
         $user3 = $this->getDataGenerator()->create_user(array('username'=>'username3', 'password'=>'password3', 'auth'=>'nologin'));
-
+        // Capture events.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username1', 'password1');
+        $events = $sink->get_events();
+        $sink->close();
+
+        // No event is triggred.
+        $this->assertEmpty($events);
         $this->assertInstanceOf('stdClass', $result);
         $this->assertEquals($user1->id, $result->id);
 
         $reason = null;
+        // Capture event.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username1', 'password1', false, $reason);
+        $events = $sink->get_events();
+        $sink->close();
+
+        // No event is triggred.
+        $this->assertEmpty($events);
         $this->assertInstanceOf('stdClass', $result);
         $this->assertEquals(AUTH_LOGIN_OK, $reason);
 
         $reason = null;
+        // Capture failed login event.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username1', 'nopass', false, $reason);
+        $events = $sink->get_events();
+        $sink->close();
+        $event = array_pop($events);
+
         $this->assertFalse($result);
         $this->assertEquals(AUTH_LOGIN_FAILED, $reason);
+        // Test Event.
+        $this->assertInstanceOf('\core\event\user_login_failed', $event);
+        $expectedlogdata = array(SITEID, 'login', 'error', 'index.php', 'username1');
+        $this->assertEventLegacyLogData($expectedlogdata, $event);
+        $eventdata = $event->get_data();
+        $this->assertSame($eventdata['other']['username'], 'username1');
+        $this->assertSame($eventdata['other']['reason'], AUTH_LOGIN_FAILED);
+        $this->assertEventContextNotUsed($event);
 
         $reason = null;
+        // Capture failed login event.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username2', 'password2', false, $reason);
+        $events = $sink->get_events();
+        $sink->close();
+        $event = array_pop($events);
+
         $this->assertFalse($result);
         $this->assertEquals(AUTH_LOGIN_SUSPENDED, $reason);
+        // Test Event.
+        $this->assertInstanceOf('\core\event\user_login_failed', $event);
+        $expectedlogdata = array(SITEID, 'login', 'error', 'index.php', 'username2');
+        $this->assertEventLegacyLogData($expectedlogdata, $event);
+        $eventdata = $event->get_data();
+        $this->assertSame($eventdata['other']['username'], 'username2');
+        $this->assertSame($eventdata['other']['reason'], AUTH_LOGIN_SUSPENDED);
+        $this->assertEventContextNotUsed($event);
 
         $reason = null;
+        // Capture failed login event.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username3', 'password3', false, $reason);
+        $events = $sink->get_events();
+        $sink->close();
+        $event = array_pop($events);
+
         $this->assertFalse($result);
         $this->assertEquals(AUTH_LOGIN_SUSPENDED, $reason);
+        // Test Event.
+        $this->assertInstanceOf('\core\event\user_login_failed', $event);
+        $expectedlogdata = array(SITEID, 'login', 'error', 'index.php', 'username3');
+        $this->assertEventLegacyLogData($expectedlogdata, $event);
+        $eventdata = $event->get_data();
+        $this->assertSame($eventdata['other']['username'], 'username3');
+        $this->assertSame($eventdata['other']['reason'], AUTH_LOGIN_SUSPENDED);
+        $this->assertEventContextNotUsed($event);
 
         $reason = null;
+        // Capture failed login event.
+        $sink = $this->redirectEvents();
         $result = authenticate_user_login('username4', 'password3', false, $reason);
+        $events = $sink->get_events();
+        $sink->close();
+        $event = array_pop($events);
+
         $this->assertFalse($result);
         $this->assertEquals(AUTH_LOGIN_NOUSER, $reason);
+        // Test Event.
+        $this->assertInstanceOf('\core\event\user_login_failed', $event);
+        $expectedlogdata = array(SITEID, 'login', 'error', 'index.php', 'username4');
+        $this->assertEventLegacyLogData($expectedlogdata, $event);
+        $eventdata = $event->get_data();
+        $this->assertSame($eventdata['other']['username'], 'username4');
+        $this->assertSame($eventdata['other']['reason'], AUTH_LOGIN_NOUSER);
+        $this->assertEventContextNotUsed($event);
 
         set_config('lockoutthreshold', 3);
 
index d89c9c7..6ac31a6 100644 (file)
@@ -78,11 +78,11 @@ class behat_hooks extends behat_base {
     protected static $currentstepexception = null;
 
     /**
-     * If we are saving screenshots on failures we should use the same parent dir during a run.
+     * If we are saving any kind of dump on failure we should use the same parent dir during a run.
      *
      * @var The parent dir name
      */
-    protected static $screenshotsdirname = false;
+    protected static $faildumpdirname = false;
 
     /**
      * Gives access to moodle codebase, ensures all is ready and sets up the test lock.
@@ -142,8 +142,8 @@ class behat_hooks extends behat_base {
             self::$lastbrowsersessionstart = time();
         }
 
-        if (!empty($CFG->behat_screenshots_path) && !is_writable($CFG->behat_screenshots_path)) {
-            throw new Exception('You set $CFG->behat_screenshots_path to a non-writable directory');
+        if (!empty($CFG->behat_faildump_path) && !is_writable($CFG->behat_faildump_path)) {
+            throw new Exception('You set $CFG->behat_faildump_path to a non-writable directory');
         }
     }
 
@@ -272,7 +272,7 @@ class behat_hooks extends behat_base {
         global $CFG;
 
         // Save a screenshot if the step failed.
-        if (!empty($CFG->behat_screenshots_path) &&
+        if (!empty($CFG->behat_faildump_path) &&
                 $event->getResult() === StepEvent::FAILED) {
             $this->take_screenshot($event);
         }
@@ -297,12 +297,29 @@ class behat_hooks extends behat_base {
     }
 
     /**
-     * Getter for self::$screenshotsdirname
+     * Execute any steps required after the step has finished.
+     *
+     * This includes creating an HTML dump of the content if there was a failure.
+     *
+     * @AfterStep
+     */
+    public function after_step($event) {
+        global $CFG;
+
+        // Save the page content if the step failed.
+        if (!empty($CFG->behat_faildump_path) &&
+                $event->getResult() === StepEvent::FAILED) {
+            $this->take_contentdump($event);
+        }
+    }
+
+    /**
+     * Getter for self::$faildumpdirname
      *
      * @return string
      */
-    protected function get_run_screenshots_dir() {
-        return self::$screenshotsdirname;
+    protected function get_run_faildump_dir() {
+        return self::$faildumpdirname;
     }
 
     /**
@@ -312,34 +329,61 @@ class behat_hooks extends behat_base {
      * @param StepEvent $event
      */
     protected function take_screenshot(StepEvent $event) {
-        global $CFG;
-
         // Goutte can't save screenshots.
         if (!$this->running_javascript()) {
             return false;
         }
 
-        // All the run screenshots in the same parent dir.
-        if (!$screenshotsdirname = self::get_run_screenshots_dir()) {
-            $screenshotsdirname = self::$screenshotsdirname = date('Ymd_His');
+        list ($dir, $filename) = $this->get_faildump_filename($event, 'png');
+        $this->saveScreenshot($filename, $dir);
+    }
+
+    /**
+     * Take a dump of the page content when a step fails.
+     *
+     * @throws Exception
+     * @param StepEvent $event
+     */
+    protected function take_contentdump(StepEvent $event) {
+        list ($dir, $filename) = $this->get_faildump_filename($event, 'html');
+
+        $fh = fopen($dir . DIRECTORY_SEPARATOR . $filename, 'w');
+        fwrite($fh, $this->getSession()->getPage()->getContent());
+        fclose($fh);
+    }
+
+    /**
+     * Determine the full pathname to store a failure-related dump.
+     *
+     * This is used for content such as the DOM, and screenshots.
+     *
+     * @param StepEvent $event
+     * @param String $filetype The file suffix to use.
+     */
+    protected function get_faildump_filename(StepEvent $event, $filetype) {
+        global $CFG;
+
+        // All the contentdumps should be in the same parent dir.
+        if (!$faildumpdir = self::get_run_faildump_dir()) {
+            $faildumpdir = self::$faildumpdirname = date('Ymd_His');
 
-            $dir = $CFG->behat_screenshots_path . DIRECTORY_SEPARATOR . $screenshotsdirname;
+            $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir;
 
-            if (!mkdir($dir, $CFG->directorypermissions, true)) {
+            if (!is_dir($dir) && !mkdir($dir, $CFG->directorypermissions, true)) {
                 // It shouldn't, we already checked that the directory is writable.
-                throw new Exception('No directories can be created inside $CFG->behat_screenshots_path, check the directory permissions.');
+                throw new Exception('No directories can be created inside $CFG->behat_faildump_path, check the directory permissions.');
             }
         } else {
             // We will always need to know the full path.
-            $dir = $CFG->behat_screenshots_path . DIRECTORY_SEPARATOR . $screenshotsdirname;
+            $dir = $CFG->behat_faildump_path . DIRECTORY_SEPARATOR . $faildumpdir;
         }
 
         // The scenario title + the failed step text.
-        // We want a i-am-the-scenario-title_i-am-the-failed-step.png format.
+        // We want a i-am-the-scenario-title_i-am-the-failed-step.$filetype format.
         $filename = $event->getStep()->getParent()->getTitle() . '_' . $event->getStep()->getText();
-        $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename) . '.png';
+        $filename = preg_replace('/([^a-zA-Z0-9\_]+)/', '-', $filename) . '.' . $filetype;
 
-        $this->saveScreenshot($filename, $dir);
+        return array($dir, $filename);
     }
 
     /**
@@ -513,3 +557,4 @@ class behat_hooks extends behat_base {
     }
 
 }
+
diff --git a/lib/tests/code_test.php b/lib/tests/code_test.php
deleted file mode 100644 (file)
index b206567..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-<?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/>.
-
-/**
- * Code quality unit tests that are fast enough to run each time.
- *
- * @package    core
- * @category   phpunit
- * @copyright  &copy; 2006 The Open University
- * @author     T.J.Hunt@open.ac.uk
- * @license    http://www.gnu.org/copyleft/gpl.html GNU Public License
- */
-
-defined('MOODLE_INTERNAL') || die();
-
-class core_code_testcase extends advanced_testcase {
-    protected $badstrings;
-    protected $extensions_to_ignore = array('exe', 'gif', 'ico', 'jpg', 'png', 'ttf', 'log');
-    protected $ignore_folders = array();
-
-    public function test_dnc() {
-        global $CFG;
-
-        if ($CFG->ostype === 'UNIX') {
-            // Try it the faster way.
-            $oldcwd = getcwd();
-            chdir($CFG->dirroot);
-            $output = null;
-            $exclude = array();
-            foreach ($this->extensions_to_ignore as $ext) {
-                $exclude[] = '--exclude="*.'.$ext.'"';
-            }
-            $exclude = implode(' ', $exclude);
-            exec('grep -r '.$exclude.' DONOT'.'COMMIT .', $output, $code);
-            chdir($oldcwd);
-            // Return code 0 means found, return code 1 means NOT found, 127 is grep not found.
-            if ($code == 1) {
-                // Executed only if no file failed the test.
-                $this->assertTrue(true);
-                return;
-            }
-        }
-
-        $regexp = '/\.(' . implode('|', $this->extensions_to_ignore) . ')$/';
-        $this->badstrings = array();
-        $this->badstrings['DONOT' . 'COMMIT'] = 'DONOT' . 'COMMIT'; // If we put the literal string here, it fails the test!
-        $this->badstrings['trailing whitespace'] = "[\t ][\r\n]";
-        foreach ($this->badstrings as $description => $ignored) {
-            $this->allok[$description] = true;
-        }
-        $this->recurseFolders($CFG->dirroot, 'search_file_for_dnc', $regexp, true);
-        $this->assertTrue(true); // Executed only if no file failed the test.
-    }
-
-    protected function search_file_for_dnc($filepath) {
-        $content = file_get_contents($filepath);
-        foreach ($this->badstrings as $description => $badstring) {
-            if (stripos($content, $badstring) !== false) {
-                $this->fail("File $filepath contains $description.");
-            }
-        }
-    }
-}
index 63d67d6..23d7770 100644 (file)
@@ -812,7 +812,7 @@ class core_completionlib_testcase extends advanced_testcase {
         $this->assertInstanceOf('\core\event\course_module_completion_updated', $event);
         $this->assertEquals($forum->cmid, $event->get_record_snapshot('course_modules_completion', $event->objectid)->coursemoduleid);
         $this->assertEquals($current, $event->get_record_snapshot('course_modules_completion', $event->objectid));
-        $this->assertEquals(context_module::instance($forum->id), $event->get_context());
+        $this->assertEquals(context_module::instance($forum->cmid), $event->get_context());
         $this->assertEquals($USER->id, $event->userid);
         $this->assertEquals($this->user->id, $event->other['relateduserid']);
         $this->assertInstanceOf('moodle_url', $event->get_url());
index 61b3b65..b4b8180 100644 (file)
@@ -370,16 +370,18 @@ class core_coursecatlib_testcase extends advanced_testcase {
      * Test the countall function
      */
     public function test_count_all() {
-        // There should be just the default category.
-        $this->assertEquals(1, coursecat::count_all());
+        global $DB;
+        // Dont assume there is just one. An add-on might create a category as part of the install.
+        $numcategories = $DB->count_records('course_categories');
+        $this->assertEquals($numcategories, coursecat::count_all());
         $category1 = coursecat::create(array('name' => 'Cat1'));
         $category2 = coursecat::create(array('name' => 'Cat2', 'parent' => $category1->id));
         $category3 = coursecat::create(array('name' => 'Cat3', 'parent' => $category2->id, 'visible' => 0));
-        // Now we've got four.
-        $this->assertEquals(4, coursecat::count_all());
+        // Now we've got three more.
+        $this->assertEquals($numcategories + 3, coursecat::count_all());
         cache_helper::purge_by_event('changesincoursecat');
         // We should still have 4.
-        $this->assertEquals(4, coursecat::count_all());
+        $this->assertEquals($numcategories + 3, coursecat::count_all());
     }
 
     /**
index 46c710b..c279e28 100644 (file)
@@ -134,7 +134,7 @@ class core_messagelib_testcase extends advanced_testcase {
         // however mod_quiz doesn't have a data generator.
         // Instead we're going to use backup notifications and give and take away the capability at various levels.
         $assign = $this->getDataGenerator()->create_module('assign', array('course'=>$course->id));
-        $modulecontext = context_module::instance($assign->id);
+        $modulecontext = context_module::instance($assign->cmid);
 
         // Create and enrol a teacher.
         $teacherrole = $DB->get_record('role', array('shortname'=>'editingteacher'), '*', MUST_EXIST);
@@ -162,7 +162,7 @@ class core_messagelib_testcase extends advanced_testcase {
         // They should now be able to see the backup message.
         assign_capability('moodle/site:config', CAP_ALLOW, $teacherrole->id, $modulecontext->id, true);
         accesslib_clear_all_caches_for_unit_testing();
-        $modulecontext = context_module::instance($assign->id);
+        $modulecontext = context_module::instance($assign->cmid);
         $this->assertTrue(has_capability('moodle/site:config', $modulecontext));
 
         $providers = message_get_providers_for_user($teacher->id);
@@ -173,7 +173,7 @@ class core_messagelib_testcase extends advanced_testcase {
         // They should not be able to see the backup message.
         assign_capability('moodle/site:config', CAP_PROHIBIT, $teacherrole->id, $coursecontext->id, true);
         accesslib_clear_all_caches_for_unit_testing();
-        $modulecontext = context_module::instance($assign->id);
+        $modulecontext = context_module::instance($assign->cmid);
         $this->assertFalse(has_capability('moodle/site:config', $modulecontext));
 
         $providers = message_get_providers_for_user($teacher->id);
index dbb4b80..2e73ea2 100644 (file)
@@ -2452,10 +2452,8 @@ class core_moodlelib_testcase extends advanced_testcase {
         $events = $sink->get_events();
         $sink->close();
 
-        $this->assertCount(2, $events);
-        $event = $events[0];
-        $this->assertInstanceOf('\core\event\user_updated', $event);
-        $event = $events[1];
+        $this->assertCount(1, $events);
+        $event = reset($events);
         $this->assertInstanceOf('\core\event\user_loggedin', $event);
         $this->assertEquals('user', $event->objecttable);
         $this->assertEquals($user->id, $event->objectid);
@@ -2470,7 +2468,6 @@ class core_moodlelib_testcase extends advanced_testcase {
 
         $this->assertTimeCurrent($USER->firstaccess);
         $this->assertTimeCurrent($USER->lastaccess);
-        $this->assertTimeCurrent($USER->timemodified);
         $this->assertTimeCurrent($USER->currentlogin);
         $this->assertSame(sesskey(), $USER->sesskey);
         $this->assertTimeCurrent($USER->preference['_lastloaded']);
similarity index 81%
rename from backup/util/progress/tests/progress_test.php
rename to lib/tests/progress_test.php
index 1e4f8c8..5181f4f 100644 (file)
@@ -17,7 +17,7 @@
 /**
  * Unit tests for the progress classes.
  *
- * @package core_backup
+ * @package core_progress
  * @category phpunit
  * @copyright 2013 The Open University
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 
 defined('MOODLE_INTERNAL') || die();
 
-// Include all the needed stuff.
-global $CFG;
-require_once($CFG->dirroot . '/backup/util/progress/core_backup_progress.class.php');
-
 /**
  * Progress tests.
  */
-class backup_progress_testcase extends basic_testcase {
+class core_progress_testcase extends basic_testcase {
 
     /**
      * Tests for basic use with simple numeric progress.
      */
     public function test_basic() {
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
 
         // Check values of empty progress things.
         $this->assertFalse($progress->is_in_progress_section());
@@ -59,7 +55,7 @@ class backup_progress_testcase extends basic_testcase {
         core_php_time_limit::get_and_clear_unit_test_data();
         $progress->progress(2);
         $this->assertTrue($progress->was_update_called());
-        $this->assertEquals(array(core_backup_progress::TIME_LIMIT_WITHOUT_PROGRESS),
+        $this->assertEquals(array(\core\progress\base::TIME_LIMIT_WITHOUT_PROGRESS),
                 core_php_time_limit::get_and_clear_unit_test_data());
 
         // Check the new value.
@@ -68,7 +64,7 @@ class backup_progress_testcase extends basic_testcase {
         // Do another progress run at same time, it should be ignored.
         $progress->progress(3);
         $this->assertFalse($progress->was_update_called());
-        $this->assert_min_max(0.2, 0.2, $progress);
+        $this->assert_min_max(0.3, 0.3, $progress);
 
         // End the section. This should cause an update.
         $progress->end_progress();
@@ -86,7 +82,7 @@ class backup_progress_testcase extends basic_testcase {
      */
     public function test_nested() {
         // Outer progress goes from 0 to 10.
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
         $progress->start_progress('hello', 10);
 
         // Get up to 4, check position.
@@ -163,7 +159,7 @@ class backup_progress_testcase extends basic_testcase {
      * Tests the feature for 'weighting' nested progress.
      */
     public function test_nested_weighted() {
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
         $progress->start_progress('', 10);
 
         // First nested child has 2 units of its own and is worth 1 unit.
@@ -183,7 +179,7 @@ class backup_progress_testcase extends basic_testcase {
         $this->assert_min_max(0.4, 0.4, $progress);
 
         // Next indeterminate child is worth 6 units.
-        $progress->start_progress('', core_backup_progress::INDETERMINATE, 6);
+        $progress->start_progress('', \core\progress\base::INDETERMINATE, 6);
         $progress->step_time();
         $progress->progress();
         $this->assert_min_max(0.4, 1.0, $progress);
@@ -196,7 +192,7 @@ class backup_progress_testcase extends basic_testcase {
      * to be similar.
      */
     public function test_realistic() {
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
         $progress->start_progress('parent', 100);
         $progress->start_progress('child', 1);
         $progress->progress(1);
@@ -210,7 +206,7 @@ class backup_progress_testcase extends basic_testcase {
      * zero entries.
      */
     public function test_zero() {
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
         $progress->start_progress('parent', 100);
         $progress->progress(1);
         $this->assert_min_max(0.01, 0.01, $progress);
@@ -229,7 +225,7 @@ class backup_progress_testcase extends basic_testcase {
      * Tests for any exceptions due to invalid calls.
      */
     public function test_exceptions() {
-        $progress = new core_backup_mock_progress();
+        $progress = new core_mock_progress();
 
         // Check errors when empty.
         try {
@@ -268,7 +264,7 @@ class backup_progress_testcase extends basic_testcase {
         // Indeterminate when value expected.
         $progress->start_progress('hello', 10);
         try {
-            $progress->progress(core_backup_progress::INDETERMINATE);
+            $progress->progress(\core\progress\base::INDETERMINATE);
             $this->fail();
         } catch (coding_exception $e) {
             $this->assertEquals(1, preg_match('~expecting value~', $e->getMessage()));
@@ -321,14 +317,73 @@ class backup_progress_testcase extends basic_testcase {
         }
     }
 
+    public function test_progress_change() {
+
+        $progress = new core_mock_progress();
+
+        $progress->start_progress('hello', 50);
+
+
+        for ($n = 1; $n <= 10; $n++) {
+            $progress->increment_progress();
+        }
+
+        // Check numeric position and indeterminate count.
+        $this->assert_min_max(0.2, 0.2, $progress);
+        $this->assertEquals(1, $progress->get_progress_count());
+
+        // Make some progress and check that the time limit gets added.
+        $progress->step_time();
+
+        for ($n = 1; $n <= 20; $n++) {
+            $progress->increment_progress();
+        }
+
+        $this->assertTrue($progress->was_update_called());
+
+        // Check the new value.
+        $this->assert_min_max(0.6, 0.6, $progress);
+        $this->assertEquals(2, $progress->get_progress_count());
+
+        for ($n = 1; $n <= 10; $n++) {
+            $progress->increment_progress();
+        }
+        $this->assertFalse($progress->was_update_called());
+        $this->assert_min_max(0.8, 0.8, $progress);
+        $this->assertEquals(2, $progress->get_progress_count());
+
+        // Do another progress run at same time, it should be ignored.
+        $progress->increment_progress(5);
+        $this->assertFalse($progress->was_update_called());
+        $this->assert_min_max(0.9, 0.9, $progress);
+        $this->assertEquals(2, $progress->get_progress_count());
+
+        for ($n = 1; $n <= 3; $n++) {
+            $progress->step_time();
+            $progress->increment_progress(1);
+        }
+        $this->assertTrue($progress->was_update_called());
+        $this->assert_min_max(0.96, 0.96, $progress);
+        $this->assertEquals(5, $progress->get_progress_count());
+
+
+        // End the section. This should cause an update.
+        $progress->end_progress();
+        $this->assertTrue($progress->was_update_called());
+        $this->assertEquals(5, $progress->get_progress_count());
+
+        // Because there are no sections left open, it thinks we finished.
+        $this->assert_min_max(1.0, 1.0, $progress);
+    }
+
     /**
      * Checks the current progress values are as expected.
      *
      * @param number $min Expected min progress
      * @param number $max Expected max progress
-     * @param core_backup_mock_progress $progress
+     * @param core_mock_progress $progress
      */
-    private function assert_min_max($min, $max, core_backup_mock_progress $progress) {
+    private function assert_min_max($min, $max, core_mock_progress $progress) {
         $this->assertEquals(array($min, $max),
                 $progress->get_progress_proportion_range());
     }
@@ -338,7 +393,7 @@ class backup_progress_testcase extends basic_testcase {
  * Helper class that records when update_progress is called and allows time
  * stepping.
  */
-class core_backup_mock_progress extends core_backup_progress {
+class core_mock_progress extends \core\progress\base {
     private $updatecalled = false;
     private $time = 1;
 
index 0ff743f..e6a7a3c 100644 (file)
@@ -149,4 +149,55 @@ class core_upgradelib_testcase extends advanced_testcase {
 
         return $DB->get_record('grade_items', array('id' => $item->id));
     }
+
+    public function test_upgrade_fix_missing_root_folders() {
+        global $DB, $SITE;
+
+        $this->resetAfterTest(true);
+
+        // Setup some broken data...
+        // Create two resources (and associated file areas).
+        $this->setAdminUser();
+        $resource1 = $this->getDataGenerator()->get_plugin_generator('mod_resource')
+            ->create_instance(array('course' => $SITE->id));
+        $resource2 = $this->getDataGenerator()->get_plugin_generator('mod_resource')
+            ->create_instance(array('course' => $SITE->id));
+
+        // Delete the folder record of resource1 to simulate broken data.
+        $context = context_module::instance($resource1->cmid);
+        $selectargs = array('contextid' => $context->id,
+                            'component' => 'mod_resource',
+                            'filearea' => 'content',
+                            'itemid' => 0);
+
+        // Verify file records exist.
+        $areafilecount = $DB->count_records('files', $selectargs);
+        $this->assertNotEmpty($areafilecount);
+
+        // Delete the folder record.
+        $folderrecord = $selectargs;
+        $folderrecord['filepath'] = '/';
+        $folderrecord['filename'] = '.';
+
+        // Get previous folder record.
+        $oldrecord = $DB->get_record('files', $folderrecord);
+        $DB->delete_records('files', $folderrecord);
+
+        // Verify the folder record has been removed.
+        $newareafilecount = $DB->count_records('files', $selectargs);
+        $this->assertSame($newareafilecount, $areafilecount - 1);
+
+        $this->assertFalse($DB->record_exists('files', $folderrecord));
+
+        // Run the upgrade step!
+        upgrade_fix_missing_root_folders();
+
+        // Verify the folder record has been restored.
+        $newareafilecount = $DB->count_records('files', $selectargs);
+        $this->assertSame($newareafilecount, $areafilecount);
+
+        $newrecord = $DB->get_record('files', $folderrecord, '*', MUST_EXIST);
+        // Verify the hash is correctly created.
+        $this->assertSame($oldrecord->pathnamehash, $newrecord->pathnamehash);
+    }
 }
index 508fa8d..780ee74 100644 (file)
@@ -23,10 +23,15 @@ YUI:
     deprecated and replaced by the modal attribute. This was actually
     changed in Moodle 2.2, but has only been marked as deprecated now. It
     will be removed in Moodle 2.9.
+  * When destroying any type of dialogue based on moodle-core-notification, the relevant content is also removed from
+    the DOM. Previously it was left orphaned.
 
 JavaSript:
     * The findChildNodes global function has been deprecated. Y.all should
       be used instead.
+    * The callback argument to confirm_action and M.util.show_confirm_dialog has been deprecated. If you need to write a
+      confirmation which includes a callback, please use moodle-core-notification-confirmation and attach callbacks to the
+      events provided.
 
 * New locking api and admin settings to configure the system locking type.
 
index 338a5cc..3f53612 100644 (file)
@@ -1503,7 +1503,7 @@ function install_core($version, $verbose) {
  * @return void, may throw exception
  */
 function upgrade_core($version, $verbose) {
-    global $CFG;
+    global $CFG, $SITE, $DB, $COURSE;
 
     raise_memory_limit(MEMORY_EXTRA);
 
@@ -1534,6 +1534,10 @@ function upgrade_core($version, $verbose) {
             upgrade_main_savepoint($result, $version, false);
         }
 
+        // In case structure of 'course' table has been changed and we forgot to update $SITE, re-read it from db.
+        $SITE = $DB->get_record('course', array('id' => $SITE->id));
+        $COURSE = clone($SITE);
+
         // perform all other component upgrade routines
         update_capabilities('moodle');
         log_update_descriptions('moodle');
@@ -2065,7 +2069,7 @@ function upgrade_grade_item_fix_sortorder() {
 
     $transaction = $DB->start_delegated_transaction();
 
-    $sql = "SELECT g1.id, g1.courseid, g1.sortorder
+    $sql = "SELECT DISTINCT g1.id, g1.courseid, g1.sortorder
               FROM {grade_items} g1
               JOIN {grade_items} g2 ON g1.courseid = g2.courseid
              WHERE g1.sortorder = g2.sortorder AND g1.id != g2.id
@@ -2089,3 +2093,34 @@ function upgrade_grade_item_fix_sortorder() {
 
     $transaction->allow_commit();
 }
+
+/**
+ * Detect file areas with missing root directory records and add them.
+ */
+function upgrade_fix_missing_root_folders() {
+    global $DB, $USER;
+
+    $transaction = $DB->start_delegated_transaction();
+
+    $sql = "SELECT contextid, component, filearea, itemid
+              FROM {files}
+             WHERE (component <> 'user' OR filearea <> 'draft')
+          GROUP BY contextid, component, filearea, itemid
+            HAVING MAX(CASE WHEN filename = '.' AND filepath = '/' THEN 1 ELSE 0 END) = 0";
+
+    $rs = $DB->get_recordset_sql($sql);
+    $defaults = array('filepath' => '/',
+        'filename' => '.',
+        'userid' => 0, // Don't rely on any particular user for these system records.
+        'filesize' => 0,
+        'timecreated' => time(),
+        'timemodified' => time(),
+        'contenthash' => sha1(''));
+    foreach ($rs as $r) {
+        $pathhash = sha1("/$r->contextid/$r->component/$r->filearea/$r->itemid/.");
+        $DB->insert_record('files', (array)$r + $defaults +
+            array('pathnamehash' => $pathhash));
+    }
+    $rs->close();
+    $transaction->allow_commit();
+}
index 1362dc3..a6d6a55 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock-debug.js differ
index a1ab1de..9689bb0 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock-min.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock-min.js differ
index f2c325a..57588b7 100644 (file)
Binary files a/lib/yui/build/moodle-core-dock/moodle-core-dock.js and b/lib/yui/build/moodle-core-dock/moodle-core-dock.js differ
index 5014285..9a609a5 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-debug.js differ
index 7cd70d5..54e334f 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop-min.js differ
index 5014285..9a609a5 100644 (file)
Binary files a/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js and b/lib/yui/build/moodle-core-dragdrop/moodle-core-dragdrop.js differ
index 2d90867..8b3d536 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-debug.js differ
index 18a0282..c831e54 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert-min.js differ
index 2d90867..8b3d536 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js and b/lib/yui/build/moodle-core-notification-alert/moodle-core-notification-alert.js differ
index cf8103c..3c6c547 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-debug.js and b/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-debug.js differ
index cdef098..b3ee827 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-min.js and b/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm-min.js differ
index cf8103c..3c6c547 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm.js and b/lib/yui/build/moodle-core-notification-confirm/moodle-core-notification-confirm.js differ
index 2e691ac..2e4bc5d 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-debug.js differ
index 138747b..313aa1c 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js differ
index cf96f86..27a544a 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js and b/lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js differ
index 065a4e2..c4687f0 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-debug.js differ
index 731bf9f..b1177b1 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception-min.js differ
index 065a4e2..c4687f0 100644 (file)
Binary files a/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js and b/lib/yui/build/moodle-core-notification-exception/moodle-core-notification-exception.js differ
index f73ddea..1b78536 100644 (file)
@@ -191,7 +191,7 @@ M.core.dock.fixTitleOrientation = function(title, text) {
     }
 
     // We need to fix a font-size - sorry theme designers.
-    test = Y.Node.create('<h2><span class="transform-test-node" style="font-size:'+fontsize+';">'+text+'</span></h2>');
+    test = Y.Node.create('<h2 class="transform-test-heading"><span class="transform-test-node" style="font-size:'+fontsize+';">'+text+'</span></h2>');
     BODY.insert(test, 0);
     width = test.one('span').get('offsetWidth') * 1.2;
     height = test.one('span').get('offsetHeight');
@@ -202,12 +202,10 @@ M.core.dock.fixTitleOrientation = function(title, text) {
 
     // Move the title into position
     title.setStyles({
-        'margin' : '0',
-        'padding' : '0',
         'position' : 'relative',
         'fontSize' : fontsize,
         'width' : width,
-        'top' : width/2
+        'top' : (width - height)/2
     });
 
     // Positioning is different when in RTL mode.
@@ -758,14 +756,16 @@ DOCK.prototype = {
             showregions = false,
             i;
         // First look for understood regions.
-        Y.all(SELECTOR.blockregion).each(function(){
+        Y.all(SELECTOR.blockregion).each(function(region){
             var regionname = region.getData('blockregion');
             if (region.all('.block').size() > 0) {
                 populatedblockregions++;
                 BODY.addClass('used-region-'+regionname);
                 BODY.removeClass('empty-region-'+regionname);
+                BODY.removeClass('docked-region-'+regionname);
             } else {
                 BODY.addClass('empty-region-'+regionname);
+                BODY.addClass('docked-region-'+regionname);
                 BODY.removeClass('used-region-'+regionname);
             }
         });
index 4fc5895..4fd5899 100644 (file)
@@ -37,7 +37,11 @@ TABHEIGHTMANAGER.prototype = {
         var dock = this.get('dock'),
             node = dock.get('dockNode'),
             items = dock.dockeditems,
-            possibleheight = node.get('offsetHeight') - node.one('.controls').get('offsetHeight') - (dock.get('bufferPanel')*3) - (items.length*2),
+            containermargin = parseInt(node.one('.dockeditem_container').getStyle('marginTop').replace('/[^0-9]+$/', ''), 10),
+            dockheight = node.get('offsetHeight') - containermargin,
+            controlheight = node.one('.controls').get('offsetHeight'),
+            buffer = (dock.get('bufferPanel') * 3),
+            possibleheight = dockheight - controlheight - buffer - (items.length*2),
             totalheight = 0,
             id, dockedtitle;
         if (items.length > 0) {
index 87bfd03..a77fe23 100644 (file)
@@ -62,6 +62,24 @@ Y.extend(DRAGDROP, Y.Base, {
      */
     parentnodeclass: null,
 
+    /**
+     * The label to use with keyboard drag/drop to describe items of the same Node.
+     *
+     * @property samenodelabel
+     * @type Object
+     * @default null
+     */
+    samenodelabel : null,
+
+    /**
+     * The label to use with keyboard drag/drop to describe items of the parent Node.
+     *
+     * @property samenodelabel
+     * @type Object
+     * @default null
+     */
+    parentnodelabel : null,
+
     /**
      * The groups for this instance.
      *
@@ -146,8 +164,7 @@ Y.extend(DRAGDROP, Y.Base, {
             .setAttribute('title', title)
             .setAttribute('tabIndex', 0)
             .setAttribute('data-draggroups', this.groups)
-            .setAttribute('role', 'button')
-            .setAttribute('aria-grabbed', 'false');
+            .setAttribute('role', 'button');
         dragelement.appendChild(dragicon);
         dragelement.addClass(MOVEICON.cssclass);
 
@@ -160,6 +177,7 @@ Y.extend(DRAGDROP, Y.Base, {
 
     unlock_drag_handle: function(drag, classname) {
         drag.addHandle('.'+classname);
+        drag.get('activeHandle').focus();
     },
 
     ajax_failure: function(response) {
@@ -350,8 +368,6 @@ Y.extend(DRAGDROP, Y.Base, {
         M.core.dragdrop.keydragcontainer = dragcontainer;
         M.core.dragdrop.keydraghandle = draghandle;
 
-        // Indicate to a screenreader the node that is selected for drag and drop.
-        dragcontainer.setAttribute('aria-grabbed', 'true');
         // Get the name of the thing to move.
         var nodetitle = this.find_element_text(dragcontainer);
         var dialogtitle = M.util.get_string('movecontent', 'moodle', nodetitle);
@@ -395,13 +411,17 @@ Y.extend(DRAGDROP, Y.Base, {
                 listlink = Y.Node.create('<a></a>');
                 nodetitle = this.find_element_text(labelroot);
 
-                listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
+                if (this.samenodelabel && node.hasClass(this.samenodeclass)) {
+                    listitemtext = M.util.get_string(this.samenodelabel.identifier, this.samenodelabel.component, nodetitle);
+                } else if (this.parentnodelabel && node.hasClass(this.parentnodeclass)) {
+                    listitemtext = M.util.get_string(this.parentnodelabel.identifier, this.parentnodelabel.component, nodetitle);
+                } else {
+                    listitemtext = M.util.get_string('tocontent', 'moodle', nodetitle);
+                }
                 listlink.setContent(listitemtext);
 
                 // Add a data attribute so we can get the real drop target.
                 listlink.setAttribute('data-drop-target', node.get('id'));
-                // Notify the screen reader this is a valid drop target.
-                listlink.setAttribute('aria-dropeffect', 'move');
                 // Allow tabbing to the link.
                 listlink.setAttribute('tabindex', '0');
 
@@ -421,9 +441,18 @@ Y.extend(DRAGDROP, Y.Base, {
             bodyContent: droplist,
             draggable: true,
             visible: true,
-            centered: true
+            center: true,
+            modal: true
         });
 
+        M.core.dragdrop.dropui.after('visibleChange', function(e) {
+            // After the dialogue has been closed, we call the cancel function. This will
+            // ensure that tidying up happens (e.g. focusing on the start Node).
+            if (e.prevVal && !e.newVal) {
+                this.global_cancel_keyboard_drag();
+            }
+        }, this);
+
         // Focus the first drop target.
         if (droplist.one('a')) {
             droplist.one('a').focus();
@@ -463,6 +492,9 @@ Y.extend(DRAGDROP, Y.Base, {
             if (param === 'node' || param === 'dragNode' || param === 'dropNode') {
                 return this.node;
             }
+            if (param === 'activeHandle') {
+                return this.node.one('.editing_move');
+            }
             return null;
         };
 
@@ -491,7 +523,6 @@ Y.extend(DRAGDROP, Y.Base, {
     global_keyboard_drop: function(e) {
         // The drag node was saved.
         var dragcontainer = M.core.dragdrop.keydragcontainer;
-        dragcontainer.setAttribute('aria-grabbed', 'false');
         // The real drop node is stored in an attribute of the proxy.
         var droptarget = Y.one('#' + e.target.getAttribute('data-drop-target'));
 
@@ -506,7 +537,6 @@ Y.extend(DRAGDROP, Y.Base, {
         this.drag_start(dragevent);
         this.global_drop_over(dropevent);
         this.global_drop_hit(dropevent);
-        M.core.dragdrop.keydraghandle.focus();
     },
 
     /**
@@ -516,7 +546,7 @@ Y.extend(DRAGDROP, Y.Base, {
      */
     global_cancel_keyboard_drag: function() {
         if (M.core.dragdrop.keydragcontainer) {
-            M.core.dragdrop.keydragcontainer.setAttribute('aria-grabbed', 'false');
+            // Focus on the node which was being dragged.
             M.core.dragdrop.keydraghandle.focus();
             M.core.dragdrop.keydragcontainer = null;
         }
index f51c20b..b7a0edc 100644 (file)
@@ -21,7 +21,7 @@ ALERT = function(config) {
     ALERT.superclass.constructor.apply(this, [config]);
 };
 Y.extend(ALERT, M.core.dialogue, {
-    _enterKeypress : null,
+    closeEvents: [],
     initializer : function() {
         this.publish('complete');
         var yes = Y.Node.create('<input type="button" id="id_yuialertconfirm-' + this.get('COUNT') + '" value="'+this.get(CONFIRMYES)+'" />'),
@@ -33,12 +33,22 @@ Y.extend(ALERT, M.core.dialogue, {
         this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE);
         this.setStdModContent(Y.WidgetStdMod.HEADER,
                 '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + this.get(TITLE) + '</h1>', Y.WidgetStdMod.REPLACE);
-        this.after('destroyedChange', function(){this.get(BASE).remove();}, this);
-        this._enterKeypress = Y.on('key', this.submit, window, 'down:13', this);
-        yes.on('click', this.submit, this);
+
+        this.closeEvents.push(
+            Y.on('key', this.submit, window, 'down:13', this),
+            yes.on('click', this.submit, this)
+        );
+
+        var closeButton = this.get('boundingBox').one('.closebutton');
+        if (closeButton) {
+            // The close button should act exactly like the 'No' button.
+            this.closeEvents.push(
+                closeButton.on('click', this.submit, this)
+            );
+        }
     },
     submit : function() {
-        this._enterKeypress.detach();
+        new Y.EventHandle(this.closeEvents).detach();
         this.fire('complete');
         this.hide();
         this.destroy();
index fe80edc..ec6cf76 100644 (file)
@@ -20,8 +20,7 @@ CONFIRM = function(config) {
     CONFIRM.superclass.constructor.apply(this, [config]);
 };
 Y.extend(CONFIRM, M.core.dialogue, {
-    _enterKeypress : null,
-    _escKeypress : null,
+    closeEvents: [],
     initializer : function() {
         this.publish('complete');
         this.publish('complete-yes');
@@ -37,15 +36,23 @@ Y.extend(CONFIRM, M.core.dialogue, {
         this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE);
         this.setStdModContent(Y.WidgetStdMod.HEADER,
                 '<h1 id="moodle-dialogue-'+this.get('COUNT')+'-header-text">' + this.get(TITLE) + '</h1>', Y.WidgetStdMod.REPLACE);
-        this.after('destroyedChange', function(){this.get(BASE).remove();}, this);
-        this._enterKeypress = Y.on('key', this.submit, window, 'down:13', this, true);
-        this._escKeypress = Y.on('key', this.submit, window, 'down:27', this, false);
-        yes.on('click', this.submit, this, true);
-        no.on('click', this.submit, this, false);
+
+        this.closeEvents.push(
+            Y.on('key', this.submit, window, 'down:27', this, false),
+            yes.on('click', this.submit, this, true),
+            no.on('click', this.submit, this, false)
+        );
+
+        var closeButton = this.get('boundingBox').one('.closebutton');
+        if (closeButton) {
+            // The close button should act exactly like the 'No' button.
+            this.closeEvents.push(
+                closeButton.on('click', this.submit, this)
+            );
+        }
     },
     submit : function(e, outcome) {
-        this._enterKeypress.detach();
-        this._escKeypress.detach();
+        new Y.EventHandle(this.closeEvents).detach();
         this.fire('complete', outcome);
         if (outcome) {
             this.fire('complete-yes');
index 25a0c83..2f3424a 100644 (file)
@@ -111,6 +111,11 @@ Y.extend(DIALOGUE, Y.Panel, {
             this.show();
             this.keyDelegation();
         }
+
+        // Remove the dialogue from the DOM when it is destroyed.
+        this.after('destroyedChange', function(){
+            this.get(BASE).remove(true);
+        }, this);
     },
 
     /**
index ed4324c..ebd83f6 100644 (file)
@@ -49,7 +49,6 @@ Y.extend(EXCEPTION, M.core.dialogue, {
             this._hideTimeout = setTimeout(function(){self.hide();}, delay);
         }
         this.after('visibleChange', this.visibilityChanged, this);
-        this.after('destroyedChange', function(){this.get(BASE).remove();}, this);
         this._keypress = Y.on('key', this.hide, window, 'down:13,27', this);
         this.centerDialogue();
     },
index 7c7a058..64da716 100644 (file)
@@ -12,6 +12,9 @@ Description of import of various YUI libraries into Moodle:
 * update lib/thrirdpartylibs.xml
 * verify our simpleyui rollup contents in /theme/yui_combo.php
 
+If you need to patch the YUI library between its official releases, you *must* read
+http://docs.moodle.org/dev/YUI/Patching.
+
 3/ YUI3 Gallery version gallery-2013.10.02-20-26:
 * selective copy of the "build" directory for the checked out tag of yui3-gallery.
   Unit test code coverage files (*-coverage.js) are removed but no other changes are made.
index cc7056f..3061e78 100644 (file)
@@ -122,6 +122,14 @@ $functions = array(
                 'type' => 'write'
         ),
 
+        'mod_assign_save_grades' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'save_grades',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Save multiple grade updates for an assignment.',
+                'type' => 'write'
+        ),
+
         'mod_assign_save_user_extensions' => array(
                 'classname' => 'mod_assign_external',
                 'methodname' => 'save_user_extensions',
index dc6e06a..2330f90 100644 (file)
@@ -1665,6 +1665,7 @@ class mod_assign_external extends external_api {
     public static function save_grade_parameters() {
         global $CFG;
         require_once("$CFG->dirroot/mod/assign/locallib.php");
+        require_once("$CFG->dirroot/grade/grading/lib.php");
         $instance = new assign(null, null, null);
         $pluginfeedbackparams = array();
 
@@ -1675,20 +1676,41 @@ class mod_assign_external extends external_api {
             }
         }
 
+        $advancedgradingdata = array();
+        $methods = array_keys(grading_manager::available_methods(false));
+        foreach ($methods as $method) {
+            require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
+            $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
+            if (!empty($details)) {
+                $items = array();
+                foreach ($details as $key => $value) {
+                    $value->required = VALUE_OPTIONAL;
+                    unset($value->content->keys['id']);
+                    $items[$key] = new external_multiple_structure (new external_single_structure(
+                        array(
+                            'criterionid' => new external_value(PARAM_INT, 'criterion id'),
+                            'fillings' => $value
+                        )
+                    ));
+                }
+                $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
+            }
+        }
+
         return new external_function_parameters(
             array(
                 'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
                 'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
-                'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user'),
+                'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. Ignored if advanced grading used'),
                 'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
                 'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if the attempt reopen method is manual'),
                 'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
                 'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
                                                                'to all members ' .
                                                                'of the group (for group assignments).'),
-                'plugindata' => new external_single_structure(
-                    $pluginfeedbackparams
-                )
+                'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data', VALUE_DEFAULT, array()),
+                'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
+                                                                       VALUE_DEFAULT, array())
             )
         );
     }
@@ -1698,12 +1720,13 @@ class mod_assign_external extends external_api {
      *
      * @param int $assignmentid The id of the assignment
      * @param int $userid The id of the user
-     * @param float $grade The grade
+     * @param float $grade The grade (ignored if the assignment uses advanced grading)
      * @param int $attemptnumber The attempt number
      * @param bool $addattempt Allow another attempt
      * @param string $workflowstate New workflow state
      * @param bool $applytoall Apply the grade to all members of the group
      * @param array $plugindata Custom data used by plugins
+     * @param array $advancedgradingdata Advanced grading data
      * @return null
      * @since Moodle 2.6
      */
@@ -1714,7 +1737,8 @@ class mod_assign_external extends external_api {
                                       $addattempt,
                                       $workflowstate,
                                       $applytoall,
-                                      $plugindata) {
+                                      $plugindata = array(),
+                                      $advancedgradingdata = array()) {
         global $CFG, $USER;
         require_once("$CFG->dirroot/mod/assign/locallib.php");
 
@@ -1726,22 +1750,38 @@ class mod_assign_external extends external_api {
                                                   'workflowstate' => $workflowstate,
                                                   'addattempt' => $addattempt,
                                                   'applytoall' => $applytoall,
-                                                  'plugindata' => $plugindata));
+                                                  'plugindata' => $plugindata,
+                                                  'advancedgradingdata' => $advancedgradingdata));
 
-        $cm = get_coursemodule_from_instance('assign', $assignmentid, 0, false, MUST_EXIST);
+        $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
         $context = context_module::instance($cm->id);
-
+        self::validate_context($context);
         $assignment = new assign($context, $cm, null);
 
-        $gradedata = (object)$plugindata;
-
-        $gradedata->addattempt = $addattempt;
-        $gradedata->attemptnumber = $attemptnumber;
-        $gradedata->workflowstate = $workflowstate;
-        $gradedata->applytoall = $applytoall;
-        $gradedata->grade = $grade;
+        $gradedata = (object)$params['plugindata'];
+
+        $gradedata->addattempt = $params['addattempt'];
+        $gradedata->attemptnumber = $params['attemptnumber'];
+        $gradedata->workflowstate = $params['workflowstate'];
+        $gradedata->applytoall = $params['applytoall'];
+        $gradedata->grade = $params['grade'];
+
+        if (!empty($params['advancedgradingdata'])) {
+            $advancedgrading = array();
+            $criteria = reset($params['advancedgradingdata']);
+            foreach ($criteria as $key => $criterion) {
+                $details = array();
+                foreach ($criterion as $value) {
+                    foreach ($value['fillings'] as $filling) {
+                        $details[$value['criterionid']] = $filling;
+                    }
+                }
+                $advancedgrading[$key] = $details;
+            }
+            $gradedata->advancedgrading = $advancedgrading;
+        }
 
-        $assignment->save_grade($userid, $gradedata);
+        $assignment->save_grade($params['userid'], $gradedata);
 
         return null;
     }
@@ -1756,6 +1796,157 @@ class mod_assign_external extends external_api {
         return null;
     }
 
+    /**
+     * Describes the parameters for save_grades
+     * @return external_external_function_parameters
+     * @since  Moodle 2.7
+     */
+    public static function save_grades_parameters() {
+        global $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        require_once("$CFG->dirroot/grade/grading/lib.php");
+        $instance = new assign(null, null, null);
+        $pluginfeedbackparams = array();
+
+        foreach ($instance->get_feedback_plugins() as $plugin) {
+            $pluginparams = $plugin->get_external_parameters();
+            if (!empty($pluginparams)) {
+                $pluginfeedbackparams = array_merge($pluginfeedbackparams, $pluginparams);
+            }
+        }
+
+        $advancedgradingdata = array();
+        $methods = array_keys(grading_manager::available_methods(false));
+        foreach ($methods as $method) {
+            require_once($CFG->dirroot.'/grade/grading/form/'.$method.'/lib.php');
+            $details  = call_user_func('gradingform_'.$method.'_controller::get_external_instance_filling_details');
+            if (!empty($details)) {
+                $items = array();
+                foreach ($details as $key => $value) {
+                    $value->required = VALUE_OPTIONAL;
+                    unset($value->content->keys['id']);
+                    $items[$key] = new external_multiple_structure (new external_single_structure(
+                        array(
+                            'criterionid' => new external_value(PARAM_INT, 'criterion id'),
+                            'fillings' => $value
+                        )
+                    ));
+                }
+                $advancedgradingdata[$method] = new external_single_structure($items, 'items', VALUE_OPTIONAL);
+            }
+        }
+
+        return new external_function_parameters(
+            array(
+                'assignmentid' => new external_value(PARAM_INT, 'The assignment id to operate on'),
+                'applytoall' => new external_value(PARAM_BOOL, 'If true, this grade will be applied ' .
+                                                               'to all members ' .
+                                                               'of the group (for group assignments).'),
+                'grades' => new external_multiple_structure(
+                    new external_single_structure(
+                        array (
+                            'userid' => new external_value(PARAM_INT, 'The student id to operate on'),
+                            'grade' => new external_value(PARAM_FLOAT, 'The new grade for this user. '.
+                                                                       'Ignored if advanced grading used'),
+                            'attemptnumber' => new external_value(PARAM_INT, 'The attempt number (-1 means latest attempt)'),
+                            'addattempt' => new external_value(PARAM_BOOL, 'Allow another attempt if manual attempt reopen method'),
+                            'workflowstate' => new external_value(PARAM_ALPHA, 'The next marking workflow state'),
+                            'plugindata' => new external_single_structure($pluginfeedbackparams, 'plugin data',
+                                                                          VALUE_DEFAULT, array()),
+                            'advancedgradingdata' => new external_single_structure($advancedgradingdata, 'advanced grading data',
+                                                                                   VALUE_DEFAULT, array())
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Save multiple student grades for a single assignment.
+     *
+     * @param int $assignmentid The id of the assignment
+     * @param boolean $applytoall If set to true and this is a team assignment,
+     * apply the grade to all members of the group
+     * @param array $grades grade data for one or more students that includes
+     *                  userid - The id of the student being graded
+     *                  grade - The grade (ignored if the assignment uses advanced grading)
+     *                  attemptnumber - The attempt number
+     *                  addattempt - Allow another attempt
+     *                  workflowstate - New workflow state
+     *                  plugindata - Custom data used by plugins
+     *                  advancedgradingdata - Optional Advanced grading data
+     * @throws invalid_parameter_exception if multiple grades are supplied for
+     * a team assignment that has $applytoall set to true
+     * @return null
+     * @since Moodle 2.7
+     */
+    public static function save_grades($assignmentid, $applytoall = false, $grades) {
+        global $CFG, $USER;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+
+        $params = self::validate_parameters(self::save_grades_parameters(),
+                                            array('assignmentid' => $assignmentid,
+                                                  'applytoall' => $applytoall,
+                                                  'grades' => $grades));
+
+        $cm = get_coursemodule_from_instance('assign', $params['assignmentid'], 0, false, MUST_EXIST);
+        $context = context_module::instance($cm->id);
+        self::validate_context($context);
+        $assignment = new assign($context, $cm, null);
+
+        if ($assignment->get_instance()->teamsubmission && $params['applytoall']) {
+            // Check that only 1 user per submission group is provided.
+            $groupids = array();
+            foreach ($params['grades'] as $gradeinfo) {
+                $group = $assignment->get_submission_group($gradeinfo['userid']);
+                if (in_array($group->id, $groupids)) {
+                    throw new invalid_parameter_exception('Multiple grades for the same team have been supplied '
+                                                          .' this is not permitted when the applytoall flag is set');
+                } else {
+                    $groupids[] = $group->id;
+                }
+            }
+        }
+
+        foreach ($params['grades'] as $gradeinfo) {
+            $gradedata = (object)$gradeinfo['plugindata'];
+            $gradedata->addattempt = $gradeinfo['addattempt'];
+            $gradedata->attemptnumber = $gradeinfo['attemptnumber'];
+            $gradedata->workflowstate = $gradeinfo['workflowstate'];
+            $gradedata->applytoall = $params['applytoall'];
+            $gradedata->grade = $gradeinfo['grade'];
+
+            if (!empty($gradeinfo['advancedgradingdata'])) {
+                $advancedgrading = array();
+                $criteria = reset($gradeinfo['advancedgradingdata']);
+                foreach ($criteria as $key => $criterion) {
+                    $details = array();
+                    foreach ($criterion as $value) {
+                        foreach ($value['fillings'] as $filling) {
+                            $details[$value['criterionid']] = $filling;
+                        }
+                    }
+                    $advancedgr