Merge branch 'wip-MDL-47676-master' of git://github.com/abgreeve/moodle
authorDan Poltawski <dan@moodle.com>
Fri, 24 Oct 2014 13:30:44 +0000 (14:30 +0100)
committerDan Poltawski <dan@moodle.com>
Fri, 24 Oct 2014 13:30:44 +0000 (14:30 +0100)
110 files changed:
admin/tool/langimport/classes/event/langpack_imported.php
admin/tool/langimport/classes/event/langpack_removed.php
admin/tool/langimport/classes/event/langpack_updated.php
admin/tool/monitor/classes/event/rule_created.php [new file with mode: 0644]
admin/tool/monitor/classes/event/rule_deleted.php [new file with mode: 0644]
admin/tool/monitor/classes/event/rule_updated.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_created.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_criteria_met.php [new file with mode: 0644]
admin/tool/monitor/classes/event/subscription_deleted.php [new file with mode: 0644]
admin/tool/monitor/classes/eventobservers.php
admin/tool/monitor/classes/output/managerules/renderable.php
admin/tool/monitor/classes/output/managerules/renderer.php
admin/tool/monitor/classes/output/managesubs/renderer.php
admin/tool/monitor/classes/rule_manager.php
admin/tool/monitor/classes/subscription_manager.php
admin/tool/monitor/index.php
admin/tool/monitor/lang/en/tool_monitor.php
admin/tool/monitor/managerules.php
admin/tool/monitor/tests/behat/subscription.feature
admin/tool/monitor/tests/eventobservers_test.php
admin/tool/monitor/tests/events_test.php [new file with mode: 0644]
admin/tool/monitor/tests/rule_manager_test.php
admin/tool/monitor/tests/task_clean_events_test.php
admin/tool/uploadcourse/tests/course_test.php
admin/tool/uploadcourse/tests/processor_test.php
backup/moodle2/tests/moodle2_test.php
calendar/lib.php
course/moodleform_mod.php
course/tests/courselib_test.php
course/tests/externallib_test.php
course/view.php
grade/edit/letter/edit_form.php
grade/edit/letter/index.php
grade/edit/tree/lib.php
grade/edit/tree/outcomeitem_form.php
grade/lib.php
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js
grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js
grade/report/grader/yui/src/gradereporttable/js/floatingheaders.js
grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-debug.js
grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js
grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector.js
grade/report/history/yui/src/userselector/js/userselector.js
grade/report/lib.php
grade/report/singleview/classes/local/screen/user.php
grade/report/singleview/lang/en/gradereport_singleview.php
grade/report/singleview/tests/behat/singleview.feature
grade/report/user/lib.php
grade/tests/behat/behat_grade.php
grade/tests/behat/grade_aggregation.feature
grade/tests/behat/grade_calculated_weights.feature
grade/tests/behat/grade_contribution_with_extra_credit.feature
grade/tests/behat/grade_mingrade.feature
grade/tests/behat/grade_natural_normalisation.feature
grade/tests/behat/grade_scales.feature
grade/tests/behat/grade_single_item_scales.feature
group/assign.php
lang/en/grades.php
lang/en/rating.php
lib/classes/task/manager.php
lib/db/services.php
lib/form/modgrade.php
lib/grade/grade_category.php
lib/grade/grade_grade.php
lib/navigationlib.php
lib/tests/event_user_graded_test.php
lib/tests/questionlib_test.php
lib/tests/scheduled_task_test.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-notification-dialogue/moodle-core-notification-dialogue-debug.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue-min.js
lib/yui/build/moodle-core-notification-dialogue/moodle-core-notification-dialogue.js
lib/yui/src/dock/js/dock.js
lib/yui/src/notification/js/dialogue.js
mod/assign/feedback/editpdf/tests/editpdf_test.php
mod/assign/tests/locallib_test.php
mod/forum/tests/mail_test.php
mod/lesson/db/access.php
mod/lesson/essay.php
mod/lesson/lang/en/lesson.php
mod/lesson/lib.php
mod/lesson/tabs.php
mod/lesson/tests/behat/teacher_grade_essays.feature [new file with mode: 0644]
mod/lesson/version.php
mod/lti/db/upgradelib.php
mod/scorm/report.php
question/engine/datalib.php
question/engine/questionattempt.php
question/engine/questionattemptstep.php
question/engine/tests/datalib_reporting_queries_test.php [moved from question/engine/tests/question_engine_data_mapper_test.php with 92% similarity]
question/engine/tests/datalib_test.php [new file with mode: 0644]
question/type/questiontypebase.php
question/type/random/questiontype.php
report/participation/index.php
theme/base/style/calendar.css
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/grade.css
theme/bootstrapbase/layout/popup.php
theme/bootstrapbase/less/moodle/calendar.less
theme/bootstrapbase/less/moodle/core.less
theme/bootstrapbase/less/moodle/course.less
theme/bootstrapbase/less/moodle/grade.less
theme/bootstrapbase/style/moodle.css
user/index.php
user/profile.php
version.php

index c8bd149..5967929 100644 (file)
@@ -36,6 +36,7 @@ defined('MOODLE_INTERNAL') || die();
  * }
  *
  * @package    tool_langimport
+ * @since      Moodle 2.8
  * @copyright  2014 Dan Poltawski <dan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 82e5270..e528da4 100644 (file)
@@ -36,6 +36,7 @@ defined('MOODLE_INTERNAL') || die();
  * }
  *
  * @package    tool_langimport
+ * @since      Moodle 2.8
  * @copyright  2014 Dan Poltawski <dan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
index 4f0528f..b9d026a 100644 (file)
@@ -36,6 +36,7 @@ defined('MOODLE_INTERNAL') || die();
  * }
  *
  * @package    tool_langimport
+ * @since      Moodle 2.8
  * @copyright  2014 Dan Poltawski <dan@moodle.com>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
diff --git a/admin/tool/monitor/classes/event/rule_created.php b/admin/tool/monitor/classes/event/rule_created.php
new file mode 100644 (file)
index 0000000..a5d6fe1
--- /dev/null
@@ -0,0 +1,77 @@
+<?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/>.
+
+/**
+ * The tool_monitor rule created event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule created event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_created extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventrulecreated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/edit.php', array('ruleid' => $this->objectid,
+            'courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/rule_deleted.php b/admin/tool/monitor/classes/event/rule_deleted.php
new file mode 100644 (file)
index 0000000..988a5b6
--- /dev/null
@@ -0,0 +1,76 @@
+<?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/>.
+
+/**
+ * The tool_monitor rule deleted event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule deleted event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventruledeleted', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' deleted the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/managerules.php', array('courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/rule_updated.php b/admin/tool/monitor/classes/event/rule_updated.php
new file mode 100644 (file)
index 0000000..c17da2d
--- /dev/null
@@ -0,0 +1,77 @@
+<?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/>.
+
+/**
+ * The tool_monitor rule updated event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor rule updated event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class rule_updated extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_rules';
+        $this->data['crud'] = 'u';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventruleupdated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' updated the event monitor rule with id '$this->objectid'.";
+    }
+
+    /**
+     * Get URL related to the action
+     *
+     * @return \moodle_url
+     */
+    public function get_url() {
+        return new \moodle_url('/admin/tool/monitor/edit.php', array('ruleid' => $this->objectid,
+            'courseid' => $this->courseid));
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_created.php b/admin/tool/monitor/classes/event/subscription_created.php
new file mode 100644 (file)
index 0000000..e3d2b81
--- /dev/null
@@ -0,0 +1,67 @@
+<?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/>.
+
+/**
+ * The tool_monitor subscription created event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription created event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_created extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_subscriptions';
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubcreated', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' created the event monitor subscription with id '$this->objectid'.";
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_criteria_met.php b/admin/tool/monitor/classes/event/subscription_criteria_met.php
new file mode 100644 (file)
index 0000000..693bcf4
--- /dev/null
@@ -0,0 +1,86 @@
+<?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/>.
+
+/**
+ * The tool_monitor subscription criteria met event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription criteria met event class.
+ *
+ * @property-read array $other {
+ *      Extra information about event.
+ *
+ *      - string subscriptionid: id of the subscription.
+ * }
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_criteria_met extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['crud'] = 'c';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubcriteriamet', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The criteria for the subscription with id '{$this->other['subscriptionid']}' was met.";
+    }
+
+    /**
+     * Custom validation.
+     *
+     * @throws \coding_exception
+     * @return void
+     */
+    protected function validate_data() {
+        parent::validate_data();
+
+        if (!isset($this->other['subscriptionid'])) {
+            throw new \coding_exception('The \'subscriptionid\' value must be set in other.');
+        }
+    }
+}
diff --git a/admin/tool/monitor/classes/event/subscription_deleted.php b/admin/tool/monitor/classes/event/subscription_deleted.php
new file mode 100644 (file)
index 0000000..e4b2bc0
--- /dev/null
@@ -0,0 +1,67 @@
+<?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/>.
+
+/**
+ * The tool_monitor subscription deleted event.
+ *
+ * @package    tool_monitor
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_monitor\event;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * The tool_monitor subscription deleted event class.
+ *
+ * @package    tool_monitor
+ * @since      Moodle 2.8
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class subscription_deleted extends \core\event\base {
+
+    /**
+     * Init method.
+     *
+     * @return void
+     */
+    protected function init() {
+        $this->data['objecttable'] = 'tool_monitor_subscriptions';
+        $this->data['crud'] = 'd';
+        $this->data['edulevel'] = self::LEVEL_TEACHING;
+    }
+
+    /**
+     * Return localised event name.
+     *
+     * @return string
+     */
+    public static function get_name() {
+        return get_string('eventsubdeleted', 'tool_monitor');
+    }
+
+    /**
+     * Returns description of what happened.
+     *
+     * @return string
+     */
+    public function get_description() {
+        return "The user with id '$this->userid' deleted the event monitor subscription with id '$this->objectid'.";
+    }
+}
index 27d3d29..2ffc4fd 100644 (file)
@@ -54,8 +54,12 @@ class eventobservers {
      */
     public static function course_deleted(\core\event\course_deleted $event) {
         $rules = rule_manager::get_rules_by_courseid($event->courseid);
+        $context = null;
+        if ($event->contextlevel == CONTEXT_COURSE) {
+            $context = $event->get_context();
+        }
         foreach ($rules as $rule) {
-            rule_manager::delete_rule($rule->id);
+            rule_manager::delete_rule($rule->id, $context);
         }
     }
 
@@ -156,6 +160,30 @@ class eventobservers {
                 $count = $DB->count_records_sql($sql, $params);
                 if (!empty($count) && $count >= $subscription->frequency) {
                     $idstosend[] = $subscription->id;
+
+                    // Trigger a subscription_criteria_met event.
+                    // It's possible that the course has been deleted since the criteria was met, so in that case use
+                    // the system context. Set it here and change later if needed.
+                    $context = \context_system::instance();
+                    // We can't perform if (!empty($subscription->courseid)) below as it uses the magic method
+                    // __get to return the variable, which will always result in being empty.
+                    $courseid = $subscription->courseid;
+                    if (!empty($courseid)) {
+                        if ($coursecontext = \context_course::instance($courseid, IGNORE_MISSING)) {
+                            $context = $coursecontext;
+                        }
+                    }
+
+                    $params = array(
+                        'userid' => $subscription->userid,
+                        'courseid' => $subscription->courseid,
+                        'context' => $context,
+                        'other' => array(
+                            'subscriptionid' => $subscription->id
+                        )
+                    );
+                    $event = \tool_monitor\event\subscription_criteria_met::create($params);
+                    $event->trigger();
                 }
             }
             if (!empty($idstosend)) {
index 2c74245..0819aae 100644 (file)
@@ -154,7 +154,7 @@ class renderable extends \table_sql implements \renderable {
         $manage = '';
         // We don't need to check for capability at course level since, user is never shown this page,
         // if he doesn't have the capability.
-        if ($this->hassystemcap || ($rule->courseid !== 0)) {
+        if ($this->hassystemcap || ($rule->courseid != 0)) {
             // There might be site rules which the user can not manage.
             $editurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/edit.php', array('ruleid' => $rule->id,
                     'courseid' => $rule->courseid, 'sesskey' => sesskey()));
@@ -175,7 +175,7 @@ class renderable extends \table_sql implements \renderable {
             $icon = $OUTPUT->action_link($deleteurl, new \pix_icon('t/delete', get_string('deleterule', 'tool_monitor')), $action);
             $manage .= $icon;
         } else {
-            $manage = '-';
+            $manage = get_string('nopermission', 'tool_monitor');
         }
         return $manage;
     }
index 889817d..5eaed9f 100644 (file)
@@ -68,7 +68,7 @@ class renderer extends \plugin_renderer_base {
     }
 
     /**
-     * Html to add a button for adding a new rul.
+     * Html to add a button for adding a new rule.
      *
      * @param int $courseid course id.
      *
@@ -81,4 +81,19 @@ class renderer extends \plugin_renderer_base {
         $addurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/edit.php', array('courseid' => $courseid));
         return \html_writer::link($addurl, $button);
     }
+
+    /**
+     * Html to add a link to go to the subscription page.
+     *
+     * @param moodle_url $manageurl The url of the subscription page.
+     *
+     * @return string html for the link to the subscription page.
+     */
+    public function render_subscriptions_link($manageurl) {
+        echo \html_writer::start_div();
+        $a = \html_writer::link($manageurl, get_string('managesubscriptions', 'tool_monitor'));
+        $link = \html_writer::tag('span', get_string('managesubscriptionslink', 'tool_monitor', $a));
+        echo $link;
+        echo \html_writer::end_div();
+    }
 }
index fe3f8e0..edf8b49 100644 (file)
@@ -91,4 +91,19 @@ class renderer extends \plugin_renderer_base {
 
         return $o;
     }
+
+    /**
+     * Html to add a link to go to the rule manager page.
+     *
+     * @param moodle_url $ruleurl The url of the rule manager page.
+     *
+     * @return string html for the link to the rule manager page.
+     */
+    public function render_rules_link($ruleurl) {
+        echo \html_writer::start_div();
+        $a = \html_writer::link($ruleurl, get_string('managerules', 'tool_monitor'));
+        $link = \html_writer::tag('span', get_string('manageruleslink', 'tool_monitor', $a));
+        echo $link;
+        echo \html_writer::end_div();
+    }
 }
index 864451c..b0c60e0 100644 (file)
@@ -49,6 +49,26 @@ class rule_manager {
         $ruledata->timemodified = $now;
 
         $ruledata->id = $DB->insert_record('tool_monitor_rules', $ruledata);
+
+        // Trigger a rule created event.
+        if ($ruledata->id) {
+            if (!empty($ruledata->courseid)) {
+                $courseid = $ruledata->courseid;
+                $context = \context_course::instance($ruledata->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $ruledata->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_created::create($params);
+            $event->trigger();
+        }
+
         return new rule($ruledata);
     }
 
@@ -85,14 +105,47 @@ class rule_manager {
      * Delete a rule and associated subscriptions, by rule id.
      *
      * @param int $ruleid id of rule to be deleted.
+     * @param \context|null $coursecontext the context of the course - this is passed when we
+     *      can not get the context via \context_course as the course has been deleted.
      *
      * @return bool
      */
-    public static function delete_rule($ruleid) {
+    public static function delete_rule($ruleid, $coursecontext = null) {
         global $DB;
 
-        subscription_manager::remove_all_subscriptions_for_rule($ruleid);
-        return $DB->delete_records('tool_monitor_rules', array('id' => $ruleid));
+        subscription_manager::remove_all_subscriptions_for_rule($ruleid, $coursecontext);
+
+        // Retrieve the rule from the DB before we delete it, so we have a record when we trigger a rule deleted event.
+        $rule = $DB->get_record('tool_monitor_rules', array('id' => $ruleid));
+
+        $success = $DB->delete_records('tool_monitor_rules', array('id' => $ruleid));
+
+        // If successful trigger a rule deleted event.
+        if ($success) {
+            // It is possible that we are deleting rules associated with a deleted course, so we should be
+            // passing the context as the second parameter.
+            if (!is_null($coursecontext)) {
+                $context = $coursecontext;
+                $courseid = $rule->courseid;
+            } else if (!empty($rule->courseid) && ($context = \context_course::instance($rule->courseid,
+                    IGNORE_MISSING))) {
+                $courseid = $rule->courseid;
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $rule->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_deleted::create($params);
+            $event->add_record_snapshot('tool_monitor_rules', $rule);
+            $event->trigger();
+        }
+
+        return $success;
     }
 
     /**
@@ -127,7 +180,34 @@ class rule_manager {
             throw new \coding_exception('Invalid rule ID.');
         }
         $ruledata->timemodified = time();
-        return $DB->update_record('tool_monitor_rules', $ruledata);
+
+        $success = $DB->update_record('tool_monitor_rules', $ruledata);
+
+        // If successful trigger a rule updated event.
+        if ($success) {
+            // If we do not have the course id we need to retrieve it.
+            if (!isset($ruledata->courseid)) {
+                $courseid = $DB->get_field('tool_monitor_rules', 'courseid', array('id' => $ruledata->id), MUST_EXIST);
+            } else {
+                $courseid = $ruledata->courseid;
+            }
+
+            if (!empty($courseid)) {
+                $context = \context_course::instance($courseid);
+            } else {
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $ruledata->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\rule_updated::create($params);
+            $event->trigger();
+        }
+
+        return $success;
     }
 
     /**
index 82796a3..ba2236b 100644 (file)
@@ -59,7 +59,28 @@ class subscription_manager {
         }
 
         $subscription->timecreated = time();
-        return $DB->insert_record('tool_monitor_subscriptions', $subscription);
+        $subscription->id = $DB->insert_record('tool_monitor_subscriptions', $subscription);
+
+        // Trigger a subscription created event.
+        if ($subscription->id) {
+            if (!empty($subscription->courseid)) {
+                $courseid = $subscription->courseid;
+                $context = \context_course::instance($subscription->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $subscription->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\subscription_created::create($params);
+            $event->trigger();
+        }
+
+        return $subscription->id;
     }
 
     /**
@@ -81,7 +102,33 @@ class subscription_manager {
         if ($checkuser && $subscription->userid != $USER->id) {
             throw new \coding_exception('Invalid subscription supplied');
         }
-        return $DB->delete_records('tool_monitor_subscriptions', array('id' => $subscription->id));
+
+        // Store the subscription before we delete it.
+        $subscription = $DB->get_record('tool_monitor_subscriptions', array('id' => $subscription->id));
+
+        $success = $DB->delete_records('tool_monitor_subscriptions', array('id' => $subscription->id));
+
+        // If successful trigger a subscription_deleted event.
+        if ($success) {
+            if (!empty($subscription->courseid)) {
+                $courseid = $subscription->courseid;
+                $context = \context_course::instance($subscription->courseid);
+            } else {
+                $courseid = 0;
+                $context = \context_system::instance();
+            }
+
+            $params = array(
+                'objectid' => $subscription->id,
+                'courseid' => $courseid,
+                'context' => $context
+            );
+            $event = \tool_monitor\event\subscription_deleted::create($params);
+            $event->add_record_snapshot('tool_monitor_subscriptions', $subscription);
+            $event->trigger();
+        }
+
+        return $success;
     }
 
     /**
@@ -112,12 +159,49 @@ class subscription_manager {
      * Delete all subscribers for a given rule.
      *
      * @param int $ruleid rule id.
+     * @param \context|null $coursecontext the context of the course - this is passed when we
+     *      can not get the context via \context_course as the course has been deleted.
      *
      * @return bool
      */
-    public static function remove_all_subscriptions_for_rule($ruleid) {
+    public static function remove_all_subscriptions_for_rule($ruleid, $coursecontext = null) {
         global $DB;
-        return $DB->delete_records('tool_monitor_subscriptions', array('ruleid' => $ruleid));
+
+        // Store all the subscriptions we have to delete.
+        $subscriptions = $DB->get_recordset('tool_monitor_subscriptions', array('ruleid' => $ruleid));
+
+        // Now delete them.
+        $success = $DB->delete_records('tool_monitor_subscriptions', array('ruleid' => $ruleid));
+
+        // If successful and there were subscriptions that were deleted trigger a subscription deleted event.
+        if ($success && $subscriptions) {
+            foreach ($subscriptions as $subscription) {
+                // It is possible that we are deleting rules associated with a deleted course, so we should be
+                // passing the context as the second parameter.
+                if (!is_null($coursecontext)) {
+                    $context = $coursecontext;
+                    $courseid = $subscription->courseid;
+                } else if (!empty($subscription->courseid) && ($coursecontext =
+                        \context_course::instance($subscription->courseid, IGNORE_MISSING))) {
+                    $courseid = $subscription->courseid;
+                    $context = $coursecontext;
+                } else {
+                    $courseid = 0;
+                    $context = \context_system::instance();
+                }
+
+                $params = array(
+                    'objectid' => $subscription->id,
+                    'courseid' => $courseid,
+                    'context' => $context
+                );
+                $event = \tool_monitor\event\subscription_deleted::create($params);
+                $event->add_record_snapshot('tool_monitor_subscriptions', $subscription);
+                $event->trigger();
+            }
+        }
+
+        return $success;
     }
 
     /**
index 45c7309..3a02a62 100644 (file)
@@ -107,5 +107,8 @@ if (empty($totalrules)) {
         echo $link;
     }
     echo html_writer::end_div();
+} else if (has_capability('tool/monitor:managerules', $context)) {
+    $manageurl = new moodle_url("/admin/tool/monitor/managerules.php", array('courseid' => $courseid));
+    echo $renderer->render_rules_link($manageurl);
 }
 echo $OUTPUT->footer();
index 0d39eeb..fcef71b 100644 (file)
@@ -39,12 +39,19 @@ $string['description'] = 'Description:';
 $string['duplicaterule'] = 'Duplicate rule';
 $string['editrule'] = 'Edit rule';
 $string['eventnotfound'] = 'Event not found';
+$string['eventrulecreated'] = 'Rule created';
+$string['eventruledeleted'] = 'Rule deleted';
+$string['eventruleupdated'] = 'Rule updated';
+$string['eventsubcreated'] = 'Subscription created';
+$string['eventsubcriteriamet'] = 'Subscription criteria met';
+$string['eventsubdeleted'] = 'Subscription deleted';
 $string['errorincorrectevent'] = 'Please select an event related to the selected plugin';
 $string['freqdesc'] = '{$a->freq} times in {$a->mins} minutes';
 $string['frequency'] = 'Frequency';
 $string['invalidmodule'] = 'Invalid module';
 $string['norules'] = 'There are no rules you can subscribe to.';
 $string['manageruleslink'] = 'You can manage rules from {$a} page.';
+$string['managesubscriptionslink'] = 'You can subscribe to rules from the {$a} page.';
 $string['moduleinstance'] = 'Module instance';
 $string['manage'] = 'Manage';
 $string['managesubscriptions'] = 'Event monitoring';
@@ -61,6 +68,7 @@ $string['messagetemplate_help'] = 'This is the content of the message that will
 $string['minutes'] = 'in minutes:';
 $string['name'] = 'Name of the rule: ';
 $string['name_help'] = "Choose a name for the rule.";
+$string['nopermission'] = "No permission";
 $string['pluginname'] = 'Event monitor';
 $string['processevents'] = 'Process events';
 $string['ruleareyousure'] = 'Are you sure you want to delete rule "{$a}"?';
index 8259fec..bdafd03 100644 (file)
@@ -90,4 +90,8 @@ if (!empty($action) && $ruleid) {
 $renderable = new \tool_monitor\output\managerules\renderable('toolmonitorrules', $manageurl, $courseid);
 $renderer = $PAGE->get_renderer('tool_monitor', 'managerules');
 echo $renderer->render($renderable);
+if (has_capability('tool/monitor:subscribe', $context)) {
+    $manageurl = new moodle_url("/admin/tool/monitor/index.php", array('courseid' => $courseid));
+    echo $renderer->render_subscriptions_link($manageurl);
+}
 echo $OUTPUT->footer();
index 8d78a0a..4fe3660 100644 (file)
@@ -123,3 +123,16 @@ Feature: tool_monitor_subscriptions
     When  I follow "Messages"
     And   I follow "Do not reply to this email (1)"
     Then  I should see "The course was viewed."
+
+  Scenario: Navigating via quick link to rules
+    Given I log in as "admin"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    Then  I should see "You can manage rules from Event monitoring rules page."
+    And   I follow "Event monitoring rules"
+    And   I should see "Event monitor"
+    And   I should see "You can subscribe to rules from the Event monitoring page."
+    And   I log out
+    And   I log in as "teacher1"
+    And   I follow "Course 1"
+    And   I navigate to "Event monitoring" node in "My profile settings"
+    Then  I should not see "You can manage rules from Event monitoring rules page."
index b8d178a..26d4086 100644 (file)
@@ -46,16 +46,17 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
         $this->resetAfterTest(true);
 
         $user = $this->getDataGenerator()->create_user();
-        $course = $this->getDataGenerator()->create_course();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
         $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
 
         $rule = new stdClass();
         $rule->userid = $user->id;
-        $rule->courseid = $course->id;
+        $rule->courseid = $course1->id;
         $rule->plugin = 'test';
 
         $sub = new stdClass();
-        $sub->courseid = $course->id;
+        $sub->courseid = $course1->id;
         $sub->userid = $user->id;
 
         // Add 10 rules for this course with subscriptions.
@@ -65,9 +66,9 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
             $monitorgenerator->create_subscription($sub);
         }
 
-        // Add 10 random rules for random courses.
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
         for ($i = 0; $i < 10; $i++) {
-            $rule->courseid = rand(10000000, 50000000);
             $createdrule = $monitorgenerator->create_rule($rule);
             $sub->courseid = $rule->courseid;
             $sub->ruleid = $createdrule->id;
@@ -77,24 +78,24 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
         // Verify data before course delete.
         $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
         $this->assertCount(20, $totalrules);
-        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course->id);
+        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
         $this->assertCount(10, $courserules);
         $totalsubs = $DB->get_records('tool_monitor_subscriptions');
         $this->assertCount(20, $totalsubs);
-        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course->id, 0, 0, $user->id);
+        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course1->id, 0, 0, $user->id);
         $this->assertCount(10, $coursesubs);
 
         // Let us delete the course now.
-        delete_course($course->id, false);
+        delete_course($course1->id, false);
 
         // Verify data after course delete.
         $totalrules = \tool_monitor\rule_manager::get_rules_by_plugin('test');
         $this->assertCount(10, $totalrules);
-        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course->id);
+        $courserules = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
         $this->assertCount(0, $courserules); // Making sure all rules are deleted.
         $totalsubs = $DB->get_records('tool_monitor_subscriptions');
         $this->assertCount(10, $totalsubs);
-        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course->id, 0, 0, $user->id);
+        $coursesubs = \tool_monitor\subscription_manager::get_user_subscriptions_for_course($course1->id, 0, 0, $user->id);
         $this->assertCount(0, $coursesubs); // Making sure all subscriptions are deleted.
     }
 
@@ -374,16 +375,17 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
         $this->resetAfterTest(true);
 
         $user = $this->getDataGenerator()->create_user();
-        $course = $this->getDataGenerator()->create_course();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
         $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
 
         $rule = new stdClass();
         $rule->userid = $user->id;
-        $rule->courseid = $course->id;
+        $rule->courseid = $course1->id;
         $rule->plugin = 'test';
 
         $sub = new stdClass();
-        $sub->courseid = $course->id;
+        $sub->courseid = $course1->id;
         $sub->userid = $user->id;
 
         // Add 10 rules for this course with subscriptions.
@@ -393,9 +395,9 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
             $monitorgenerator->create_subscription($sub);
         }
 
-        // Add 10 random rules for random courses.
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
         for ($i = 0; $i < 10; $i++) {
-            $rule->courseid = rand(10000000, 50000000);
             $createdrule = $monitorgenerator->create_rule($rule);
             $sub->courseid = $rule->courseid;
             $sub->ruleid = $createdrule->id;
@@ -428,21 +430,22 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
         $this->resetAfterTest(true);
 
         $user = $this->getDataGenerator()->create_user();
-        $course = $this->getDataGenerator()->create_course();
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
         $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
 
         // Now let us create a rule specific to a module instance.
         $cm = new stdClass();
-        $cm->course = $course->id;
+        $cm->course = $course1->id;
         $book = $this->getDataGenerator()->create_module('book', $cm);
 
         $rule = new stdClass();
         $rule->userid = $user->id;
-        $rule->courseid = $course->id;
+        $rule->courseid = $course1->id;
         $rule->plugin = 'test';
 
         $sub = new stdClass();
-        $sub->courseid = $course->id;
+        $sub->courseid = $course1->id;
         $sub->userid = $user->id;
         $sub->cmid = $book->cmid;
 
@@ -453,9 +456,9 @@ class tool_monitor_eventobservers_testcase extends advanced_testcase {
             $monitorgenerator->create_subscription($sub);
         }
 
-        // Add 10 random rules for random courses.
+        // Add 10 random rules for course 2.
+        $rule->courseid = $course2->id;
         for ($i = 0; $i < 10; $i++) {
-            $rule->courseid = rand(10000000, 50000000);
             $createdrule = $monitorgenerator->create_rule($rule);
             $sub->courseid = $rule->courseid;
             $sub->ruleid = $createdrule->id;
diff --git a/admin/tool/monitor/tests/events_test.php b/admin/tool/monitor/tests/events_test.php
new file mode 100644 (file)
index 0000000..9f55c19
--- /dev/null
@@ -0,0 +1,337 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Events tests.
+ *
+ * @package    tool_monitor
+ * @category   test
+ * @copyright  2014 Mark Nelson <markn@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Tests that the tool_monitor events are valid and triggered correctly.
+ */
+class tool_monitor_events_testcase extends advanced_testcase {
+
+    /**
+     * Tests set up.
+     */
+    public function setUp() {
+        $this->resetAfterTest();
+    }
+
+    /**
+     * Test the rule created event.
+     */
+    public function test_rule_created() {
+        // Create the items we need to create a rule.
+        $course = $this->getDataGenerator()->create_course();
+        $user = $this->getDataGenerator()->create_user();
+
+        // Create the variables for the rule we want to create.
+        $ruledata = new stdClass();
+        $ruledata->userid = $user->id;
+        $ruledata->courseid = $course->id;
+        $ruledata->description = 'Rule description';
+        $ruledata->descriptionformat = FORMAT_HTML;
+        $ruledata->template = 'A message template';
+        $ruledata->templateformat = FORMAT_HTML;
+        $ruledata->frequency = 1;
+        $ruledata->timewindow = 60;
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $rule = \tool_monitor\rule_manager::add_rule($ruledata);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_created', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's add a system rule (courseid = 0).
+        $ruledata->courseid = 0;
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::add_rule($ruledata);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_created', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the rule updated event.
+     */
+    public function test_rule_updated() {
+        // Create the items we need.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the rule we are going to update.
+        $createrule = new stdClass();
+        $createrule->courseid = $course->id;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $updaterule = new stdClass();
+        $updaterule->id = $rule->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_updated', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's update a system rule (courseid = 0).
+        $createrule->courseid = 0;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $updaterule = new stdClass();
+        $updaterule->id = $rule->id;
+        \tool_monitor\rule_manager::update_rule($updaterule);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_updated', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the rule deleted event.
+     */
+    public function test_rule_deleted() {
+        // Create the items we need.
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+        $course = $this->getDataGenerator()->create_course();
+
+        // Create the rule we are going to delete.
+        $createrule = new stdClass();
+        $createrule->courseid = $course->id;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\rule_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($rule->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's delete a system rule (courseid = 0).
+        $createrule = new stdClass();
+        $createrule->courseid = 0;
+        $rule = $monitorgenerator->create_rule($createrule);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\rule_manager::delete_rule($rule->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\rule_deleted', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the subscription created event.
+     */
+    public function test_subscription_created() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule to subscribe to.
+        $rule = $monitorgenerator->create_rule();
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        $subscriptionid = \tool_monitor\subscription_manager::create_subscription($rule->id, $course->id, 0, $user->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_created', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscriptionid, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Create a system subscription - trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::create_subscription($rule->id, 0, 0, $user->id);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_created', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+    }
+
+    /**
+     * Test the subscription deleted event.
+     */
+    public function test_subscription_deleted() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule to subscribe to.
+        $rule = $monitorgenerator->create_rule();
+
+        $sub = new stdClass();
+        $sub->courseid = $course->id;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+
+        // Create the subscription we are going to delete.
+        $subscription = $monitorgenerator->create_subscription($sub);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::delete_subscription($subscription->id, false);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscription->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+
+        // Now let's delete a system subscription.
+        $sub = new stdClass();
+        $sub->courseid = 0;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+
+        // Create the subscription we are going to delete.
+        $subscription = $monitorgenerator->create_subscription($sub);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::delete_subscription($subscription->id, false);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event uses the system context.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_system::instance(), $event->get_context());
+
+        // Now, create a bunch of subscriptions for the rule we created.
+        $sub->courseid = $course->id;
+        for ($i = 1; $i <= 10; $i++) {
+            $sub->userid = $i;
+            $subscription = $monitorgenerator->create_subscription($sub);
+            if ($i == 1) {
+                $subscription1 = $subscription;
+            }
+        }
+
+        // Trigger and capture the events.
+        $sink = $this->redirectEvents();
+        \tool_monitor\subscription_manager::remove_all_subscriptions_for_rule($rule->id);
+        $events = $sink->get_events();
+
+        // Check that there were 10 events in total.
+        $this->assertCount(10, $events);
+
+        // Get the first event and ensure it is valid (we can assume the rest are the same).
+        $event = reset($events);
+        $this->assertInstanceOf('\tool_monitor\event\subscription_deleted', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEquals($subscription1->id, $event->objectid);
+        $this->assertEventContextNotUsed($event);
+    }
+
+    /**
+     * Test the subscription criteria met event.
+     */
+    public function test_subscription_criteria_met() {
+        // Create the items we need to test this.
+        $user = $this->getDataGenerator()->create_user();
+        $course = $this->getDataGenerator()->create_course();
+        $book = $this->getDataGenerator()->create_module('book', array('course' => $course->id));
+        $bookgenerator = $this->getDataGenerator()->get_plugin_generator('mod_book');
+        $chapter = $bookgenerator->create_chapter(array('bookid' => $book->id));
+        $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
+
+        // Create a rule we want to subscribe to.
+        $rule = new stdClass();
+        $rule->userid = $user->id;
+        $rule->courseid = $course->id;
+        $rule->plugin = 'mod_book';
+        $rule->eventname = '\mod_book\event\chapter_viewed';
+        $rule->frequency = 1;
+        $rule->timewindow = 60;
+        $rule = $monitorgenerator->create_rule($rule);
+
+        // Create the subscription.
+        $sub = new stdClass();
+        $sub->courseid = $course->id;
+        $sub->userid = $user->id;
+        $sub->ruleid = $rule->id;
+        $monitorgenerator->create_subscription($sub);
+
+        // Now create the \mod_book\event\chapter_viewed event we are listening for.
+        $context = context_module::instance($book->cmid);
+        $event = \mod_book\event\chapter_viewed::create_from_chapter($book, $context, $chapter);
+
+        // Trigger and capture the event.
+        $sink = $this->redirectEvents();
+        \tool_monitor\eventobservers::process_event($event);
+        $events = $sink->get_events();
+        $this->assertCount(1, $events);
+        $event = reset($events);
+
+        // Confirm that the event contains the expected values.
+        $this->assertInstanceOf('\tool_monitor\event\subscription_criteria_met', $event);
+        $this->assertEquals(context_course::instance($course->id), $event->get_context());
+        $this->assertEventContextNotUsed($event);
+    }
+}
index f9e05dc..1296791 100644 (file)
@@ -108,11 +108,14 @@ class tool_monitor_rule_manager_testcase extends advanced_testcase {
 
         $monitorgenerator = $this->getDataGenerator()->get_plugin_generator('tool_monitor');
 
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+
         $record = new stdClass();
-        $record->courseid = 3;
+        $record->courseid = $course1->id;
 
         $record2 = new stdClass();
-        $record2->courseid = 4;
+        $record2->courseid = $course2->id;
 
         $ruleids = array();
         for ($i = 0; $i < 10; $i++) {
@@ -122,8 +125,8 @@ class tool_monitor_rule_manager_testcase extends advanced_testcase {
             $ruleids[] = $rule->id;
             $rule = $monitorgenerator->create_rule($record2); // Create rules in a different course.
         }
-        $ruledata = \tool_monitor\rule_manager::get_rules_by_courseid(3);
-        $this->assertEquals($ruleids, array_keys($ruledata));
+        $ruledata = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
+        $this->assertEmpty(array_merge(array_diff(array_keys($ruledata), $ruleids), array_diff($ruleids, array_keys($ruledata))));
         $this->assertCount(20, $ruledata);
     }
 
@@ -150,7 +153,7 @@ class tool_monitor_rule_manager_testcase extends advanced_testcase {
         }
 
         $ruledata = \tool_monitor\rule_manager::get_rules_by_plugin('core');
-        $this->assertEquals($ruleids, array_keys($ruledata));
+        $this->assertEmpty(array_merge(array_diff(array_keys($ruledata), $ruleids), array_diff($ruleids, array_keys($ruledata))));
         $this->assertCount(10, $ruledata);
     }
 
index 94c0c4d..6737c1b 100644 (file)
@@ -114,13 +114,11 @@ class tool_monitor_task_clean_events_testcase extends advanced_testcase {
             \mod_scorm\event\course_module_instance_list_viewed::create($eventparams)->trigger();
         }
 
-        // Check that there are a bunch of events now. There will be additional events for creating courses and modules.
-        $this->assertEquals(20, $DB->count_records('tool_monitor_events'));
+        // Check that the events exist - there will be additional events for creating courses, modules and rules.
+        $this->assertEquals(26, $DB->count_records('tool_monitor_events'));
 
-        // Run the task and check that all the mod_quiz/mod_scorm events are removed as well as the course_module_*
-        // viewed events in the second course. The chapter_viewed event in the second course should remain though as
-        // there is a rule associated with that event in that course. The chapter_viewed event in the first course
-        // should also remain as there is a site wide rule.
+        // Run the task and check that all the quiz, scorm and rule events are removed as well as the course_module_*
+        // viewed events in the second course.
         $task = new \tool_monitor\task\clean_events();
         $task->execute();
 
index fd33545..a173e72 100644 (file)
@@ -35,6 +35,13 @@ global $CFG;
  */
 class tool_uploadcourse_course_testcase extends advanced_testcase {
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     public function test_proceed_without_prepare() {
         $this->resetAfterTest(true);
         $mode = tool_uploadcourse_processor::MODE_CREATE_NEW;
index 1dd20dc..2d2ec04 100644 (file)
@@ -36,6 +36,13 @@ require_once($CFG->libdir . '/csvlib.class.php');
  */
 class tool_uploadcourse_processor_testcase extends advanced_testcase {
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     public function test_basic() {
         global $DB;
         $this->resetAfterTest(true);
index bb937aa..960c9ec 100644 (file)
@@ -38,6 +38,13 @@ require_once($CFG->libdir . '/completionlib.php');
  */
 class core_backup_moodle2_testcase extends advanced_testcase {
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     /**
      * Tests the availability field on modules and sections is correctly
      * backed up and restored.
index 676b26d..ba0fc08 100644 (file)
@@ -2928,7 +2928,7 @@ function calendar_add_icalendar_event($event, $courseid, $subscriptionid, $timez
             $timezone;
     $eventrecord->timestart = strtotime($event->properties['DTSTART'][0]->value . ' ' . $tz);
     if (empty($event->properties['DTEND'])) {
-        $eventrecord->timeduration = 3600; // one hour if no end time specified
+        $eventrecord->timeduration = 0; // no duration if no end time specified
     } else {
         $endtz = isset($event->properties['DTEND'][0]->parameters['TZID']) ? $event->properties['DTEND'][0]->parameters['TZID'] :
                 $timezone;
index 1f5b051..33014d6 100644 (file)
@@ -291,6 +291,14 @@ abstract class moodleform_mod extends moodleform {
             }
         }
 
+        // Ratings: Don't let them select an aggregate type without selecting a scale.
+        // If the user has selected to use ratings but has not chosen a scale or set max points then the form is
+        // invalid. If ratings have been selected then the user must select either a scale or max points.
+        // This matches (horrible) logic in data_preprocessing.
+        if (isset($data['assessed']) && $data['assessed'] > 0 && empty($data['scale'])) {
+            $errors['assessed'] = get_string('scaleselectionrequired', 'rating');
+        }
+
         // Completion: Don't let them choose automatic completion without turning
         // on some conditions. Ignore this check when completion settings are
         // locked, as the options are then disabled.
index 62ac25a..77ef9e0 100644 (file)
@@ -33,6 +33,13 @@ require_once($CFG->dirroot . '/tag/lib.php');
 
 class core_course_courselib_testcase extends advanced_testcase {
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     /**
      * Set forum specific test values for calling create_module().
      *
index 13826b8..756c626 100644 (file)
@@ -47,6 +47,13 @@ class core_course_externallib_testcase extends externallib_advanced_testcase {
         require_once($CFG->dirroot . '/course/externallib.php');
     }
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     /**
      * Test create_categories
      */
index 1f6d0d4..6acd0f8 100644 (file)
@@ -94,6 +94,9 @@
 
     require_once($CFG->dirroot.'/calendar/lib.php');    /// This is after login because it needs $USER
 
+    // Must set layout before gettting section info. See MDL-47555.
+    $PAGE->set_pagelayout('course');
+
     if ($section and $section > 0) {
 
         // Get section details and check it exists.
     // Fix course format if it is no longer installed
     $course->format = course_get_format($course)->get_format();
 
-    $PAGE->set_pagelayout('course');
     $PAGE->set_pagetype('course-view-' . $course->format);
     $PAGE->set_other_editing_capability('moodle/course:update');
     $PAGE->set_other_editing_capability('moodle/course:manageactivities');
index baa4087..ae103fb 100644 (file)
@@ -46,38 +46,34 @@ class edit_letter_form extends moodleform {
         $gradeletter       = get_string('gradeletter', 'grades');
         $gradeboundary     = get_string('gradeboundary', 'grades');
 
-        $percentages = array(-1 => get_string('unused', 'grades'));
-        for ($i=100; $i > -1; $i--) {
-            $percentages[$i] = "$i %";
-        }
-
-        for($i=1; $i<$num+1; $i++) {
+        for ($i=1; $i<$num+1; $i++) {
             $gradelettername = 'gradeletter'.$i;
             $gradeboundaryname = 'gradeboundary'.$i;
 
-            $mform->addElement('text', $gradelettername, $gradeletter." $i");
-            if ($i == 1) {
-                $mform->addHelpButton($gradelettername, 'gradeletter', 'grades');
-            }
+            $entry = array();
+            $entry[] = $mform->createElement('text', $gradelettername, $gradeletter . " $i");
             $mform->setType($gradelettername, PARAM_TEXT);
 
             if (!$admin) {
                 $mform->disabledIf($gradelettername, 'override', 'notchecked');
-                $mform->disabledIf($gradelettername, $gradeboundaryname, 'eq', -1);
             }
 
-            $mform->addElement('select', $gradeboundaryname, $gradeboundary." $i", $percentages);
-            if ($i == 1) {
-                $mform->addHelpButton($gradeboundaryname, 'gradeboundary', 'grades');
-            }
-            $mform->setDefault($gradeboundaryname, -1);
-            $mform->setType($gradeboundaryname, PARAM_INT);
+            $entry[] = $mform->createElement('static', '', '', '&ge;');
+            $entry[] = $mform->createElement('text', $gradeboundaryname, $gradeboundary." $i");
+            $entry[] = $mform->createElement('static', '', '', '%');
+            $mform->addGroup($entry, 'gradeentry'.$i, $gradeletter." $i", array(' '), false);
+
+            $mform->setType($gradeboundaryname, PARAM_FLOAT);
 
             if (!$admin) {
                 $mform->disabledIf($gradeboundaryname, 'override', 'notchecked');
             }
         }
 
+        if ($num > 0) {
+            $mform->addHelpButton('gradeentry1', 'gradeletter', 'grades');
+        }
+
         // hidden params
         $mform->addElement('hidden', 'id');
         $mform->setType('id', PARAM_INT);
index d0dc35a..1974367 100644 (file)
@@ -146,7 +146,8 @@ if (!$edit) {
                 if ($letter == '') {
                     continue;
                 }
-                $letters[$data->$gradeboundaryname] = $letter;
+                // The keys need to be strings so floats are not truncated.
+                $letters[strval($data->$gradeboundaryname)] = $letter;
             }
         }
         krsort($letters, SORT_NUMERIC);
index 4a86cb3..da33c55 100644 (file)
@@ -109,7 +109,7 @@ class grade_edit_tree {
      * @return string HTML
      */
     public function build_html_tree($element, $totals, $parents, $level, &$row_count) {
-        global $CFG, $COURSE, $USER, $OUTPUT;
+        global $CFG, $COURSE, $PAGE, $OUTPUT;
 
         $object = $element['object'];
         $eid    = $element['eid'];
@@ -126,30 +126,41 @@ class grade_edit_tree {
             $rowclasses[] = $parent_eid;
         }
 
-        $actions = '';
         $moveaction = '';
+        $actionsmenu = new action_menu();
+        $actionsmenu->initialise_js($PAGE);
+        $actionsmenu->set_menu_trigger(get_string('edit'));
+        $actionsmenu->set_owner_selector('grade-item-' . $eid);
+        $actionsmenu->set_alignment(action_menu::TL, action_menu::BL);
 
-        if (!$is_category_item) {
-            $actions .= $this->gtree->get_edit_icon($element, $this->gpr);
+        if (!$is_category_item && ($icon = $this->gtree->get_edit_icon($element, $this->gpr, true))) {
+            $actionsmenu->add($icon);
         }
 
-        if ($this->show_calculations) {
-            $actions .= $this->gtree->get_calculation_icon($element, $this->gpr);
+        if ($this->show_calculations && ($icon = $this->gtree->get_calculation_icon($element, $this->gpr, true))) {
+            $actionsmenu->add($icon);
         }
 
         if ($element['type'] == 'item' or ($element['type'] == 'category' and $element['depth'] > 1)) {
             if ($this->element_deletable($element)) {
                 $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'delete', 'eid' => $eid, 'sesskey' => sesskey()));
-                $actions .= $OUTPUT->action_icon($aurl, new pix_icon('t/delete', get_string('delete')));
+                $icon = new action_menu_link_secondary($aurl, new pix_icon('t/delete', get_string('delete')), get_string('delete'));
+                $actionsmenu->add($icon);
             }
 
             $aurl = new moodle_url('index.php', array('id' => $COURSE->id, 'action' => 'moveselect', 'eid' => $eid, 'sesskey' => sesskey()));
             $moveaction .= $OUTPUT->action_icon($aurl, new pix_icon('t/move', get_string('move')));
         }
 
-        $actions .= $this->gtree->get_hiding_icon($element, $this->gpr);
+        if ($icon = $this->gtree->get_hiding_icon($element, $this->gpr, true)) {
+            $actionsmenu->add($icon);
+        }
+
+        if ($icon = $this->gtree->get_reset_icon($element, $this->gpr, true)) {
+            $actionsmenu->add($icon);
+        }
 
-        $actions .= $this->gtree->get_reset_icon($element, $this->gpr);
+        $actions = $OUTPUT->render($actionsmenu);
 
         $returnrows = array();
         $root = false;
@@ -279,6 +290,7 @@ class grade_edit_tree {
             }
 
             $row = new html_table_row();
+            $row->id = 'grade-item-' . $eid;
             $row->attributes['class'] = $courseclass . ' category ' . $dimmed;
             foreach ($rowclasses as $class) {
                 $row->attributes['class'] .= ' ' . $class;
@@ -327,6 +339,7 @@ class grade_edit_tree {
 
             $dimmed = ($item->is_hidden()) ? "dimmed_text" : "";
             $gradeitemrow = new html_table_row();
+            $gradeitemrow->id = 'grade-item-' . $eid;
             $gradeitemrow->attributes['class'] = $categoryitemclass . ' item ' . $dimmed;
             foreach ($rowclasses as $class) {
                 $gradeitemrow->attributes['class'] .= ' ' . $class;
@@ -698,9 +711,11 @@ class grade_edit_tree_column_weight extends grade_edit_tree_column {
         }
         $itemcell = parent::get_item_cell($item, $params);
         $itemcell->text = '&nbsp;';
+        $object = $params['element']['object'];
 
-        if (!in_array($params['element']['object']->itemtype, array('courseitem', 'categoryitem', 'category'))
-                && !in_array($params['element']['object']->gradetype, array(GRADE_TYPE_NONE, GRADE_TYPE_TEXT))) {
+        if (!in_array($object->itemtype, array('courseitem', 'categoryitem', 'category'))
+                && !in_array($object->gradetype, array(GRADE_TYPE_NONE, GRADE_TYPE_TEXT))
+                && (!$object->is_outcome_item() || $object->load_parent_category()->aggregateoutcomes)) {
             $itemcell->text = grade_edit_tree::get_weight_input($item);
         }
 
index aeece1f..285acbe 100644 (file)
@@ -202,7 +202,7 @@ class edit_outcomeitem_form extends moodleform {
 
                 $parent_category->apply_forced_settings();
 
-                if (!$parent_category->is_aggregationcoef_used()) {
+                if (!$parent_category->is_aggregationcoef_used() || !$parent_category->aggregateoutcomes) {
                     if ($mform->elementExists('aggregationcoef')) {
                         $mform->removeElement('aggregationcoef');
                     }
@@ -228,8 +228,11 @@ class edit_outcomeitem_form extends moodleform {
                         $mform->addHelpButton('aggregationcoef', $aggcoef, 'grades');
                     }
                 }
-                // Remove fields used by natural weighting if the parent category is not using natural weighting.
-                if ($parent_category->aggregation != GRADE_AGGREGATE_SUM) {
+
+                // Remove the natural weighting fields for other aggregations,
+                // or when the category does not aggregate outcomes.
+                if ($parent_category->aggregation != GRADE_AGGREGATE_SUM ||
+                        !$parent_category->aggregateoutcomes) {
                     if ($mform->elementExists('weightoverride')) {
                         $mform->removeElement('weightoverride');
                     }
index 1b8c824..40aa1c5 100644 (file)
@@ -897,7 +897,7 @@ function print_grade_page_head($courseid, $active_type, $active_plugin=null,
 
     $returnval .= print_natural_aggregation_upgrade_notice($courseid,
                                                            context_course::instance($courseid),
-                                                           '/grade/report/' . $active_plugin . '/index.php',
+                                                           $PAGE->url,
                                                            $return);
 
     if ($return) {
@@ -1564,16 +1564,17 @@ class grade_structure {
      *
      * @param array  $element An array representing an element in the grade_tree
      * @param object $gpr A grade_plugin_return object
-     * @return string
+     * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
+     * @return string|action_menu_link
      */
-    public function get_reset_icon($element, $gpr) {
+    public function get_reset_icon($element, $gpr, $returnactionmenulink = false) {
         global $CFG, $OUTPUT;
 
         // Limit to category items set to use the natural weights aggregation method, and users
         // with the capability to manage grades.
         if ($element['type'] != 'category' || $element['object']->aggregation != GRADE_AGGREGATE_SUM ||
                 !has_capability('moodle/grade:manage', $this->context)) {
-            return '';
+            return $returnactionmenulink ? null : '';
         }
 
         $str = get_string('resetweights', 'grades', $this->get_params_for_iconstr($element));
@@ -1584,7 +1585,12 @@ class grade_structure {
             'sesskey' => sesskey(),
         ));
 
-        return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str));
+        if ($returnactionmenulink) {
+            return new action_menu_link_secondary($gpr->add_url_params($url), new pix_icon('t/reset', $str),
+                get_string('resetweightsshort', 'grades'));
+        } else {
+            return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/reset', $str));
+        }
     }
 
     /**
@@ -1592,17 +1598,17 @@ class grade_structure {
      *
      * @param array  $element An array representing an element in the grade_tree
      * @param object $gpr A grade_plugin_return object
-     *
-     * @return string
+     * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
+     * @return string|action_menu_link
      */
-    public function get_edit_icon($element, $gpr) {
+    public function get_edit_icon($element, $gpr, $returnactionmenulink = false) {
         global $CFG, $OUTPUT;
 
         if (!has_capability('moodle/grade:manage', $this->context)) {
             if ($element['type'] == 'grade' and has_capability('moodle/grade:edit', $this->context)) {
                 // oki - let them override grade
             } else {
-                return '';
+                return $returnactionmenulink ? null : '';
             }
         }
 
@@ -1656,10 +1662,16 @@ class grade_structure {
         }
 
         if ($url) {
-            return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
+            if ($returnactionmenulink) {
+                return new action_menu_link_secondary($gpr->add_url_params($url),
+                    new pix_icon('t/edit', $stredit),
+                    get_string('editsettings'));
+            } else {
+                return $OUTPUT->action_icon($gpr->add_url_params($url), new pix_icon('t/edit', $stredit));
+            }
 
         } else {
-            return '';
+            return $returnactionmenulink ? null : '';
         }
     }
 
@@ -1668,19 +1680,19 @@ class grade_structure {
      *
      * @param array  $element An array representing an element in the grade_tree
      * @param object $gpr A grade_plugin_return object
-     *
-     * @return string
+     * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
+     * @return string|action_menu_link
      */
-    public function get_hiding_icon($element, $gpr) {
+    public function get_hiding_icon($element, $gpr, $returnactionmenulink = false) {
         global $CFG, $OUTPUT;
 
         if (!$element['object']->can_control_visibility()) {
-            return '';
+            return $returnactionmenulink ? null : '';
         }
 
         if (!has_capability('moodle/grade:manage', $this->context) and
             !has_capability('moodle/grade:hide', $this->context)) {
-            return '';
+            return $returnactionmenulink ? null : '';
         }
 
         $strparams = $this->get_params_for_iconstr($element);
@@ -1703,11 +1715,19 @@ class grade_structure {
 
             $url->param('action', 'show');
 
-            $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon')));
+            if ($returnactionmenulink) {
+                $hideicon = new action_menu_link_secondary($url, new pix_icon('t/'.$type, $tooltip), get_string('show'));
+            } else {
+                $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/'.$type, $tooltip, 'moodle', array('alt'=>$strshow, 'class'=>'smallicon')));
+            }
 
         } else {
             $url->param('action', 'hide');
-            $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
+            if ($returnactionmenulink) {
+                $hideicon = new action_menu_link_secondary($url, new pix_icon('t/hide', $strhide), get_string('hide'));
+            } else {
+                $hideicon = $OUTPUT->action_icon($url, new pix_icon('t/hide', $strhide));
+            }
         }
 
         return $hideicon;
@@ -1775,13 +1795,13 @@ class grade_structure {
      *
      * @param array  $element An array representing an element in the grade_tree
      * @param object $gpr A grade_plugin_return object
-     *
-     * @return string
+     * @param bool $returnactionmenulink return the instance of action_menu_link instead of string
+     * @return string|action_menu_link
      */
-    public function get_calculation_icon($element, $gpr) {
+    public function get_calculation_icon($element, $gpr, $returnactionmenulink = false) {
         global $CFG, $OUTPUT;
         if (!has_capability('moodle/grade:manage', $this->context)) {
-            return '';
+            return $returnactionmenulink ? null : '';
         }
 
         $type   = $element['type'];
@@ -1804,11 +1824,17 @@ class grade_structure {
 
                 $url = new moodle_url('/grade/edit/tree/calculation.php', array('courseid' => $this->courseid, 'id' => $object->id));
                 $url = $gpr->add_url_params($url);
-                return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation));
+                if ($returnactionmenulink) {
+                    return new action_menu_link_secondary($url,
+                        new pix_icon($icon, $streditcalculation),
+                        get_string('editcalculation', 'grades'));
+                } else {
+                    return $OUTPUT->action_icon($url, new pix_icon($icon, $streditcalculation));
+                }
             }
         }
 
-        return '';
+        return $returnactionmenulink ? null : '';
     }
 }
 
index 374c742..dca2b93 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-debug.js differ
index 56aa1a3..d324e6f 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable-min.js differ
index fe61924..212ab0b 100644 (file)
Binary files a/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js and b/grade/report/grader/yui/build/moodle-gradereport_grader-gradereporttable/moodle-gradereport_grader-gradereporttable.js differ
index 21000d8..faf7b3b 100644 (file)
@@ -203,6 +203,15 @@ FloatingHeaders.prototype = {
      */
     firstUserCellWidth: 0,
 
+    /**
+     * The width of the dock if it is visible.
+     *
+     * @property dockWidth
+     * @type Number
+     * @protected
+     */
+    dockWidth: 0,
+
     /**
      * The position of the top of the final user cell.
      * This is used when processing the scroll event as an optimisation. It must be updated when
@@ -303,6 +312,13 @@ FloatingHeaders.prototype = {
             this.footerRowPosition = this.tableFooterRow.getY();
         }
 
+        // Add the width of the dock if it is visible.
+        this.dockWidth = 0;
+        var dock = Y.one('.has_dock #dock');
+        if (dock) {
+            this.dockWidth = dock.get(OFFSETWIDTH);
+        }
+
         var userCellList = Y.all(SELECTORS.USERCELL);
 
         // The left of the user cells matches the left of the headerRow.
@@ -436,7 +452,9 @@ FloatingHeaders.prototype = {
             // Listen for window scrolls, resizes, and rotation events.
             Y.one(Y.config.win).on('scroll', this._handleScrollEvent, this),
             Y.one(Y.config.win).on('resize', this._handleResizeEvent, this),
-            Y.one(Y.config.win).on('orientationchange', this._handleResizeEvent, this)
+            Y.one(Y.config.win).on('orientationchange', this._handleResizeEvent, this),
+            Y.Global.on('dock:shown', this._handleResizeEvent, this),
+            Y.Global.on('dock:hidden', this._handleResizeEvent, this)
         );
     },
 
@@ -451,7 +469,7 @@ FloatingHeaders.prototype = {
         var userColumn = Y.all(SELECTORS.USERCELL),
 
         // Create a floating table.
-            floatingUserColumn = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater"></div>'),
+            floatingUserColumn = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly"></div>'),
 
         // Get the XY for the floating element.
             coordinates = this._getRelativeXY(this.firstUserCell);
@@ -498,7 +516,7 @@ FloatingHeaders.prototype = {
         this.headerCell = Y.one(SELECTORS.STUDENTHEADER);
 
         // Create the floating row and cell.
-        var floatingUserHeaderRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater heading"></div>'),
+        var floatingUserHeaderRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly heading"></div>'),
             floatingUserHeaderCell = Y.Node.create('<div></div>'),
             nodepos = this._getRelativeXY(this.headerCell)[0],
             coordinates = this._getRelativeXY(this.headerRow),
@@ -678,7 +696,7 @@ FloatingHeaders.prototype = {
         }
 
         // Create the floating row and cell.
-        var floatingRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater"></div>'),
+        var floatingRow = Y.Node.create('<div aria-hidden="true" role="presentation" class="floater sideonly"></div>'),
             floatingCell = Y.Node.create('<div></div>'),
             coordinates = this._getRelativeXY(origin),
             width = this.firstUserCell.getComputedStyle(WIDTH),
@@ -761,14 +779,14 @@ FloatingHeaders.prototype = {
 
         // User column position.
         if (right_to_left()) {
-            floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset;
+            floatingUserTriggerPoint = Y.config.win.innerWidth + Y.config.win.pageXOffset - this.dockWidth;
             floatingUserRelativePoint = floatingUserTriggerPoint - this.firstUserCellWidth;
             userFloats = floatingUserTriggerPoint < (this.firstUserCellLeft + this.firstUserCellWidth);
             leftTitleFloats = (floatingUserTriggerPoint - this.firstNonUserCellWidth) <
                               (this.firstNonUserCellLeft + this.firstUserCellWidth);
         } else {
-            floatingUserTriggerPoint = Y.config.win.pageXOffset;
-            floatingUserRelativePoint = floatingUserTriggerPoint;
+            floatingUserRelativePoint = Y.config.win.pageXOffset;
+            floatingUserTriggerPoint = floatingUserRelativePoint + this.dockWidth;
             userFloats = floatingUserTriggerPoint > this.firstUserCellLeft;
             leftTitleFloats = floatingUserTriggerPoint > (this.firstNonUserCellLeft - this.firstUserCellWidth);
         }
index a305c9a..a53eb2a 100644 (file)
Binary files a/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-debug.js and b/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-debug.js differ
index 6efa157..8d69f0d 100644 (file)
Binary files a/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js and b/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector-min.js differ
index 3209946..390997d 100644 (file)
Binary files a/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector.js and b/grade/report/history/yui/build/moodle-gradereport_history-userselector/moodle-gradereport_history-userselector.js differ
index 38f662e..0622cb9 100644 (file)
@@ -641,6 +641,10 @@ Y.namespace('M.gradereport_history').UserSelector = Y.extend(USERSELECTOR, M.cor
         if (this._userTabFocus) {
             this._userTabFocus.setAttribute('tabindex', '-1');
         }
+        if (!user) {
+            // We were not passed a user, there is apparently none in the dialogue. Nothing to do here \\\o/.
+            return;
+        }
 
         this._userTabFocus = user.one(SELECTORS.USERSELECT);
         this._userTabFocus.setAttribute('tabindex', '0');
index af0208b..b0aa35b 100644 (file)
@@ -510,6 +510,13 @@ abstract class grade_report {
                 if (array_key_exists($course_item->id, $hiding_affected['alteredaggregationweight'])) {
                     $aggregationweight = $hiding_affected['alteredaggregationweight'][$course_item->id];
                 }
+
+                if (!$this->showtotalsifcontainhidden[$courseid]) {
+                    // If the course total is hidden we must hide the weight otherwise
+                    // it can be used to compute the course total.
+                    $aggregationstatus = 'unknown';
+                    $aggregationweight = null;
+                }
             }
         } else if (!empty($hiding_affected['unknown'][$course_item->id])) {
             //not sure whether or not this item depends on a hidden item
index cdd56d9..c588d6e 100644 (file)
@@ -33,6 +33,7 @@ use gradereport_singleview\local\ui\range;
 use gradereport_singleview\local\ui\bulk_insert;
 use grade_item;
 use grade_grade;
+use stdClass;
 
 defined('MOODLE_INTERNAL') || die;
 
@@ -122,8 +123,6 @@ class user extends tablelike implements selectable_items {
 
         $this->requirespaging = count($this->items) > $this->perpage;
 
-        unset($seq);
-
         $this->setup_structure();
 
         $this->definition = array(
@@ -179,16 +178,26 @@ class user extends tablelike implements selectable_items {
         if (isset($item->cmid)) {
             $realmodid = $item->cmid;
         }
-        $url = new moodle_url('/mod/' . $item->itemmodule . '/view.php', array('id' => $realmodid));
+
         $iconstring = get_string('filtergrades', 'gradereport_singleview', $item->get_name());
+
+        // Create a fake gradetreeitem so we can call get_element_header().
+        // The type logic below is from grade_category->_get_children_recursion().
+        $gradetreeitem = array();
+        if (in_array($item->itemtype, array('course', 'category'))) {
+            $gradetreeitem['type'] = $item->itemtype.'item';
+        } else {
+            $gradetreeitem['type'] = 'item';
+        }
+        $gradetreeitem['object'] = $item;
+        $gradetreeitem['userid'] = $this->item->id;
+
+        $itemlabel = $this->structure->get_element_header($gradetreeitem, true, false);
         $grade->label = $item->get_name();
 
         $line = array(
             $OUTPUT->action_icon($this->format_link('grade', $item->id), new pix_icon('t/editstring', $iconstring)),
-            $this->format_icon($item) . $lockicon,
-            html_writer::link($url, $item->get_name()),
-            $this->category($item),
-            (new range($item))
+            $this->format_icon($item) . $lockicon, $itemlabel, $this->category($item), (new range($item))
         );
         return $this->format_definition($line, $grade);
     }
index a4d3545..2b5bd32 100644 (file)
@@ -33,6 +33,7 @@ $string['exclude'] = 'Exclude';
 $string['excludeall'] = 'Exclude all grades';
 $string['excludefor'] = 'Exclude for {$a}';
 $string['excludenone'] = 'Exclude no grades';
+$string['eventgradereportviewed'] = 'Grade single view report viewed.';
 $string['feedbackfor'] = 'Feedback for {$a}';
 $string['filtergrades'] = 'Show grades for {$a}.';
 $string['gradefor'] = 'Grade for {$a}';
index f8f8090..8683961 100644 (file)
@@ -15,6 +15,9 @@ Feature: We can use Single view
       | student2 | Student | 2 | student1@asd.com | s2 | holly |
       | student3 | Student | 3 | student1@asd.com | s3 | anna |
       | student4 | Student | 4 | student1@asd.com | s4 | zac |
+    And the following "grade items" exist:
+      | itemname | course  |
+      | new grade item 1 | C1 |
     And the following "course enrolments" exist:
       | user | course | role |
       | teacher1 | C1 | editingteacher |
@@ -36,7 +39,7 @@ Feature: We can use Single view
     And I follow "Course 1"
     And I follow "Grades"
 
- @javascript
 @javascript
   Scenario: I can update grades, add feedback and exclude grades.
     Given I click on "Single view" "option"
     And I click on "Student 4" "option"
@@ -71,10 +74,8 @@ Feature: We can use Single view
     And I follow "Single view for Student 1"
     Then I should see "Student 1"
 
-  @javascript
   Scenario: Navigation works in the Single view.
-    Given I click on "Single view" "option"
-    Then I click on "Student 1" "option"
+    Given I follow "Single view for Student 1"
     Then I should see "Student 1"
     And I follow "Student 2"
     Then I should see "Student 2"
@@ -86,3 +87,10 @@ Feature: We can use Single view
     Then I should see "Test assignment three"
     And I follow "Test assignment four"
     Then I should see "Test assignment four"
+
+  Scenario: Activities are clickable only when
+    it has a valid activity page.
+    Given I follow "Single view for Student 1"
+    And "new grade item 1" "link" should not exist in the "//tbody//tr[position()=1]//td[position()=2]" "xpath_element"
+    Then "Category total" "link" should not exist in the "//tbody//tr[position()=2]//td[position()=2]" "xpath_element"
+    And "Course total" "link" should not exist in the "//tbody//tr[position()=last()]//td[position()=2]" "xpath_element"
index b4cc0ab..e02c99f 100644 (file)
@@ -446,6 +446,32 @@ class grade_report_user extends grade_report {
                 }
             }
 
+            // Actual Grade - We need to calculate this whether the row is hidden or not.
+            $gradeval = $grade_grade->finalgrade;
+            $hint = $grade_grade->get_aggregation_hint();
+            if (!$this->canviewhidden) {
+                /// Virtual Grade (may be calculated excluding hidden items etc).
+                $adjustedgrade = $this->blank_hidden_total_and_adjust_bounds($this->courseid,
+                                                                             $grade_grade->grade_item,
+                                                                             $gradeval);
+
+                $gradeval = $adjustedgrade['grade'];
+
+                // We temporarily adjust the view of this grade item - because the min and
+                // max are affected by the hidden values in the aggregation.
+                $grade_grade->grade_item->grademax = $adjustedgrade['grademax'];
+                $grade_grade->grade_item->grademin = $adjustedgrade['grademin'];
+                $hint['status'] = $adjustedgrade['aggregationstatus'];
+                $hint['weight'] = $adjustedgrade['aggregationweight'];
+            } else {
+                // The max and min for an aggregation may be different to the grade_item.
+                if (!is_null($gradeval)) {
+                    $grade_grade->grade_item->grademax = $grade_grade->rawgrademax;
+                    $grade_grade->grade_item->grademin = $grade_grade->rawgrademin;
+                }
+            }
+
+
             if (!$hide) {
                 /// Excluded Item
                 /**
@@ -473,31 +499,6 @@ class grade_report_user extends grade_report {
                 $data['itemname']['celltype'] = 'th';
                 $data['itemname']['id'] = $header_row;
 
-                /// Actual Grade
-                $gradeval = $grade_grade->finalgrade;
-                $hint = $grade_grade->get_aggregation_hint();
-                if (!$this->canviewhidden) {
-                    /// Virtual Grade (may be calculated excluding hidden items etc).
-                    $adjustedgrade = $this->blank_hidden_total_and_adjust_bounds($this->courseid,
-                                                                                 $grade_grade->grade_item,
-                                                                                 $gradeval);
-
-                    $gradeval = $adjustedgrade['grade'];
-
-                    // We temporarily adjust the view of this grade item - because the min and
-                    // max are affected by the hidden values in the aggregation.
-                    $grade_grade->grade_item->grademax = $adjustedgrade['grademax'];
-                    $grade_grade->grade_item->grademin = $adjustedgrade['grademin'];
-                    $hint['status'] = $adjustedgrade['aggregationstatus'];
-                    $hint['weight'] = $adjustedgrade['aggregationweight'];
-                } else {
-                    // The max and min for an aggregation may be different to the grade_item.
-                    if (!is_null($gradeval)) {
-                        $grade_grade->grade_item->grademax = $grade_grade->rawgrademax;
-                        $grade_grade->grade_item->grademin = $grade_grade->rawgrademin;
-                    }
-                }
-
                 if ($this->showfeedback) {
                     // Copy $class before appending itemcenter as feedback should not be centered
                     $classfeedback = $class;
@@ -649,17 +650,20 @@ class grade_report_user extends grade_report {
                     $data['contributiontocoursetotal']['content'] = '-';
                     $data['contributiontocoursetotal']['headers'] = "$header_cat $header_row contributiontocoursetotal";
 
-                    $hint['grademax'] = $grade_grade->grade_item->grademax;
-                    $hint['grademin'] = $grade_grade->grade_item->grademin;
-                    $hint['grade'] = $gradeval;
-                    $parent = $grade_object->load_parent_category();
-                    if ($grade_object->is_category_item()) {
-                        $parent = $parent->load_parent_category();
-                    }
-                    $hint['parent'] = $parent->load_grade_item()->id;
-                    $this->aggregationhints[$grade_grade->itemid] = $hint;
                 }
             }
+            // We collect the aggregation hints whether they are hidden or not.
+            if ($this->showcontributiontocoursetotal) {
+                $hint['grademax'] = $grade_grade->grade_item->grademax;
+                $hint['grademin'] = $grade_grade->grade_item->grademin;
+                $hint['grade'] = $gradeval;
+                $parent = $grade_object->load_parent_category();
+                if ($grade_object->is_category_item()) {
+                    $parent = $parent->load_parent_category();
+                }
+                $hint['parent'] = $parent->load_grade_item()->id;
+                $this->aggregationhints[$grade_grade->itemid] = $hint;
+            }
         }
 
         /// Category
@@ -735,9 +739,14 @@ class grade_report_user extends grade_report {
 
                 // Multiply the normalised value by the weight
                 // of all the categories higher in the tree.
+                $parent = null;
                 do {
                     if (!is_null($this->aggregationhints[$itemid]['weight'])) {
                         $gradeval *= $this->aggregationhints[$itemid]['weight'];
+                    } else if (empty($parent)) {
+                        // If we are in the first loop, and the weight is null, then we cannot calculate the contribution.
+                        $gradeval = null;
+                        break;
                     }
 
                     // The second part of this if is to prevent infinite loops
@@ -751,8 +760,12 @@ class grade_report_user extends grade_report {
                         $parent = false;
                     }
                 } while ($parent);
+
                 // Finally multiply by the course grademax.
-                $gradeval *= $this->aggregationhints[$itemid]['grademax'];
+                if (!is_null($gradeval)) {
+                    // Convert to percent.
+                    $gradeval *= 100;
+                }
 
                 // Now we need to loop through the "built" table data and update the
                 // contributions column for the current row.
@@ -760,8 +773,12 @@ class grade_report_user extends grade_report {
                 foreach ($this->tabledata as $key => $row) {
                     if (isset($row['itemname']) && ($row['itemname']['id'] == $header_row)) {
                         // Found it - update the column.
-                        $decimals = $grade_object->get_decimals();
-                        $this->tabledata[$key]['contributiontocoursetotal']['content'] = format_float($gradeval, $decimals, true);
+                        $content = '-';
+                        if (!is_null($gradeval)) {
+                            $decimals = $grade_object->get_decimals();
+                            $content = format_float($gradeval, $decimals, true) . ' %';
+                        }
+                        $this->tabledata[$key]['contributiontocoursetotal']['content'] = $content;
                         break;
                     }
                 }
index 6272c83..46a7c3f 100644 (file)
@@ -59,14 +59,49 @@ class behat_grade extends behat_base {
      * @return Given[]
      */
     public function i_set_the_following_settings_for_grade_item($gradeitem, TableNode $data) {
+
+        $steps = array();
+        $gradeitem = $this->getSession()->getSelectorsHandler()->xpathLiteral($gradeitem);
+
+        if ($this->running_javascript()) {
+            $xpath = "//tr[contains(.,$gradeitem)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
+            if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
+                $steps[] = new Given('I click on "' . $this->escape($xpath) . '" "xpath_element"');
+            }
+        }
+
         $savechanges = get_string('savechanges', 'grades');
         $edit = $this->getSession()->getSelectorsHandler()->xpathLiteral(get_string('edit') . '  ');
-        $gradeitem = $this->getSession()->getSelectorsHandler()->xpathLiteral($gradeitem);
         $linkxpath = "//a[./img[starts-with(@title,$edit) and contains(@title,$gradeitem)]]";
-        return array(
-            new Given('I click on "' . $this->escape($linkxpath) . '" "xpath_element"'),
-            new Given('I set the following fields to these values:', $data),
-            new Given('I press "' . $this->escape($savechanges) . '"'),
-        );
+        $steps[] = new Given('I click on "' . $this->escape($linkxpath) . '" "xpath_element"');
+        $steps[] = new Given('I set the following fields to these values:', $data);
+        $steps[] = new Given('I press "' . $this->escape($savechanges) . '"');
+        return $steps;
+    }
+
+    /**
+     * Resets the weights for the grade category
+     *
+     * Teacher must be on the grade setup page.
+     *
+     * @Given /^I reset weights for grade category "(?P<grade_item_string>(?:[^"]|\\")*)"$/
+     * @param $gradeitem
+     * @return array
+     */
+    public function i_reset_weights_for_grade_category($gradeitem) {
+
+        $steps = array();
+
+        if ($this->running_javascript()) {
+            $gradeitemliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($gradeitem);
+            $xpath = "//tr[contains(.,$gradeitemliteral)]//*[contains(@class,'moodle-actionmenu')]//a[contains(@class,'toggle-display')]";
+            if ($this->getSession()->getPage()->findAll('xpath', $xpath)) {
+                $steps[] = new Given('I click on "' . $this->escape($xpath) . '" "xpath_element"');
+            }
+        }
+
+        $linktext = get_string('resetweights', 'grades', (object)array('itemname' => $gradeitem));
+        $steps[] = new Given('I click on "' . $this->escape($linktext) . '" "link"');
+        return $steps;
     }
 }
index 40e7063..e545f61 100644 (file)
@@ -311,8 +311,6 @@ Feature: We can use calculated grade totals
     And I set the following settings for grade item "Course 1":
       | Aggregation                     | Natural |
       | Include outcomes in aggregation | 0       |
-    And I set the following settings for grade item "Test outcome item one":
-     | Extra credit     | 0   |
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
@@ -394,16 +392,16 @@ Feature: We can use calculated grade totals
     And I set the field "Select all or one user" to "Student 1"
     And the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Range | Contribution to course total |
-      | Test assignment five | 28.57 % | 10.00 (50.00 %) | 0–20 | 6.43 |
-      | Test assignment six | 50.00 % | 5.00 (50.00 %) | 0–10 | 11.25 |
-      | Test assignment seven | 21.43 % | - | 0–15 | 0.00 |
-      | Test assignment eight | 66.67 % | 10.00 (50.00 %) | 0–20 | 10.00 |
-      | Test assignment nine | 33.33 % | 5.00 (50.00 %) | 0–10 | 5.00 |
-      | Test assignment ten | -( Empty ) | - | 0–15 | 0.00 |
-      | Test assignment one | 48.00 % | 60.00 (20.00 %) | 0–300 | 60.00 |
-      | Test assignment two | 16.00 % | 20.00 (20.00 %) | 0–100 | 20.00 |
-      | Test assignment three | 24.00 %( Extra credit ) | 40.00 (26.67 %) | 0–150 | 40.00 |
-      | Test assignment four | 24.00 % | - | 0–150 | 0.00 |
+      | Test assignment five | 28.57 % | 10.00 (50.00 %) | 0–20 | 1.03 % |
+      | Test assignment six | 50.00 % | 5.00 (50.00 %) | 0–10 | 1.80 % |
+      | Test assignment seven | 21.43 % | - | 0–15 | 0.00 |
+      | Test assignment eight | 66.67 % | 10.00 (50.00 %) | 0–20 | 1.60 % |
+      | Test assignment nine | 33.33 % | 5.00 (50.00 %) | 0–10 | 0.80 % |
+      | Test assignment ten | 0.00 %( Empty ) | - | 0–15 | 0.00 % |
+      | Test assignment one | 48.00 % | 60.00 (20.00 %) | 0–300 | 9.60 % |
+      | Test assignment two | 16.00 % | 20.00 (20.00 %) | 0–100 | 3.20 % |
+      | Test assignment three | 24.00 %( Extra credit ) | 40.00 (26.67 %) | 0–150 | 6.40 % |
+      | Test assignment four | 24.00 % | - | 0–150 | 0.00 |
     And I log out
     And I log in as "student1"
     And I follow "Course 1"
@@ -413,13 +411,13 @@ Feature: We can use calculated grade totals
     And I set the field "Grade report" to "User report"
     And the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Range | Contribution to course total |
-      | Test assignment six | 70.00 % | 5.00 (50.00 %) | 0–10 | 8.75 |
-      | Test assignment seven | 30.00 % | - | 0–15 | 0.00 |
-      | Test assignment nine | 100.00 % | 5.00 (50.00 %) | 0–10 | 5.00 |
-      | Test assignment ten | -( Empty ) | - | 0–15 | 0.00 |
-      | Test assignment one | 61.86 % | 60.00 (20.00 %) | 0–300 | 60.0 |
-      | Test assignment three | 30.93 %( Extra credit ) | 40.00 (26.67 %) | 0–150 | 40.0 |
-      | Test assignment four | 30.93 % | - | 0–150 | 0.00 |
+      | Test assignment six | 70.00 % | 5.00 (50.00 %) | 0–10 | 1.80 % |
+      | Test assignment seven | 30.00 % | - | 0–15 | 0.00 |
+      | Test assignment nine | 100.00 % | 5.00 (50.00 %) | 0–10 | 1.03 % |
+      | Test assignment ten | -( Empty ) | - | 0–15 | - |
+      | Test assignment one | 61.86 % | 60.00 (20.00 %) | 0–300 | 12.37 % |
+      | Test assignment three | 30.93 %( Extra credit ) | 40.00 (26.67 %) | 0–150 | 8.25 % |
+      | Test assignment four | 30.93 % | - | 0–150 | 0.00 |
 
   @javascript
   Scenario: Natural aggregation with drop lowest
@@ -504,15 +502,12 @@ Feature: We can use calculated grade totals
   @javascript
   Scenario: Natural aggregation from the setup screen
     And I set the field "Grade report" to "Categories and items"
-    And I follow "Edit   Course 1"
-    And I set the field "Aggregation" to "Natural"
-    And I press "Save changes"
-    And I follow "Edit   Sub category 1"
-    And I set the field "Aggregation" to "Natural"
-    And I press "Save changes"
-    And I follow "Edit   Sub category 2"
-    And I set the field "Aggregation" to "Natural"
-    And I press "Save changes"
+    And I set the following settings for grade item "Course 1":
+      | Aggregation          | Natural |
+    And I set the following settings for grade item "Sub category 1":
+      | Aggregation          | Natural |
+    And I set the following settings for grade item "Sub category 2":
+      | Aggregation          | Natural |
 
     And I set the field "Override weight of Test assignment one" to "1"
     And the field "Weight of Test assignment one" matches value "37.975"
@@ -553,7 +548,7 @@ Feature: We can use calculated grade totals
     And I set the field "Override weight of Sub category 1" to "1"
     And the field "Weight of Test assignment one" matches value "37.975"
     And the field "Weight of Sub category 1" matches value "5.696"
-    And I click on "Reset weights of Sub category 2" "link"
+    And I reset weights for grade category "Sub category 2"
     And the field "Weight of Test assignment ten" matches value "33.333"
 
   @javascript
@@ -587,15 +582,15 @@ Feature: We can use calculated grade totals
     And I set the field "Select all or one user" to "Student 1"
     And the following should exist in the "user-grade" table:
       | Grade item            | Calculated weight | Grade           | Contribution to course total |
-      | Test assignment five  | 57.14 %           | 10.00 (50.00 %) | 10.00                        |
-      | Test assignment six   | 0.00 %            | 5.00 (50.00 %)  | 0.00                         |
-      | Test assignment seven | 42.86 %           | -               | 0.00                         |
-      | Test assignment eight | 0.00 %            | 10.00 (50.00 %) | 0.00                         |
-      | Test assignment nine  | 100.00 %          | 5.00 (50.00 %)  | 5.00                         |
-      | Test assignment ten   | 0.00 %            | -               | 0.00                         |
-      | Test assignment one   | 0.00 %            | 60.00 (20.00 %) | 0.00                         |
-      | Test assignment two   | 22.47 %           | 20.00 (20.00 %) | 20.00                        |
-      | Test assignment three | 33.71 %           | 40.00 (26.67 %) | 40.00                        |
+      | Test assignment five  | 57.14 %           | 10.00 (50.00 %) | 2.25 %                        |
+      | Test assignment six   | 0.00 %            | 5.00 (50.00 %)  | 0.00 %                        |
+      | Test assignment seven | 42.86 %           | -               | 0.00 %                        |
+      | Test assignment eight | 0.00 %            | 10.00 (50.00 %) | 0.00 %                        |
+      | Test assignment nine  | 100.00 %          | 5.00 (50.00 %)  | 1.12 %                         |
+      | Test assignment ten   | 0.00 %            | -               | 0.00                         |
+      | Test assignment one   | 0.00 %            | 60.00 (20.00 %) | 0.00                         |
+      | Test assignment two   | 22.47 %           | 20.00 (20.00 %) | 4.49 %                        |
+      | Test assignment three | 33.71 %           | 40.00 (26.67 %) | 8.99 %                        |
       | Test assignment four  | 33.71 %           | -               | 0.00                         |
     And I log out
     And I log in as "student1"
@@ -606,10 +601,10 @@ Feature: We can use calculated grade totals
     And I set the field "Grade report" to "User report"
     And the following should exist in the "user-grade" table:
       | Grade item            | Calculated weight | Grade           | Contribution to course total |
-      | Test assignment six   | 0.00 %            | 5.00 (50.00 %)  | 0.00                         |
-      | Test assignment seven | 100.00 %          | -               | 0.00                         |
-      | Test assignment nine  | 100.00 %          | 5.00 (50.00 %)  | 5.00                         |
-      | Test assignment ten   | 0.00              | -               | 0.00                         |
-      | Test assignment one   | 0.00 %            | 60.00 (20.00 %) | 0.00                         |
-      | Test assignment three | 46.15 %           | 40.00 (26.67 %) | 40.00                        |
-      | Test assignment four  | 46.15 %           | -               | 0.00                         |
+      | Test assignment six   | 0.00 %            | 5.00 (50.00 %)  | 0.00                         |
+      | Test assignment seven | 100.00 %          | -               | 0.00                         |
+      | Test assignment nine  | 100.00 %          | 5.00 (50.00 %)  | 1.54 %                         |
+      | Test assignment ten   | 0.00              | -               | 0.00                         |
+      | Test assignment one   | 0.00 %            | 60.00 (20.00 %) | 0.00                         |
+      | Test assignment three | 46.15 %           | 40.00 (26.67 %) | 12.31 %                        |
+      | Test assignment four  | 46.15 %           | -               | 0.00 %                        |
index fc61a26..13d1177 100644 (file)
@@ -65,12 +65,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 25.00 % | 60.00 | 15.00 |
-      | Test assignment two | 25.00 % | 20.00 | 5.00 |
-      | Test assignment three | 25.00 % | 40.00 | 10.00 |
-      | Test assignment four | 33.33 % | 10.00 | 0.83 |
-      | Test assignment five | 33.33 % | 70.00 | 5.83 |
-      | Test assignment six | 33.33 % | 30.00 | 2.50 |
+      | Test assignment one | 25.00 % | 60.00 | 15.00 |
+      | Test assignment two | 25.00 % | 20.00 | 5.00 |
+      | Test assignment three | 25.00 % | 40.00 | 10.00 |
+      | Test assignment four | 33.33 % | 10.00 | 0.83 |
+      | Test assignment five | 33.33 % | 70.00 | 5.83 |
+      | Test assignment six | 33.33 % | 30.00 | 2.50 |
 
   @javascript
   Scenario: Weighted mean of grades aggregation
@@ -90,12 +90,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 40.00 % | 60.00 | 24.00 |
-      | Test assignment two | 20.00 % | 20.00 | 4.00 |
-      | Test assignment three | 20.00 % | 40.00 | 8.00 |
-      | Test assignment four | 33.33 % | 10.00 | 0.67 |
-      | Test assignment five | 33.33 % | 70.00 | 4.67 |
-      | Test assignment six | 33.33 % | 30.00 | 2.00 |
+      | Test assignment one | 40.00 % | 60.00 | 24.00 |
+      | Test assignment two | 20.00 % | 20.00 | 4.00 |
+      | Test assignment three | 20.00 % | 40.00 | 8.00 |
+      | Test assignment four | 33.33 % | 10.00 | 0.67 |
+      | Test assignment five | 33.33 % | 70.00 | 4.67 |
+      | Test assignment six | 33.33 % | 30.00 | 2.00 |
 
   @javascript
   Scenario: Simple weighted mean of grades aggregation
@@ -111,12 +111,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 33.33 % | 60.00 | 20.00 |
-      | Test assignment two | 33.33 % | 20.00 | 6.67 |
-      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
-      | Test assignment four | 33.33 % | 10.00 | 1.11 |
-      | Test assignment five | 33.33 % | 70.00 | 7.78 |
-      | Test assignment six | 33.33 % | 30.00 | 3.33 |
+      | Test assignment one | 33.33 % | 60.00 | 20.00 |
+      | Test assignment two | 33.33 % | 20.00 | 6.67 |
+      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
+      | Test assignment four | 33.33 % | 10.00 | 1.11 |
+      | Test assignment five | 33.33 % | 70.00 | 7.78 |
+      | Test assignment six | 33.33 % | 30.00 | 3.33 |
 
   @javascript
   Scenario: Mean of grades (with extra credits) aggregation
@@ -130,12 +130,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 33.33 % | 60.00 | 20.00 |
-      | Test assignment two | 33.33 % | 20.00 | 6.67 |
-      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
-      | Test assignment four | 33.33 % | 10.00 | 1.11 |
-      | Test assignment five | 33.33 % | 70.00 | 7.78 |
-      | Test assignment six | 33.33 % | 30.00 | 3.33 |
+      | Test assignment one | 33.33 % | 60.00 | 20.00 |
+      | Test assignment two | 33.33 % | 20.00 | 6.67 |
+      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 |
+      | Test assignment four | 33.33 % | 10.00 | 1.11 |
+      | Test assignment five | 33.33 % | 70.00 | 7.78 |
+      | Test assignment six | 33.33 % | 30.00 | 3.33 |
 
   @javascript
   Scenario: Median of grades aggregation
@@ -147,12 +147,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 0.00 % | 60.00 | 0.00 |
-      | Test assignment two | 0.00 % | 20.00 | 0.00 |
-      | Test assignment three | 50.00 % | 40.00 | 20.00 |
-      | Test assignment four | 33.33 % | 10.00 | 1.67 |
-      | Test assignment five | 33.33 % | 70.00 | 11.67 |
-      | Test assignment six | 33.33 % | 30.00 | 5.00 |
+      | Test assignment one | 0.00 % | 60.00 | 0.00 |
+      | Test assignment two | 0.00 % | 20.00 | 0.00 |
+      | Test assignment three | 50.00 % | 40.00 | 20.00 |
+      | Test assignment four | 33.33 % | 10.00 | 1.67 |
+      | Test assignment five | 33.33 % | 70.00 | 11.67 |
+      | Test assignment six | 33.33 % | 30.00 | 5.00 |
 
   @javascript
   Scenario: Lowest grade aggregation
@@ -164,12 +164,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 0.00 % | 60.00 | 0.00 |
-      | Test assignment two | 100.00 % | 20.00 | 20.00 |
-      | Test assignment three | 0.00 % | 40.00 | 0.00 |
-      | Test assignment four | 33.33 % | 10.00 | 0.00 |
-      | Test assignment five | 33.33 % | 70.00 | 0.00 |
-      | Test assignment six | 33.33 % | 30.00 | 0.00 |
+      | Test assignment one | 0.00 % | 60.00 | 0.00 |
+      | Test assignment two | 100.00 % | 20.00 | 20.00 |
+      | Test assignment three | 0.00 % | 40.00 | 0.00 |
+      | Test assignment four | 33.33 % | 10.00 | 0.00 |
+      | Test assignment five | 33.33 % | 70.00 | 0.00 |
+      | Test assignment six | 33.33 % | 30.00 | 0.00 |
 
   @javascript
   Scenario: Highest grade aggregation
@@ -181,12 +181,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 100.00 % | 60.00 | 60.00 |
-      | Test assignment two | 0.00 % | 20.00 | 0.00 |
-      | Test assignment three | 0.00 % | 40.00 | 0.00 |
-      | Test assignment four | 33.33 % | 10.00 | 0.00 |
-      | Test assignment five | 33.33 % | 70.00 | 0.00 |
-      | Test assignment six | 33.33 % | 30.00 | 0.00 |
+      | Test assignment one | 100.00 % | 60.00 | 60.00 |
+      | Test assignment two | 0.00 % | 20.00 | 0.00 |
+      | Test assignment three | 0.00 % | 40.00 | 0.00 |
+      | Test assignment four | 33.33 % | 10.00 | 0.00 |
+      | Test assignment five | 33.33 % | 70.00 | 0.00 |
+      | Test assignment six | 33.33 % | 30.00 | 0.00 |
 
   @javascript
   Scenario: Mode of grades aggregation
@@ -198,12 +198,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 100.00 % | 60.00 | 60.00 |
-      | Test assignment two | 0.00 % | 20.00 | 0.00 |
-      | Test assignment three | 0.00 % | 40.00 | 0.00 |
-      | Test assignment four | 33.33 % | 10.00 | 0.00 |
-      | Test assignment five | 33.33 % | 70.00 | 0.00 |
-      | Test assignment six | 33.33 % | 30.00 | 0.00 |
+      | Test assignment one | 100.00 % | 60.00 | 60.00 |
+      | Test assignment two | 0.00 % | 20.00 | 0.00 |
+      | Test assignment three | 0.00 % | 40.00 | 0.00 |
+      | Test assignment four | 33.33 % | 10.00 | 0.00 |
+      | Test assignment five | 33.33 % | 70.00 | 0.00 |
+      | Test assignment six | 33.33 % | 30.00 | 0.00 |
 
   @javascript
   Scenario: View user report with mixed aggregation methods
@@ -219,12 +219,12 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 33.33 % | 60.00 | 60.00 |
-      | Test assignment two | 33.33 % | 20.00 | 20.00 |
-      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 40.00 |
-      | Test assignment four | 33.33 % | 10.00 | 3.33 |
-      | Test assignment five | 33.33 % | 70.00 | 23.33 |
-      | Test assignment six | 33.33 % | 30.00 | 10.00 |
+      | Test assignment one | 33.33 % | 60.00 | 20.00 % |
+      | Test assignment two | 33.33 % | 20.00 | 6.67 % |
+      | Test assignment three | 33.33 %( Extra credit ) | 40.00 | 13.33 % |
+      | Test assignment four | 33.33 % | 10.00 | 1.11 % |
+      | Test assignment five | 33.33 % | 70.00 | 7.78 % |
+      | Test assignment six | 33.33 % | 30.00 | 3.33 % |
       | Category totalWeighted mean of grades. | 33.33 % | 36.67 | - |
       | Course total | - | 156.67 | - |
 
@@ -238,11 +238,11 @@ Feature: We can understand the gradebook user report
     # Check the values in the weights column.
     Then the following should exist in the "user-grade" table:
       | Grade item | Calculated weight | Grade | Contribution to course total |
-      | Test assignment one | 20.00 % | 60.00 | 60.00 |
-      | Test assignment two | 20.00 % | 20.00 | 20.00 |
-      | Test assignment three | 20.00 %( Extra credit ) | 40.00 | 40.00 |
-      | Test assignment four | 33.33 % | 10.00 | 10.00 |
-      | Test assignment five | 33.33 % | 70.00 | 70.00 |
-      | Test assignment six | 33.33 % | 30.00 | 30.00 |
+      | Test assignment one | 20.00 % | 60.00 | 12.00 % |
+      | Test assignment two | 20.00 % | 20.00 | 4.00 % |
+      | Test assignment three | 20.00 %( Extra credit ) | 40.00 | 8.00 % |
+      | Test assignment four | 33.33 % | 10.00 | 2.00 % |
+      | Test assignment five | 33.33 % | 70.00 | 14.00 % |
+      | Test assignment six | 33.33 % | 30.00 | 6.00 % |
       | Category total | 60.00 % | 110.00 | - |
       | Course total | - | 230.00 | - |
index 88e45d9..ed10866 100644 (file)
@@ -72,10 +72,10 @@ Feature: Extra credit contributions are normalised when going out of bounds
       | Manual item 1 | <m1w>             | 80.00  | <m1c>                        |
       | Manual item 2 | <m2w>             | 10.00  | <m2c>                        |
       | Manual item 3 | <m3w>             | 70.00  | <m3c>                        |
-      | Manual item 4 | 0.00 %            | 90.00  | 0.00                         |
+      | Manual item 4 | 0.00 %            | 90.00  | 0.00 %                       |
 
     Examples:
       | aggregation                         | m1w      | m1c   | m2w      | m2c   | m3w     | m3c   |
-      | Natural                             | 100.00 % | 80.00 | 66.67 %  | 10.00 | 57.14 % | 60.00 |
-      | Simple weighted mean of grades      | 100.00 % | 53.33 | 66.67 %  | 6.67  | 57.14 % | 40.00 |
-      | Mean of grades (with extra credits) | 100.00 % | 53.33 | 100.00 % | 10.00 | 52.38 % | 36.67 |
+      | Natural                             | 100.00 % | 53.33 % | 66.67 %  | 6.67 % | 57.14 % | 40.00 % |
+      | Simple weighted mean of grades      | 100.00 % | 53.33 % | 66.67 %  | 6.67 % | 57.14 % | 40.00 % |
+      | Mean of grades (with extra credits) | 100.00 % | 53.33 % | 100.00 % | 10.00 % | 52.38 % | 36.67 % |
index 05d8052..3a1623f 100644 (file)
@@ -105,18 +105,18 @@ Feature: We can use a minimum grade different than zero
     And I set the field "Select all or one user" to "Student 1"
     Then the following should exist in the "user-grade" table:
       | Grade item    | Calculated weight | Grade  | Contribution to course total |
-      | Manual item 1 | 18.18 %           | -25.00 | -25.00                       |
-      | Manual item 2 | 18.18 %           | 50.00  | 50.00                        |
-      | Manual item 3 | 33.33 %           | -80.00 | -80.00                       |
-      | Manual item 4 | 66.67 %           | -10.00 | -10.00                       |
-      | Manual item 5 | 50.00 %           | 50.00  | 50.00                        |
-      | Manual item 6 | 50.00 %           | 75.00  | 75.00                        |
+      | Manual item 1 | 18.18 %           | -25.00 | -4.55 %                      |
+      | Manual item 2 | 18.18 %           | 50.00  | 9.09 %                       |
+      | Manual item 3 | 33.33 %           | -80.00 | -14.55 %                     |
+      | Manual item 4 | 66.67 %           | -10.00 | -1.82 %                      |
+      | Manual item 5 | 50.00 %           | 50.00  | 9.09 %                       |
+      | Manual item 6 | 50.00 %           | 75.00  | 13.64 %                      |
     And I set the field "Select all or one user" to "Student 2"
     And the following should exist in the "user-grade" table:
       | Grade item    | Calculated weight | Grade  | Contribution to course total |
-      | Manual item 1 | 18.18 %           | 0.00   | 0.00                         |
-      | Manual item 2 | 18.18 %           | 50.00  | 50.00                        |
-      | Manual item 3 | 33.33 %           | -10.00 | -10.00                       |
-      | Manual item 4 | 66.67 %           | 50.00  | 50.00                        |
-      | Manual item 5 | 50.00 %           | 50.00  | 50.00                        |
-      | Manual item 6 | 50.00 %           | 50.00  | 50.00                        |
\ No newline at end of file
+      | Manual item 1 | 18.18 %           | 0.00   | 0.00 %                       |
+      | Manual item 2 | 18.18 %           | 50.00  | 9.09 %                       |
+      | Manual item 3 | 33.33 %           | -10.00 | -1.82 %                      |
+      | Manual item 4 | 66.67 %           | 50.00  | 9.09 %                       |
+      | Manual item 5 | 50.00 %           | 50.00  | 9.09 %                       |
+      | Manual item 6 | 50.00 %           | 50.00  | 9.09 %                       |
index c1d325a..860a7b0 100644 (file)
@@ -99,9 +99,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum exactly 100). Extra credit is set to zero.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -115,7 +114,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And the field "Weight of Test assignment five" matches value "60.000"
     And the field "Weight of Test assignment six" matches value "40.000"
     And the field "Weight of Test assignment seven" matches value "0.0"
-    And I follow "Reset weights of Sub category 1"
+    And I reset weights for grade category "Sub category 1"
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -123,9 +122,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum over 100). Extra credit is set to zero.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And I set the field "Override weight of Test assignment five" to "1"
     And I set the field "Override weight of Test assignment six" to "1"
     And I set the field "Weight of Test assignment five" to "60"
@@ -136,7 +134,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And the field "Weight of Test assignment five" matches value "54.545"
     And the field "Weight of Test assignment six" matches value "45.455"
     And the field "Weight of Test assignment seven" matches value "0.0"
-    And I follow "Reset weights of Sub category 1"
+    And I reset weights for grade category "Sub category 1"
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -144,9 +142,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   @javascript
   Scenario: Grade items weights are normalised when all grade item weights are overridden (sum under 100). Extra credit is set to zero.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And I set the field "Override weight of Test assignment five" to "1"
     And I set the field "Override weight of Test assignment six" to "1"
     And I set the field "Weight of Test assignment five" to "40"
@@ -157,7 +154,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And the field "Weight of Test assignment five" matches value "57.143"
     And the field "Weight of Test assignment six" matches value "42.857"
     And the field "Weight of Test assignment seven" matches value "0.0"
-    And I follow "Reset weights of Sub category 1"
+    And I reset weights for grade category "Sub category 1"
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -165,9 +162,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   @javascript
   Scenario: Grade items weights are normalised when not all grade item weights are overridden. Extra credit is set respectful to non-overridden items.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And I set the field "Override weight of Test assignment five" to "1"
     And I set the field "Weight of Test assignment five" to "40"
     And I press "Save changes"
@@ -176,7 +172,7 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And the field "Weight of Test assignment five" matches value "40.00"
     And the field "Weight of Test assignment six" matches value "60.000"
     And the field "Weight of Test assignment seven" matches value "90.0"
-    And I follow "Reset weights of Sub category 1"
+    And I reset weights for grade category "Sub category 1"
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -185,9 +181,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   Scenario: The extra credit grade item weight is overridden to a figure over one hundred and then
   the grade item is set to normal.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And I set the field "Override weight of Test assignment seven" to "1"
     And I set the field "Weight of Test assignment seven" to "105"
     And I press "Save changes"
@@ -195,9 +190,8 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "105.0"
-    And I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "0"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 0 |
     And I should see "Your weights have been adjusted to total 100."
 
     And the field "Weight of Test assignment five" matches value "0.0"
@@ -208,14 +202,13 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   Scenario: The extra credit grade item weight is overridden to a figure over one hundred and then
   the grade category is reset.
 
-    When I follow "Edit  assign Test assignment seven"
-    And I set the field "Extra credit" to "1"
-    And I press "Save changes"
+    When I set the following settings for grade item "Test assignment seven":
+      | Extra credit | 1 |
     And I set the field "Override weight of Test assignment seven" to "1"
     And I set the field "Weight of Test assignment seven" to "105"
     And I press "Save changes"
 
-    And I follow "Reset weights of Sub category 1"
+    And I reset weights for grade category "Sub category 1"
     And the field "Weight of Test assignment five" matches value "66.667"
     And the field "Weight of Test assignment six" matches value "33.333"
     And the field "Weight of Test assignment seven" matches value "50.0"
@@ -238,15 +231,14 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   @javascript
   Scenario: With one grade item set as extra credit, when I reset the weights for a category they return to the natural weights.
 
-  When I follow "Edit  assign Test assignment five"
-  And I set the field "Extra credit" to "1"
-  And I press "Save changes"
+  When I set the following settings for grade item "Test assignment five":
+    | Extra credit | 1 |
   And I set the field "Override weight of Test assignment six" to "1"
   And I set the field "Override weight of Test assignment seven" to "1"
   And I set the field "Weight of Test assignment six" to "55"
   And I set the field "Weight of Test assignment seven" to "40"
   And I press "Save changes"
-  And I follow "Reset weights of Sub category 1"
+  And I reset weights for grade category "Sub category 1"
   Then the field "Weight of Test assignment five" matches value "80.0"
   And the field "Weight of Test assignment six" matches value "40.0"
   And the field "Weight of Test assignment seven" matches value "60.0"
@@ -273,4 +265,4 @@ Feature: We can use natural aggregation and weights will be normalised to a tota
   And I press "Save changes"
   And the field "Weight of Test assignment five" matches value "0.0"
   And the field "Weight of Test assignment six" matches value "100.0"
-  And the field "Weight of Test assignment seven" matches value "0.0"
\ No newline at end of file
+  And the field "Weight of Test assignment seven" matches value "0.0"
index c884ba8..c04f0f8 100644 (file)
@@ -89,7 +89,7 @@ Feature: View gradebook when scales are used
     And I click on "Select all or one user" "select"
     And the following should exist in the "user-grade" table:
       | Grade item          | Grade | Range | Percentage | Contribution to course total |
-      | Test assignment one | C     | F–A   | 50.00 %    | 3.00                         |
+      | Test assignment one | C     | F–A   | 50.00 %    | 60.00 %                      |
       | Category total      | 3.00  | 0–5   | 60.00 %    | -                            |
       | Course total        | 3.00  | 0–5   | 60.00 %    | -                            |
     And I set the field "jump" to "Categories and items"
@@ -104,7 +104,7 @@ Feature: View gradebook when scales are used
     And I follow "Grades"
     And the following should exist in the "user-grade" table:
       | Grade item          | Grade | Range | Percentage | Contribution to course total |
-      | Test assignment one | B     | F–A   | 75.00 %    | 4.00                         |
+      | Test assignment one | B     | F–A   | 75.00 %    | 80.00 %                      |
       | Category total      | 4.00  | 0–5   | 80.00 %    | -                            |
       | Course total        | 4.00  | 0–5   | 80.00 %    | -                            |
 
@@ -158,11 +158,11 @@ Feature: View gradebook when scales are used
 
     Examples:
       | aggregation                         | coursetotal1 | coursetotal2 | coursetotal3 | coursetotal4 | coursetotal5 |overallavg | courseperc2 | courseperc3 | contrib2 | contrib3 |
-      | Mean of grades                      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Weighted mean of grades             | -            | -            | -            | -            | -            | -         | -           | -           | 0.00     | 0.00     |
-      | Simple weighted mean of grades      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Mean of grades (with extra credits) | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Median of grades                    | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Lowest grade                        | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Highest grade                       | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
-      | Mode of grades                      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00    | 50.00    |
+      | Mean of grades                      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Weighted mean of grades             | -            | -            | -            | -            | -            | -         | -           | -           | 0.00 %   | 0.00 %   |
+      | Simple weighted mean of grades      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Mean of grades (with extra credits) | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Median of grades                    | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Lowest grade                        | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Highest grade                       | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
+      | Mode of grades                      | 100.00       | 75.00        | 50.00        | 25.00        | 0.00         | 50.00     | 75.00 %     | 50.00 %     | 75.00 %  | 50.00 %  |
index 6692c49..5a83d48 100644 (file)
@@ -70,13 +70,13 @@ Feature: View gradebook when single item scales are used
     And I set the field "Select all or one user" to "Student 1"
     And the following should exist in the "user-grade" table:
       | Grade item          | Grade | Range     | Contribution to course total |
-      | Test assignment one | Ace!  | Ace!–Ace! | 1.00                         |
+      | Test assignment one | Ace!  | Ace!–Ace! | 100.00 %                     |
       | Category total      | 1.00  | 0–1       | -                            |
       | Course total        | 1.00  | 0–1       | -                            |
     And I set the field "Select all or one user" to "Student 2"
     And the following should exist in the "user-grade" table:
       | Grade item          | Grade | Range     | Contribution to course total |
-      | Test assignment one | -     | Ace!–Ace! | 0.00                         |
+      | Test assignment one | -     | Ace!–Ace! | -                            |
       | Category total      | -     | 0–1       | -                            |
       | Course total        | -     | 0–1       | -                            |
     And I set the field "jump" to "Categories and items"
@@ -124,11 +124,11 @@ Feature: View gradebook when single item scales are used
 
     Examples:
       | aggregation                         | contrib1 | cattotal1 | coursetotal1 | catavg | overallavg |
-      | Mean of grades                      | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Weighted mean of grades             | 0.00     | 100.00    | -            | 100.00 | -          |
-      | Simple weighted mean of grades      | 0.00     | -         | -            | -      | -          |
-      | Mean of grades (with extra credits) | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Median of grades                    | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Lowest grade                        | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Highest grade                       | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
-      | Mode of grades                      | 100.00   | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Mean of grades                      | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Weighted mean of grades             | 0.00 %   | 100.00    | -            | 100.00 | -          |
+      | Simple weighted mean of grades      | 0.00 %   | -         | -            | -      | -          |
+      | Mean of grades (with extra credits) | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Median of grades                    | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Lowest grade                        | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Highest grade                       | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
+      | Mode of grades                      | 100.00 % | 100.00    | 100.00       | 100.00 | 100.00     |
index ac326e7..0218022 100644 (file)
@@ -136,43 +136,43 @@ echo $OUTPUT->header();
 ?>
 <div id="addmembersform">
     <h3 class="main"><?php print_string('addgroupstogroupings', 'group'); echo ": $groupingname"; ?></h3>
-
     <form id="assignform" method="post" action="">
     <div>
     <input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
-
-    <table summary="" cellpadding="5" cellspacing="0">
+    <table summary="" class="generaltable generalbox groupmanagementtable boxaligncenter">
     <tr>
-      <td valign="top">
+      <td id="existingcell">
           <label for="removeselect"><?php print_string('existingmembers', 'group', $currentmemberscount); ?></label>
-          <br />
+          <div class="userselector" id="removeselect_wrapper">
           <select name="removeselect[]" size="20" id="removeselect" multiple="multiple"
                   onfocus="document.getElementById('assignform').add.disabled=true;
                            document.getElementById('assignform').remove.disabled=false;
                            document.getElementById('assignform').addselect.selectedIndex=-1;">
           <?php echo $currentmembersoptions ?>
-          </select></td>
-      <td valign="top">
-
+          </select></div></td>
+      <td id="buttonscell">
         <p class="arrow_button">
-            <input name="add" id="add" type="submit" value="<?php echo '&nbsp;'.$OUTPUT->larrow().' &nbsp; &nbsp; '.get_string('add'); ?>" title="<?php print_string('add'); ?>" />
-            <br />
-            <input name="remove" id="remove" type="submit" value="<?php echo '&nbsp; '.$OUTPUT->rarrow().' &nbsp; &nbsp; '.get_string('remove'); ?>" title="<?php print_string('remove'); ?>" />
+            <input name="add" id="add" type="submit"
+                   value="<?php echo $OUTPUT->larrow().'&nbsp;'.get_string('add'); ?>"
+                   title="<?php print_string('add'); ?>" /><br>
+            <input name="remove" id="remove" type="submit"
+                   value="<?php echo get_string('remove').'&nbsp;'.$OUTPUT->rarrow(); ?>"
+                   title="<?php print_string('remove'); ?>" />
         </p>
       </td>
-      <td valign="top">
+      <td id="potentialcell">
           <label for="addselect"><?php print_string('potentialmembers', 'group', $potentialmemberscount); ?></label>
-          <br />
+          <div class="userselector" id="addselect_wrapper">
           <select name="addselect[]" size="20" id="addselect" multiple="multiple"
                   onfocus="document.getElementById('assignform').add.disabled=false;
                            document.getElementById('assignform').remove.disabled=true;
                            document.getElementById('assignform').removeselect.selectedIndex=-1;">
          <?php echo $potentialmembersoptions ?>
          </select>
-         <br />
+          </div>
        </td>
     </tr>
-    <tr><td>
+    <tr><td colspan="3" id="backcell">
         <input type="submit" name="cancel" value="<?php print_string('backtogroupings', 'group'); ?>" />
     </td></tr>
     </table>
index b32a971..1187a49 100644 (file)
@@ -576,6 +576,7 @@ $string['reportdefault'] = 'Report default ({$a})';
 $string['reportplugins'] = 'Report plugins';
 $string['reportsettings'] = 'Report settings';
 $string['reprintheaders'] = 'Reprint headers';
+$string['resetweightsshort'] = 'Reset weights';
 $string['resetweights'] = 'Reset weights of {$a->itemname}';
 $string['respectingcurrentdata'] = 'leaving current configuration unmodified';
 $string['rowpreviewnum'] = 'Preview rows';
index 79cc8d2..1282b05 100644 (file)
@@ -55,3 +55,4 @@ $string['ratingtime'] = 'Restrict ratings to items with dates in this range:';
 $string['ratings'] = 'Ratings';
 $string['rolewarning'] = 'Roles with permission to rate';
 $string['rolewarning_help'] = 'To submit ratings users require the moodle/rating:rate capability and any module specific capabilities. Users assigned the following roles should be able to rate items. The list of roles may be amended via the permissions link in the administration block.';
+$string['scaleselectionrequired'] = 'When selecting a ratings aggregate type you must also select to use either a scale or set a maximum points.';
\ No newline at end of file
index d112172..17313e0 100644 (file)
@@ -93,12 +93,18 @@ class manager {
         $tasks = self::load_default_scheduled_tasks_for_component($componentname);
 
         $tasklocks = array();
-        foreach ($tasks as $task) {
+        foreach ($tasks as $taskid => $task) {
             $classname = get_class($task);
             if (strpos($classname, '\\') !== 0) {
                 $classname = '\\' . $classname;
             }
 
+            // If there is an existing task with a custom schedule, do not override it.
+            $currenttask = self::get_scheduled_task($classname);
+            if ($currenttask && $currenttask->is_customised()) {
+                $tasks[$taskid] = $currenttask;
+            }
+
             if (!$lock = $cronlockfactory->get_lock($classname, 10, 60)) {
                 // Could not get all the locks required - release all locks and fail.
                 foreach ($tasklocks as $tasklock) {
@@ -330,7 +336,7 @@ class manager {
 
         $tasks = array();
         // We are just reading - so no locks required.
-        $records = $DB->get_records('task_scheduled', array('componentname' => $componentname), 'classname', '*', IGNORE_MISSING);
+        $records = $DB->get_records('task_scheduled', array('component' => $componentname), 'classname', '*', IGNORE_MISSING);
         foreach ($records as $record) {
             $task = self::scheduled_task_from_record($record);
             // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
index e01a493..f71f55f 100644 (file)
@@ -968,7 +968,13 @@ $services = array(
             'mod_forum_get_forum_discussions_paginated',
             'mod_forum_get_forum_discussion_posts',
             'core_files_get_files',
-            'core_message_get_messages'),
+            'core_message_get_messages',
+            'core_message_create_contacts',
+            'core_message_delete_contacts',
+            'core_message_block_contacts',
+            'core_message_unblock_contacts',
+            'core_message_get_contacts',
+            'core_message_search_contacts'),
         'enabled' => 0,
         'restrictedusers' => 0,
         'shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE,
index 2521516..d76122b 100644 (file)
@@ -207,30 +207,82 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
      *
      * @param string $event Name of event
      * @param mixed $arg event arguments
-     * @param object $caller calling object
+     * @param moodleform $caller calling object
      * @return mixed
      */
     public function onQuickFormEvent($event, $arg, &$caller) {
-        global $COURSE;
-
         switch ($event) {
+            case 'createElement':
+                // The first argument is the name.
+                $name = $arg[0];
+
+                // Set disable actions.
+                $caller->disabledIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale');
+                $caller->disabledIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point');
+
+                // Set validation rules for the sub-elements belonging to this element.
+                // A handy note: the parent scope of a closure is the function in which the closure was declared.
+                // Because of this using $this is safe despite the closures being called statically.
+                // A nasty magic hack!
+                $checkmaxgrade = function($val) {
+                    // Closure to validate a max points value. See the note above about scope if this confuses you.
+                    if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
+                        if (!isset($val['modgrade_point'])) {
+                            return false;
+                        }
+                        return $this->validate_point($val['modgrade_point']);
+                    }
+                    return true;
+                };
+                $checkvalidscale = function($val) {
+                    // Closure to validate a scale value. See the note above about scope if this confuses you.
+                    if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'scale') {
+                        if (!isset($val['modgrade_scale'])) {
+                            return false;
+                        }
+                        return $this->validate_scale($val['modgrade_scale']);
+                    }
+                    return true;
+                };
+
+                $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax'));
+                $invalidscale = get_string('modgradeerrorbadscale', 'grades');
+                // When creating the rules the sixth arg is $force, we set it to true because otherwise the form
+                // will attempt to validate the existence of the element, we don't want this because the element
+                // is being created right now and doesn't actually exist as a registered element yet.
+                $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade, 'server', false, true);
+                $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale, 'server', false, true);
+
+                break;
+
             case 'updateValue':
+                // As this is a group element with no value of its own we are only interested in situations where the
+                // default value or a constant value are being provided to the actual element.
+                // In this case we expect an int that is going to translate to a scale if negative, or to max points
+                // if positive.
+
+                // A constant value should be given as an int.
+                // The default value should be an int and should really be $CFG->gradepointdefault.
                 $value = $this->_findValue($caller->_constantValues);
                 if (null === $value) {
                     if ($caller->isSubmitted()) {
-                        $value = $this->_findValue($caller->_submitValues);
-                    } else {
-                        $value = $this->_findValue($caller->_defaultValues);
+                        break;
                     }
+                    $value = $this->_findValue($caller->_defaultValues);
                 }
 
-                $name = $this->getName();
-
-                // Set disable actions.
-                $caller->disabledIf($name.'[modgrade_scale]', $name.'[modgrade_type]', 'neq', 'scale');
-                $caller->disabledIf($name.'[modgrade_point]', $name.'[modgrade_type]', 'neq', 'point');
+                if (!is_null($value) && !is_scalar($value)) {
+                    // Something unexpected (likely an array of subelement values) has been given - this will be dealt
+                    // with somewhere else - where exactly... likely the subelements.
+                    debugging('An invalid value (type '.gettype($value).') has arrived at '.__METHOD__, DEBUG_DEVELOPER);
+                    break;
+                }
 
                 // Set element state for existing data.
+                // This is really a pretty hacky thing to do, when data is being set the group element is called
+                // with the data first and the subelements called afterwards.
+                // This means that the subelements data (inc const and default values) can be overridden by form code.
+                // So - when we call this code really we can't be sure that will be the end value for the element.
                 if (!empty($this->_elements)) {
                     if (!empty($value)) {
                         if ($value < 0) {
@@ -245,38 +297,10 @@ class MoodleQuickForm_modgrade extends MoodleQuickForm_group{
                         $this->_elements[7]->setValue('');
                     }
                 }
-
-                // Value Validation.
-                if ($name && $caller->elementExists($name)) {
-                    $checkmaxgrade = function($val) {
-                        if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'point') {
-                            if (!isset($val['modgrade_point'])) {
-                                return false;
-                            }
-                            return $this->validate_point($val['modgrade_point']);
-                        }
-                        return true;
-                    };
-
-                    $checkvalidscale = function($val) {
-                        if (isset($val['modgrade_type']) && $val['modgrade_type'] === 'scale') {
-                            if (!isset($val['modgrade_scale'])) {
-                                return false;
-                            }
-                            return $this->validate_scale($val['modgrade_scale']);
-                        }
-                        return true;
-                    };
-
-                    $maxgradeexceeded = get_string('modgradeerrorbadpoint', 'grades', get_config('core', 'gradepointmax'));
-                    $invalidscale = get_string('modgradeerrorbadscale', 'grades');
-                    $caller->addRule($name, $maxgradeexceeded, 'callback', $checkmaxgrade);
-                    $caller->addRule($name, $invalidscale, 'callback', $checkvalidscale);
-                }
                 break;
-
         }
 
+        // Always let the parent do its thing!
         return parent::onQuickFormEvent($event, $arg, $caller);
     }
 
index dc2f0b3..21e496b 100644 (file)
@@ -779,6 +779,24 @@ class grade_category extends grade_object {
     private function set_usedinaggregation($userid, $usedweights, $novalue, $dropped, $extracredit) {
         global $DB;
 
+        // First set them all to weight null and status = 'unknown'.
+        if ($allitems = grade_item::fetch_all(array('categoryid'=>$this->id))) {
+            list($itemsql, $itemlist) = $DB->get_in_or_equal(array_keys($allitems), SQL_PARAMS_NAMED, 'g');
+
+            $itemlist['userid'] = $userid;
+
+            $DB->set_field_select('grade_grades',
+                                  'aggregationstatus',
+                                  'unknown',
+                                  "itemid $itemsql AND userid = :userid",
+                                  $itemlist);
+            $DB->set_field_select('grade_grades',
+                                  'aggregationweight',
+                                  0,
+                                  "itemid $itemsql AND userid = :userid",
+                                  $itemlist);
+        }
+
         // Included.
         if (!empty($usedweights)) {
             // The usedweights items are updated individually to record the weights.
@@ -1436,6 +1454,9 @@ class grade_category extends grade_object {
             if ($gradeitem->gradetype == GRADE_TYPE_NONE || $gradeitem->gradetype == GRADE_TYPE_TEXT) {
                 // Text items and none items do not have a weight.
                 continue;
+            } else if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) {
+                // We will not aggregate outcome items, so we can ignore them.
+                continue;
             }
 
             // Record the ID and the weight for this grade item.
@@ -1508,6 +1529,9 @@ class grade_category extends grade_object {
                 // Text items and none items do not have a weight, no need to set their weight to
                 // zero as they must never be used during aggregation.
                 continue;
+            } else if (!$this->aggregateoutcomes && $gradeitem->is_outcome_item()) {
+                // We will not aggregate outcome items, so we can ignore updating their weights.
+                continue;
             }
 
             if (!$gradeitem->weightoverride) {
index 46eea2d..63b9914 100644 (file)
@@ -703,6 +703,8 @@ class grade_grade extends grade_object {
             } else if ($grade_grade->is_hidden()) {
                 $hiddenfound = true;
                 $altered[$grade_grade->itemid] = null;
+                $alteredaggregationstatus[$grade_grade->itemid] = 'dropped';
+                $alteredaggregationweight[$grade_grade->itemid] = 0;
             } else if ($grade_grade->is_locked() or $grade_grade->is_overridden()) {
                 // no need to recalculate locked or overridden grades
             } else {
index d21c5b1..aabbdc6 100644 (file)
@@ -2282,7 +2282,14 @@ class global_navigation extends navigation_node {
             $userscourses = enrol_get_users_courses($user->id);
             $userscoursesnode = $usernode->add(get_string('courses'));
 
+            $count = 0;
             foreach ($userscourses as $usercourse) {
+                if ($count === (int)$CFG->navcourselimit) {
+                    $url = new moodle_url('/user/profile.php', array('id' => $user->id, 'showallcourses' => 1));
+                    $userscoursesnode->add(get_string('showallcourses'), $url);
+                    break;
+                }
+                $count++;
                 $usercoursecontext = context_course::instance($usercourse->id);
                 $usercourseshortname = format_string($usercourse->shortname, true, array('context' => $usercoursecontext));
                 $usercoursenode = $userscoursesnode->add($usercourseshortname, new moodle_url('/user/view.php', array('id'=>$user->id, 'course'=>$usercourse->id)), self::TYPE_CONTAINER);
index 2d78c8f..b0eb084 100644 (file)
@@ -108,115 +108,131 @@ class core_event_user_graded_testcase extends advanced_testcase {
         $user = $this->getDataGenerator()->create_user();
         $this->getDataGenerator()->enrol_user($user->id, $course->id);
         $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+        $quizitemparams = array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id,
+            'courseid' => $course->id);
+        $gradeitem = grade_item::fetch($quizitemparams);
+        $courseitem = grade_item::fetch_course_item($course->id);
 
         // Now mark the quiz using grade_update as this is the function that modules use.
         $grade = array();
         $grade['userid'] = $user->id;
-        $grade['rawgrade'] = 50;
+        $grade['rawgrade'] = 60;
 
         $sink = $this->redirectEvents();
         grade_update('mod/quiz', $course->id, 'mod', 'quiz', $quiz->id, 0, $grade);
         $events = $sink->get_events();
         $sink->close();
 
-        // Ensure we have two user_graded events - for the quiz and for the course total.
+        // Ensure we have two user_graded events, one for the item, one for the course.
         $this->assertEquals(2, count($events));
-        $eventitem = reset($events);
-        $eventcourse = next($events);
-        $this->assertInstanceOf('\core\event\user_graded', $eventitem);
-        $this->AssertEquals($quiz->name, $eventitem->get_grade()->grade_item->get_name());
-        $this->assertInstanceOf('\core\event\user_graded', $eventcourse);
-        $this->AssertEquals('Course total', $eventcourse->get_grade()->grade_item->get_name());
-
-        // Get the grade item.
-        $gradeitem = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id,
-            'courseid' => $course->id));
-
-        // Let's alter the grade in the DB so when we call regrade_final_grades() it is changed and an event is called.
-        $sql = "UPDATE {grade_grades}
-                   SET finalgrade = '2'
-                 WHERE itemid = :itemid
-                   AND userid = :userid";
-        $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id));
-
-        // Now check when we regrade this that there is a user graded event.
-        $sink = $this->redirectEvents();
-        $gradeitem->regrade_final_grades();
-        $events = $sink->get_events();
-        $event = reset($events);
-        $sink->close();
-
-        // Ensure we have a user_graded event.
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
 
-        // Remove the grades.
+        // Remove the grades, force the regrading and re-fetch the item. This is needed because the item
+        // will be set as needing an update when the grades are deleted.
         $gradeitem->delete_all_grades();
+        grade_regrade_final_grades($course->id);
+        $gradeitem = grade_item::fetch($quizitemparams);
 
-        // Now, create a grade using update_raw_grade().
+        // Now, create a grade using grade_item::update_final_grade().
         $sink = $this->redirectEvents();
-        $gradeitem->update_raw_grade($user->id, 50);
+        $gradeitem->update_raw_grade($user->id, 10);
         $events = $sink->get_events();
-        $event = reset($events);
         $sink->close();
 
-        // Ensure we have a user_graded event.
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
 
-        // Now, update this grade using update_raw_grade().
+        // Now, update this grade using grade_item::update_raw_grade().
         $sink = $this->redirectEvents();
-        $gradeitem->update_raw_grade($user->id, 100);
+        $gradeitem->update_raw_grade($user->id, 20);
         $events = $sink->get_events();
-        $event = reset($events);
         $sink->close();
 
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
 
-        // Remove the grades.
+        // Remove the grades, force the regrading and re-fetch the item. This is needed because the item
+        // will be set as needing an update when the grades are deleted.
         $gradeitem->delete_all_grades();
+        grade_regrade_final_grades($course->id);
+        $gradeitem = grade_item::fetch($quizitemparams);
 
-        // Now, create a grade using update_final_grade().
+        // Now, create a grade using grade_item::update_final_grade().
         $sink = $this->redirectEvents();
-        $gradeitem->update_final_grade($user->id, 50);
+        $gradeitem->update_final_grade($user->id, 30);
         $events = $sink->get_events();
-        $event = reset($events);
         $sink->close();
 
-        // Ensure we have a user_graded event.
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
 
-        // Now, update this grade using update_final_grade().
+        // Now, update this grade using grade_item::update_final_grade().
         $sink = $this->redirectEvents();
-        $gradeitem->update_final_grade($user->id, 100);
+        $gradeitem->update_final_grade($user->id, 40);
         $events = $sink->get_events();
-        $event = reset($events);
         $sink->close();
 
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
+
+        // Remove the overridden flag from the grade, this was set by grade_item::update_final_grade().
+        $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id));
+        $gradegrade->set_overridden(false, false);
 
         // Let's change the calculation to anything that won't cause an error.
         $calculation = calc_formula::unlocalize("=3");
         $gradeitem->set_calculation($calculation);
 
-        // Let's alter the grade in the DB so when we call compute() it is changed and an event is called.
-        $sql = "UPDATE {grade_grades}
-                   SET finalgrade = 2, overridden = 0
-                 WHERE itemid = :itemid
-                   AND userid = :userid";
-        $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id));
+        // Now force the computation of the grade.
+        $sink = $this->redirectEvents();
+        grade_regrade_final_grades($course->id);
+        $events = $sink->get_events();
+        $sink->close();
+
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
+
+        // Now, let's trick the gradebook, we manually update a grade, and flag the grade item as
+        // needing a regrading, so we can trigger the event in grade_item::regrade_final_grades().
+        $gradeitem = grade_item::fetch($quizitemparams);
+        $gradeitem->set_calculation('');
+        $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id));
+        $gradegrade->rawgrade = 50;
+        $gradegrade->update();
 
-        // Now check when we compute that there is a user graded event.
         $sink = $this->redirectEvents();
-        $gradeitem->compute();
+        grade_regrade_final_grades($course->id);
         $events = $sink->get_events();
-        $event = reset($events);
         $sink->close();
 
-        $this->assertEquals(1, count($events));
-        $this->assertInstanceOf('\core\event\user_graded', $event);
+        // Ensure we have two user_graded events, one for the item, one for the course.
+        $this->assertEquals(2, count($events));
+        $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+        $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+        $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+        $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
     }
 }
index 1b0c2bc..10b4007 100644 (file)
@@ -51,6 +51,13 @@ class core_questionlib_testcase extends advanced_testcase {
         $this->resetAfterTest();
     }
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     public function test_question_reorder_qtypes() {
         $this->assertEquals(
             array(0 => 't2', 1 => 't1', 2 => 't3'),
index 599c501..27b401a 100644 (file)
@@ -157,6 +157,45 @@ class core_scheduled_task_testcase extends advanced_testcase {
         date_default_timezone_set($currenttimezonephp);
     }
 
+    public function test_reset_scheduled_tasks_for_component() {
+        global $DB;
+
+        $this->resetAfterTest(true);
+        // Remember the defaults.
+        $defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
+        $initcount = count($defaulttasks);
+        // Customise a task.
+        $firsttask = reset($defaulttasks);
+        $firsttask->set_minute('1');
+        $firsttask->set_hour('2');
+        $firsttask->set_month('3');
+        $firsttask->set_day_of_week('4');
+        $firsttask->set_day('5');
+        $firsttask->set_customised('1');
+        \core\task\manager::configure_scheduled_task($firsttask);
+        $firsttaskrecord = \core\task\manager::record_from_scheduled_task($firsttask);
+        // We reset this field, because we do not want to compare it.
+        $firsttaskrecord->nextruntime = '0';
+
+        // Now call reset on all the tasks.
+        \core\task\manager::reset_scheduled_tasks_for_component('moodle');
+
+        // Load the tasks again.
+        $defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle');
+        $finalcount = count($defaulttasks);
+        // Compare the first task.
+        $newfirsttask = reset($defaulttasks);
+        $newfirsttaskrecord = \core\task\manager::record_from_scheduled_task($newfirsttask);
+        // We reset this field, because we do not want to compare it.
+        $newfirsttaskrecord->nextruntime = '0';
+
+        // Assert a customised task was not altered by reset.
+        $this->assertEquals($firsttaskrecord, $newfirsttaskrecord);
+
+        // Assert we have the same number of tasks.
+        $this->assertEquals($initcount, $finalcount);
+    }
+
     public function test_get_next_scheduled_task() {
         global $DB;
 
index d3167d0..6d4e80a 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 8403f33..bac5cb5 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 0ec6b7c..5d21f8e 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 5ab6c80..3ada4cc 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 8b58eea..547ce89 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 dadf0e8..e5f5f10 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 67332a3..ae00312 100644 (file)
@@ -339,12 +339,12 @@ DOCK.prototype = {
          * Fires after the dock has been changed from hidden to visible.
          * @event dock:shown
          */
-        this.publish('dock:shown', {prefix:'dock'});
+        this.publish('dock:shown', {prefix:'dock', broadcast: 2});
         /**
          * Fired after the dock has been changed from visible to hidden.
          * @event dock:hidden
          */
-        this.publish('dock:hidden', {prefix:'dock'});
+        this.publish('dock:hidden', {prefix:'dock', broadcast: 2});
         /**
          * Fires when an item is added to the dock.
          * @event dock:itemadded
@@ -360,7 +360,7 @@ DOCK.prototype = {
          * This happens after the itemadded and itemremoved events have been called.
          * @event dock:itemschanged
          */
-        this.publish('dock:itemschanged', {prefix:'dock'});
+        this.publish('dock:itemschanged', {prefix:'dock', broadcast: 2});
         /**
          * Fires once when the docks panel is first initialised.
          * @event dock:panelgenerated
index b7788c2..aac1b9c 100644 (file)
@@ -69,6 +69,16 @@ Y.extend(DIALOGUE, Y.Panel, {
     _orientationevent : null,
     _calculatedzindex : false,
 
+    /**
+     * The original position of the dialogue before it was reposition to
+     * avoid browser jumping.
+     *
+     * @property _originalPosition
+     * @protected
+     * @type Array
+     */
+    _originalPosition: null,
+
     /**
      * Initialise the dialogue.
      *
@@ -103,14 +113,21 @@ Y.extend(DIALOGUE, Y.Panel, {
         }
         // Recalculate the zIndex every time the modal is altered.
         this.on('maskShow', this.applyZIndex);
-        // We must show - after the dialogue has been positioned,
-        // either by centerDialogue or makeResonsive. This is because the show() will trigger
-        // a focus on the dialogue, which will scroll the page. If the dialogue has not
-        // been positioned it will scroll back to the top of the page.
-        if (this.get('visible')) {
-            this.show();
-            this.keyDelegation();
-        }
+
+        this.on('maskShow', function() {
+            // When the mask shows, position the boundingBox at the top-left of the window such that when it is
+            // focused, the position does not change.
+            var w = Y.one(Y.config.win),
+                bb = this.get('boundingBox');
+
+            if (!this.get('center')) {
+                this._originalPosition = bb.getXY();
+            }
+            bb.setStyles({
+                top: w.get('scrollTop'),
+                left: w.get('scrollLeft')
+            });
+        }, this);
 
         // Remove the dialogue from the DOM when it is destroyed.
         this.after('destroyedChange', function(){
@@ -296,7 +313,7 @@ Y.extend(DIALOGUE, Y.Panel, {
                Math.floor(Y.one(document.body).get('winWidth')) < this.get('responsiveWidth');
     },
 
-    show : function() {
+    show: function() {
         var result = null,
             header = this.headerNode,
             content = this.bodyNode,
@@ -305,6 +322,11 @@ Y.extend(DIALOGUE, Y.Panel, {
 
         result = DIALOGUE.superclass.show.call(this);
 
+        if (!this.get('center') && this._originalPosition) {
+            // Restore the dialogue position to it's location before it was moved at show time.
+            this.get('boundingBox').setXY(this._originalPosition);
+        }
+
         // Lock scroll if the plugin is present.
         if (this.lockScroll) {
             // We need to force the scroll locking for full screen dialogues, even if they have a small vertical size to
index 6afe93f..207a64b 100644 (file)
@@ -52,6 +52,13 @@ class assignfeedback_editpdf_testcase extends mod_assign_base_testcase {
         parent::setUp();
     }
 
+    /**
+     * Tidy up open files that may be left open.
+     */
+    protected function tearDown() {
+        gc_collect_cycles();
+    }
+
     protected function create_assign_and_submit_pdf() {
         global $CFG;
         $assign = $this->create_instance(array('assignsubmission_onlinetext_enabled' => 1,
index 66ab13d..e2f6ea0 100644 (file)
@@ -507,7 +507,7 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         // Create an assignment with a description that should be hidden.
         $assign = $this->create_instance(array('duedate'=>$now + 160,
                                                'alwaysshowdescription'=>false,
-                                               'allowsubmissionsfromdate'=>$now+3,
+                                               'allowsubmissionsfromdate'=>$now + 60,
                                                'intro'=>'Some text'));
 
         // Get the event from the calendar.
@@ -515,7 +515,11 @@ class mod_assign_locallib_testcase extends mod_assign_base_testcase {
         $event = $DB->get_record('event', $params);
 
         $this->assertEmpty($event->description);
-        sleep(6);
+
+        // Change the allowsubmissionfromdate to the past - do this directly in the DB
+        // because if we call the assignment update method - it will update the calendar
+        // and we want to test that this works from cron.
+        $DB->set_field('assign', 'allowsubmissionsfromdate', $now - 60, array('id'=>$assign->get_instance()->id));
         // Run cron to update the event in the calendar.
         assign::cron();
         $event = $DB->get_record('event', $params);
index fc5eb63..29239ec 100644 (file)
@@ -55,9 +55,9 @@ class mod_forum_mail_testcase extends advanced_testcase {
         $messages = $helper->mailsink->get_messages();
         $this->assertEquals(0, count($messages));
 
-        // Forcibly reduce the maxeditingtime to a one second to ensure that
-        // messages are sent out.
-        $CFG->maxeditingtime = 1;
+        // Forcibly reduce the maxeditingtime to a second in the past to
+        // ensure that messages are sent out.
+        $CFG->maxeditingtime = -1;
 
         // Ensure that we don't prevent e-mail as this will cause unit test failures.
         $CFG->noemailever = false;
@@ -107,6 +107,7 @@ class mod_forum_mail_testcase extends advanced_testcase {
      * @param array An array containing the discussion object, and the post object
      */
     protected function helper_post_to_forum($forum, $author) {
+        global $DB;
         $generator = $this->getDataGenerator()->get_plugin_generator('mod_forum');
 
         // Create a discussion in the forum, and then add a post to that discussion.
@@ -116,13 +117,8 @@ class mod_forum_mail_testcase extends advanced_testcase {
         $record->forum = $forum->id;
         $discussion = $generator->create_discussion($record);
 
-        $record = new stdClass();
-        $record->course = $forum->course;
-        $record->userid = $author->id;
-        $record->forum = $forum->id;
-        $record->discussion = $discussion->id;
-        $record->mailnow = 1;
-        $post = $generator->create_post($record);
+        // Retrieve the post which was created by create_discussion.
+        $post = $DB->get_record('forum_posts', array('discussion' => $discussion->id));
 
         return array($discussion, $post);
     }
index 4da9931..9e658dc 100644 (file)
@@ -52,6 +52,18 @@ $capabilities = array(
         )
     ),
 
+    // Grade essay questions.
+    'mod/lesson:grade' => array(
+        'riskbitmask' => RISK_SPAM,
+        'captype' => 'write',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        )
+    ),
+
     'mod/lesson:manage' => array(
 
         'captype' => 'write',
index 3026e20..11e58a4 100644 (file)
@@ -38,7 +38,7 @@ $lesson = new lesson($dblesson);
 
 require_login($course, false, $cm);
 $context = context_module::instance($cm->id);
-require_capability('mod/lesson:edit', $context);
+require_capability('mod/lesson:grade', $context);
 
 $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$id));
 if ($mode !== 'display') {
index 594554f..b0232f9 100644 (file)
@@ -220,6 +220,7 @@ $string['leftduringtimedsession'] = 'You have left during a timed lesson.';
 $string['leftduringtimed'] = 'You have left during a timed lesson.<br />Please click on Continue to restart the lesson.';
 $string['leftduringtimednoretake'] = 'You have left during a timed lesson and you are<br />not allowed to retake or continue the lesson.';
 $string['lesson:addinstance'] = 'Add a new lesson';
+$string['lesson:grade'] = 'Grade lesson essay questions';
 $string['lessonattempted'] = 'Lesson attempted';
 $string['lessonclosed'] = 'This lesson closed on {$a}.';
 $string['lessoncloses'] = 'Lesson closes';
index fe2923d..7bab80f 100644 (file)
@@ -773,12 +773,10 @@ function lesson_supports($feature) {
 function lesson_extend_settings_navigation($settings, $lessonnode) {
     global $PAGE, $DB;
 
-    $canedit = has_capability('mod/lesson:edit', $PAGE->cm->context);
-
     $url = new moodle_url('/mod/lesson/view.php', array('id'=>$PAGE->cm->id));
     $lessonnode->add(get_string('preview', 'lesson'), $url);
 
-    if ($canedit) {
+    if (has_capability('mod/lesson:edit', $PAGE->cm->context)) {
         $url = new moodle_url('/mod/lesson/edit.php', array('id'=>$PAGE->cm->id));
         $lessonnode->add(get_string('edit', 'lesson'), $url);
     }
@@ -791,7 +789,7 @@ function lesson_extend_settings_navigation($settings, $lessonnode) {
         $reportsnode->add(get_string('detailedstats', 'lesson'), $url);
     }
 
-    if ($canedit) {
+    if (has_capability('mod/lesson:grade', $PAGE->cm->context)) {
         $url = new moodle_url('/mod/lesson/essay.php', array('id'=>$PAGE->cm->id));
         $lessonnode->add(get_string('manualgrading', 'lesson'), $url);
     }
index f804279..89263af 100644 (file)
@@ -51,7 +51,7 @@ $attemptscount = $DB->count_records('lesson_grades', array('lessonid'=>$lesson->
 $row[] = new tabobject('view', "$CFG->wwwroot/mod/lesson/view.php?id=$cm->id", get_string('preview', 'lesson'), get_string('previewlesson', 'lesson', format_string($lesson->name)));
 $row[] = new tabobject('edit', "$CFG->wwwroot/mod/lesson/edit.php?id=$cm->id", get_string('edit', 'lesson'), get_string('edita', 'moodle', format_string($lesson->name)));
 $row[] = new tabobject('reports', "$CFG->wwwroot/mod/lesson/report.php?id=$cm->id", get_string('reports', 'lesson'), get_string('viewreports2', 'lesson', $attemptscount));
-if (has_capability('mod/lesson:edit', $context)) {
+if (has_capability('mod/lesson:grade', $context)) {
     $row[] = new tabobject('essay', "$CFG->wwwroot/mod/lesson/essay.php?id=$cm->id", get_string('manualgrading', 'lesson'));
 }
 if ($lesson->highscores) {
diff --git a/mod/lesson/tests/behat/teacher_grade_essays.feature b/mod/lesson/tests/behat/teacher_grade_essays.feature
new file mode 100644 (file)
index 0000000..e6068b5
--- /dev/null
@@ -0,0 +1,57 @@
+@mod @mod_lesson
+Feature: In a lesson activity, a non editing teacher can grade essay questions
+  As a non editing teacher
+  I need to grade student answers to essay questions in lesson
+
+  @javascript
+  Scenario: non editing teacher grade essay questions
+    Given the following "users" exist:
+      | username | firstname | lastname | email |
+      | teacher1 | Teacher | 1 | teacher1@asd.com |
+      | teacher2 | Teacher | 2 | teacher2@asd.com |
+      | student1 | Student | 1 | student1@asd.com |
+    And the following "courses" exist:
+      | fullname | shortname | category |
+      | Course 1 | C1 | 0 |
+    And the following "course enrolments" exist:
+      | user | course | role |
+      | teacher1 | C1 | editingteacher |
+      | teacher2 | C1 | teacher |
+      | student1 | C1 | student |
+    And I log in as "teacher1"
+    And I am on homepage
+    And I follow "Course 1"
+    And I turn editing mode on
+    And I add a "Lesson" to section "1" and I fill the form with:
+      | Name | Test lesson name |
+      | Description | Test lesson description |
+    And I follow "Test lesson name"
+    And I follow "Add a question page"
+    And I set the field "Select a question type" to "Essay"
+    And I press "Add a question page"
+    And I set the following fields to these values:
+      | Page title | Essay question |
+      | Page contents | <p>Please write a story about a frog.</p> |
+    And I press "Save page"
+    And I log out
+    And I log in as "student1"
+    And I follow "Course 1"
+    And I follow "Test lesson name"
+    And I set the field "Your answer" to "<p>Once upon a time there was a little green frog."
+    And I press "Submit"
+    And I log out
+    When I log in as "teacher2"
+    And I follow "Course 1"
+    And I follow "Test lesson name"
+    Then I should see "Grade essays"
+    And I follow "Grade essays"
+    And I should see "Student 1"
+    And I should see "Essay question"
+    And I follow "Essay question"
+    And I should see "Student 1's response"
+    And I should see "Once upon a time there was a little green frog."
+    And I set the following fields to these values:
+      | Your comments | Well done. |
+      | Essay score | 1 |
+    And I press "Save changes"
+    And I should see "Changes saved"
index 1101874..9c2e2b0 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2014100600;       // The current module version (Date: YYYYMMDDXX)
+$plugin->version   = 2014101600;       // The current module version (Date: YYYYMMDDXX)
 $plugin->requires  = 2014050800;    // Requires this Moodle version
 $plugin->component = 'mod_lesson'; // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 0;
index 232d410..671728e 100644 (file)
@@ -32,31 +32,26 @@ defined('MOODLE_INTERNAL') || die();
 function mod_lti_upgrade_custom_separator() {
     global $DB;
 
-    if ($DB->replace_all_text_supported()) {
-
-        // Initialise parameter array.
-        $params = array('semicolon' => ';', 'likecr' => "%\r%", 'likelf' => "%\n%", 'lf' => "\n");
-
-        // Initialise NOT LIKE clauses to check for CR and LF characters.
-        $notlikecr = $DB->sql_like('value', ':likecr', true, true, true);
-        $notlikelf = $DB->sql_like('value', ':likelf', true, true, true);
-
-        // Update any instances in the lti_types_config table.
-        $sql = 'UPDATE {lti_types_config} ' .
-               'SET value = REPLACE(value, :semicolon, :lf) ' .
-               'WHERE (name = \'customparameters\') AND (' . $notlikecr . ') AND (' . $notlikelf . ')';
-        $DB->execute($sql, $params);
-
-        // Initialise NOT LIKE clauses to check for CR and LF characters.
-        $notlikecr = $DB->sql_like('instructorcustomparameters', ':likecr', true, true, true);
-        $notlikelf = $DB->sql_like('instructorcustomparameters', ':likelf', true, true, true);
-
-        // Update any instances in the lti table.
-        $sql = 'UPDATE {lti} ' .
-               'SET instructorcustomparameters = REPLACE(instructorcustomparameters, :semicolon, :lf) ' .
-               'WHERE (instructorcustomparameters IS NOT NULL) AND (' . $notlikecr . ') AND (' . $notlikelf . ')';
-        $DB->execute($sql, $params);
-
-    }
-
+    // Initialise parameter array.
+    $params = array('semicolon' => ';', 'likecr' => "%\r%", 'likelf' => "%\n%", 'lf' => "\n");
+
+    // Initialise NOT LIKE clauses to check for CR and LF characters.
+    $notlikecr = $DB->sql_like('value', ':likecr', true, true, true);
+    $notlikelf = $DB->sql_like('value', ':likelf', true, true, true);
+
+    // Update any instances in the lti_types_config table.
+    $sql = 'UPDATE {lti_types_config} ' .
+           'SET value = REPLACE(value, :semicolon, :lf) ' .
+           'WHERE (name = \'customparameters\') AND (' . $notlikecr . ') AND (' . $notlikelf . ')';
+    $DB->execute($sql, $params);
+
+    // Initialise NOT LIKE clauses to check for CR and LF characters.
+    $notlikecr = $DB->sql_like('instructorcustomparameters', ':likecr', true, true, true);
+    $notlikelf = $DB->sql_like('instructorcustomparameters', ':likelf', true, true, true);
+
+    // Update any instances in the lti table.
+    $sql = 'UPDATE {lti} ' .
+           'SET instructorcustomparameters = REPLACE(instructorcustomparameters, :semicolon, :lf) ' .
+           'WHERE (instructorcustomparameters IS NOT NULL) AND (' . $notlikecr . ') AND (' . $notlikelf . ')';
+    $DB->execute($sql, $params);
 }
index 593589c..7e39f2e 100644 (file)
@@ -52,6 +52,7 @@ $url->param('mode', $mode);
 $PAGE->set_url($url);
 
 require_login($course, false, $cm);
+$PAGE->set_pagelayout('report');
 
 require_capability('mod/scorm:viewreport', $contextmodule);
 
index 61910e8..1fbe2aa 100644 (file)
@@ -867,6 +867,7 @@ ORDER BY
     public function update_question_attempt(question_attempt $qa) {
         $record = new stdClass();
         $record->id = $qa->get_database_id();
+        $record->variant = $qa->get_variant();
         $record->maxmark = $qa->get_max_mark();
         $record->minfraction = $qa->get_min_fraction();
         $record->maxfraction = $qa->get_max_fraction();
@@ -1076,6 +1077,20 @@ ORDER BY
      * @param number $newmaxmark the new max mark to set.
      */
     public function set_max_mark_in_attempts(qubaid_condition $qubaids, $slot, $newmaxmark) {
+        if ($this->db->get_dbfamily() == 'mysql') {
+            // MySQL's query optimiser completely fails to cope with the
+            // set_field_select call below, so we have to give it a clue. See MDL-32616.
+            // TODO MDL-29589 encapsulate this MySQL-specific code with a $DB method.
+            $this->db->execute("
+                    UPDATE " . $qubaids->from_question_attempts('qa') . "
+                       SET qa.maxmark = :newmaxmark
+                     WHERE " . $qubaids->where() . "
+                       AND slot = :slot
+                    ", $qubaids->from_where_params() + array('newmaxmark' => $newmaxmark, 'slot' => $slot));
+            return;
+        }
+
+        // Normal databases.
         $this->db->set_field_select('question_attempts', 'maxmark', $newmaxmark,
                 "questionusageid {$qubaids->usage_id_in()} AND slot = :slot",
                 $qubaids->usage_id_in_params() + array('slot' => $slot));
index 3d1c55b..8a59289 100644 (file)
@@ -1178,6 +1178,9 @@ class question_attempt {
             if ($pendingstep->response_summary_changed()) {
                 $this->responsesummary = $pendingstep->get_new_response_summary();
             }
+            if ($pendingstep->variant_number_changed()) {
+                $this->variant = $pendingstep->get_new_variant_number();
+            }
         }
     }
 
index e18f997..101a37f 100644 (file)
@@ -432,15 +432,23 @@ class question_attempt_step {
 
 
 /**
- * A subclass with a bit of additional funcitonality, for pending steps.
+ * A subclass of {@link question_attempt_step} used when processing a new submission.
+ *
+ * When we are processing some new submitted data, which may or may not lead to
+ * a new step being added to the {@link question_usage_by_activity} we create an
+ * instance of this class. which is then passed to the question behaviour and question
+ * type for processing. At the end of processing we then may, or may not, keep it.
  *
  * @copyright  2010 The Open University
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class question_attempt_pending_step extends question_attempt_step {
-    /** @var string . */
+    /** @var string the new response summary, if there is one. */
     protected $newresponsesummary = null;
 
+    /** @var int the new variant number, if there is one. */
+    protected $newvariant = null;
+
     /**
      * If as a result of processing this step, the response summary for the
      * question attempt should changed, you should call this method to set the
@@ -451,15 +459,48 @@ class question_attempt_pending_step extends question_attempt_step {
         $this->newresponsesummary = $responsesummary;
     }
 
-    /** @return string the new response summary, if any. */
+    /**
+     * Get the new response summary, if there is one.
+     * @return string the new response summary, or null if it has not changed.
+     */
     public function get_new_response_summary() {
         return $this->newresponsesummary;
     }
 
-    /** @return string whether this step changes the response summary. */
+    /**
+     * Whether this processing this step has changed the response summary.
+     * @return bool true if there is a new response summary.
+     */
     public function response_summary_changed() {
         return !is_null($this->newresponsesummary);
     }
+
+    /**
+     * If as a result of processing this step, you identify that this variant of the
+     * question is acutally identical to the another one, you may change the
+     * variant number recorded, in order to give better statistics. For an example
+     * see qbehaviour_opaque.
+     * @param int $variant the new variant number.
+     */
+    public function set_new_variant_number($variant) {
+        $this->newvariant = $variant;
+    }
+
+    /**
+     * Get the new variant number, if there is one.
+     * @return int the new variant number, or null if it has not changed.
+     */
+    public function get_new_variant_number() {
+        return $this->newvariant;
+    }
+
+    /**
+     * Whether this processing this step has changed the variant number.
+     * @return bool true if there is a new variant number.
+     */
+    public function variant_number_changed() {
+        return !is_null($this->newvariant);
+    }
 }
 
 
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * This file contains tests for the autosave code in the question_usage class.
+ * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting.
  *
- * @package    moodlecore
- * @subpackage questionengine
- * @copyright  2013 The Open University
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   core_question
+ * @category  test
+ * @copyright 2013 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 
@@ -32,17 +32,12 @@ require_once(dirname(__FILE__) . '/helpers.php');
 
 
 /**
- * Unit tests for the autosave parts of the {@link question_usage} class.
- *
- * Note that many of the methods used when attempting questions, like
- * load_questions_usage_by_activity, insert_question_*, delete_steps are
- * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not
- * re-test them here.
+ * Unit tests for the parts of {@link question_engine_data_mapper} related to reporting.
  *
  * @copyright 2013 The Open University
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_base {
+class question_engine_data_mapper_reporting_testcase extends qbehaviour_walkthrough_test_base {
 
     /** @var question_engine_data_mapper */
     protected $dm;
@@ -132,6 +127,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
         $this->dotest_question_attempt_latest_state_view();
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_load_questions_usages_latest_steps() {
         $rawstates = $this->dm->load_questions_usages_latest_steps($this->bothusages, $this->allslots,
                 'qa.id AS questionattemptid, qa.questionusageid, qa.slot, ' .
@@ -178,6 +176,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
         ), $state);
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_load_questions_usages_question_state_summary() {
         $summary = $this->dm->load_questions_usages_question_state_summary(
                 $this->bothusages, $this->allslots);
@@ -206,6 +207,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
                 ));
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_load_questions_usages_where_question_in_state() {
         $this->assertEquals(
                 array(array($this->usageids[0], $this->usageids[1]), 2),
@@ -223,6 +227,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
                 'needsgrading', $this->allslots[1], null, 'questionusageid'));
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_load_average_marks() {
         $averages = $this->dm->load_average_marks($this->bothusages);
 
@@ -240,6 +247,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
         ), $averages);
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_sum_usage_marks_subquery() {
         global $DB;
 
@@ -253,6 +263,9 @@ class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_b
         $this->assertEquals(0, $totals[$this->usageids[1]]);
     }
 
+    /**
+     * This test is executed by {@link test_reporting_queries()}.
+     */
     protected function dotest_question_attempt_latest_state_view() {
         global $DB;
 
diff --git a/question/engine/tests/datalib_test.php b/question/engine/tests/datalib_test.php
new file mode 100644 (file)
index 0000000..9279626
--- /dev/null
@@ -0,0 +1,129 @@
+<?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/>.
+
+/**
+ * Unit tests for parts of {@link question_engine_data_mapper}.
+ *
+ * @package   core_question
+ * @category  test
+ * @copyright 2014 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(dirname(__FILE__) . '/../lib.php');
+require_once(dirname(__FILE__) . '/helpers.php');
+
+
+/**
+ * Unit tests for parts of {@link question_engine_data_mapper}.
+ *
+ * Note that many of the methods used when attempting questions, like
+ * load_questions_usage_by_activity, insert_question_*, delete_steps are
+ * tested elsewhere, e.g. by {@link question_usage_autosave_test}. We do not
+ * re-test them here.
+ *
+ * @copyright 2014 The Open University
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class question_engine_data_mapper_testcase extends qbehaviour_walkthrough_test_base {
+
+    /**
+     * We create two usages, each with two questions, a short-answer marked
+     * out of 5, and and essay marked out of 10. We just start these attempts.
+     *
+     * Then we change the max mark for the short-answer question in one of the
+     * usages to 20, using a qubaid_list, and verify.
+     *
+     * Then we change the max mark for the essay question in the other
+     * usage to 2, using a qubaid_join, and verify.
+     */
+    public function test_set_max_mark_in_attempts() {
+
+        // Set up some things the tests will need.
+        $this->resetAfterTest();
+        $dm = new question_engine_data_mapper();
+
+        // Create the questions.
+        $generator = $this->getDataGenerator()->get_plugin_generator('core_question');
+        $cat = $generator->create_question_category();
+        $sa = $generator->create_question('shortanswer', null,
+                array('category' => $cat->id));
+        $essay = $generator->create_question('essay', null,
+                array('category' => $cat->id));
+
+        // Create the first usage.
+        $q = question_bank::load_question($sa->id);
+        $this->start_attempt_at_question($q, 'interactive', 5);
+
+        $q = question_bank::load_question($essay->id);
+        $this->start_attempt_at_question($q, 'interactive', 10);
+
+        $this->finish();
+        $this->save_quba();
+        $usage1id = $this->quba->get_id();
+
+        // Create the second usage.
+        $this->quba = question_engine::make_questions_usage_by_activity('unit_test',
+                context_system::instance());
+
+        $q = question_bank::load_question($sa->id);
+        $this->start_attempt_at_question($q, 'interactive', 5);
+        $this->process_submission(array('answer' => 'fish'));
+
+        $q = question_bank::load_question($essay->id);
+        $this->start_attempt_at_question($q, 'interactive', 10);
+
+        $this->finish();
+        $this->save_quba();
+        $usage2id = $this->quba->get_id();
+
+        // Test set_max_mark_in_attempts with a qubaid_list.
+        $usagestoupdate = new qubaid_list(array($usage1id));
+        $dm->set_max_mark_in_attempts($usagestoupdate, 1, 20.0);
+        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
+        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
+        $this->assertEquals(20, $quba1->get_question_max_mark(1));
+        $this->assertEquals(10, $quba1->get_question_max_mark(2));
+        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
+        $this->assertEquals(10, $quba2->get_question_max_mark(2));
+
+        // Test set_max_mark_in_attempts with a qubaid_join.
+        $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
+                'qu.id = :usageid', array('usageid' => $usage2id));
+        $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
+        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
+        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
+        $this->assertEquals(20, $quba1->get_question_max_mark(1));
+        $this->assertEquals(10, $quba1->get_question_max_mark(2));
+        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
+        $this->assertEquals( 2, $quba2->get_question_max_mark(2));
+
+        // Test the nothing to do case.
+        $usagestoupdate = new qubaid_join('{question_usages} qu', 'qu.id',
+                'qu.id = :usageid', array('usageid' => -1));
+        $dm->set_max_mark_in_attempts($usagestoupdate, 2, 2.0);
+        $quba1 = question_engine::load_questions_usage_by_activity($usage1id);
+        $quba2 = question_engine::load_questions_usage_by_activity($usage2id);
+        $this->assertEquals(20, $quba1->get_question_max_mark(1));
+        $this->assertEquals(10, $quba1->get_question_max_mark(2));
+        $this->assertEquals( 5, $quba2->get_question_max_mark(1));
+        $this->assertEquals( 2, $quba2->get_question_max_mark(2));
+    }
+}
index 256ebe5..7c70a6a 100644 (file)
@@ -244,30 +244,7 @@ class question_type {
     public function display_question_editing_page($mform, $question, $wizardnow) {
         global $OUTPUT;
         $heading = $this->get_heading(empty($question->id));
-
         echo $OUTPUT->heading_with_help($heading, 'pluginname', $this->plugin_name());
-
-        $permissionstrs = array();
-        if (!empty($question->id)) {
-            if ($question->formoptions->canedit) {
-                $permissionstrs[] = get_string('permissionedit', 'question');
-            }
-            if ($question->formoptions->canmove) {
-                $permissionstrs[] = get_string('permissionmove', 'question');
-            }
-            if ($question->formoptions->cansaveasnew) {
-                $permissionstrs[] = get_string('permissionsaveasnew', 'question');
-            }
-        }
-        if (count($permissionstrs)) {
-            echo $OUTPUT->heading(get_string('permissionto', 'question'), 3);
-            $html = '<ul>';
-            foreach ($permissionstrs as $permissionstr) {
-                $html .= '<li>'.$permissionstr.'</li>';
-            }
-            $html .= '</ul>';
-            echo $OUTPUT->box($html, 'boxwidthnarrow boxaligncenter generalbox');
-        }
         $mform->display();
     }
 
index 3d1f0b7..107e647 100644 (file)
@@ -115,13 +115,6 @@ class qtype_random extends question_type {
         $this->manualqtypes = implode(',', $manualqtypes);
     }
 
-    public function display_question_editing_page($mform, $question, $wizardnow) {
-        global $OUTPUT;
-        $heading = $this->get_heading(empty($question->id));
-        echo $OUTPUT->heading_with_help($heading, 'pluginname', $this->plugin_name());
-        $mform->display();
-    }
-
     public function get_question_options($question) {
         return true;
     }
index c097b67..2d717bb 100644 (file)
@@ -37,7 +37,7 @@ $timefrom   = optional_param('timefrom', 0, PARAM_INT); // how far back to look.
 $action     = optional_param('action', '', PARAM_ALPHA);
 $page       = optional_param('page', 0, PARAM_INT);                     // which page to show
 $perpage    = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT);  // how many per page
-$currentgroup = optional_param('group', 0, PARAM_INT); // Get the active group.
+$currentgroup = optional_param('group', null, PARAM_INT); // Get the active group.
 
 $url = new moodle_url('/report/participation/index.php', array('id'=>$id));
 if ($roleid !== 0) $url->param('roleid');
@@ -128,8 +128,15 @@ if ($onlyuselegacyreader) {
 // Print first controls.
 report_participation_print_filter_form($course, $timefrom, $minlog, $action, $roleid, $instanceid);
 
-$baseurl =  $CFG->wwwroot.'/report/participation/index.php?id='.$course->id.'&amp;roleid='
-    .$roleid.'&amp;instanceid='.$instanceid.'&amp;timefrom='.$timefrom.'&amp;action='.$action.'&amp;perpage='.$perpage;
+$baseurl = new moodle_url('/report/participation/index.php', array(
+    'id' => $course->id,
+    'roleid' => $roleid,
+    'instanceid' => $instanceid,
+    'timefrom' => $timefrom,
+    'action' => $action,
+    'perpage' => $perpage,
+    'group' => $currentgroup
+));
 $select = groups_allgroups_course_menu($course, $baseurl, true, $currentgroup);
 
 // User cannot see any group.
@@ -341,10 +348,15 @@ if (!empty($instanceid) && !empty($roleid)) {
     $table->print_html();
 
     if ($perpage == SHOW_ALL_PAGE_SIZE) {
-        echo '<div id="showall"><a href="'.$baseurl.'&amp;perpage='.DEFAULT_PAGE_SIZE.'">'.get_string('showperpage', '', DEFAULT_PAGE_SIZE).'</a></div>'."\n";
-    }
-    else if ($matchcount > 0 && $perpage < $matchcount) {
-        echo '<div id="showall"><a href="'.$baseurl.'&amp;perpage='.SHOW_ALL_PAGE_SIZE.'">'.get_string('showall', '', $matchcount).'</a></div>'."\n";
+        $perpageurl = new moodle_url($baseurl, array('perpage' => DEFAULT_PAGE_SIZE));
+        echo html_writer::start_div('', array('id' => 'showall'));
+        echo html_writer::link($perpageurl, get_string('showperpage', '', DEFAULT_PAGE_SIZE));
+        echo html_writer::end_div();
+    } else if ($matchcount > 0 && $perpage < $matchcount) {
+        $perpageurl = new moodle_url($baseurl, array('perpage' => SHOW_ALL_PAGE_SIZE));
+        echo html_writer::start_div('', array('id' => 'showall'));
+        echo html_writer::link($perpageurl, get_string('showall', '', $matchcount));
+        echo html_writer::end_div();
     }
 
     echo '<div class="selectbuttons">';
index 9f7540d..60619b7 100644 (file)
@@ -81,7 +81,8 @@
 }
 .ical-link:hover,
 .ical-link:active,
-.ical-link:focus {
+.ical-link:focus,
+.ical-link:visited {
     color: #fff;
     text-decoration: none;
 }
index 209dcd8..a7a1bf6 100644 (file)
@@ -1993,7 +1993,7 @@ img#persona_signin { cursor: pointer; }
 .moodle-actionmenu[data-enhanced].show .menu {position: absolute;text-align:left;z-index: 1000;display: block;background-color: #fff;border: 1px solid #ccc;-webkit-border-radius: 5px;-moz-border-radius: 5px;border-radius: 5px;-webkit-box-shadow: 5px 5px 20px 0 #666666;-moz-box-shadow: 5px 5px 20px 0 #666666;box-shadow: 5px 5px 20px 0 #666666;}
 .moodle-actionmenu[data-enhanced].show .menu a {
     display: block;
-    padding: 4px 1em 4px 28px;
+    padding: 2px 1em 2px 28px;
     color: #333333;
 }
 .moodle-actionmenu[data-enhanced].show .menu a > img {
@@ -2002,6 +2002,11 @@ img#persona_signin { cursor: pointer; }
     width: 12px;
     height: 12px;
 }
+.moodle-actionmenu[data-enhanced].show .menu a > img,
+.moodle-actionmenu[data-enhanced].show .menu a > span {
+    display: inline-block;
+    vertical-align: middle;
+}
 .moodle-actionmenu[data-enhanced].show .menu a:hover {color: #ffffff;background-color: #0088cc;}
 .moodle-actionmenu[data-enhanced].show .menu a:first-child {-webkit-border-top-right-radius: 4px;border-top-right-radius: 4px;-webkit-border-top-left-radius: 4px;border-top-left-radius: 4px;-moz-border-radius-topright: 4px;-moz-border-radius-topleft: 4px;}
 .moodle-actionmenu[data-enhanced].show .menu a:last-child {-webkit-border-bottom-right-radius: 4px;border-bottom-right-radius: 4px;-webkit-border-bottom-left-radius: 4px;border-bottom-left-radius: 4px;-moz-border-radius-bottomright: 4px;-moz-border-radius-bottomleft: 4px;}
@@ -2011,8 +2016,10 @@ img#persona_signin { cursor: pointer; }
 .block .moodle-actionmenu {text-align: right;}
 
 .dir-rtl .block .moodle-actionmenu {text-align: right;}
+.dir-rtl .moodle-actionmenu[data-enhanced].show .menu a { display: block; padding: 2px 28px 2px 1em; }
 .dir-rtl .moodle-actionmenu[data-enhanced].show .menu {text-align: right;right: auto;left: 0;}
-.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .iconsmall {margin-right: 0;margin-left: 8px;}
+.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .iconsmall,
+.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .smallicon { margin-right: -24px; margin-left: 4px; }
 
 .jsenabled .moodle-actionmenu[data-enhanced] .menu.align-tl-bl {top: 100%;left: 0;margin-top: 4px;}
 .jsenabled .moodle-actionmenu[data-enhanced] .menu.align-tr-bl {top: 100%;right: 100%;}
index 81af932..d3c8aa4 100644 (file)
@@ -414,6 +414,8 @@ input.titleeditor { vertical-align: text-bottom; }
 #course-category-listings li[data-selected='1']:last-of-type > div {border-bottom-color:#e1e1e8;}
 #course-category-listings > div > div > ul.ml > li:first-child > div {border-top:0;}
 
+#course-category-listings .moodle-actionmenu.show .menu li { line-height: 20px; }
+
 #course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced]) li {line-height:normal;}
 #course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced]) > .menubar li,
 #course-category-listings .listing-actions .moodle-actionmenu:not([data-enhanced]) > .menubar a,
@@ -434,10 +436,9 @@ input.titleeditor { vertical-align: text-bottom; }
 }
 
 #course-category-listings .item-actions {margin-right:1em;display:inline-block;display:initial;}
-#course-category-listings .item-actions img {margin: 0 4px;}
-#course-category-listings .item-actions .menubar img {vertical-align:inherit;}
+#course-category-listings .item-actions .menubar img,
+#course-category-listings .item-actions > a img { margin: 0 4px; vertical-align:inherit; }
 #course-category-listings .item-actions .menu img {max-width:none;width:12px;}
-#course-category-listings .item-actions.show .menu a {padding:4px 1em 4px 4px;}
 
 #course-category-listings li .tree-icon {margin-left:0;}
 #course-category-listings li li .tree-icon {margin-left:1em;}
@@ -465,6 +466,7 @@ input.titleeditor { vertical-align: text-bottom; }
 #category-listing .course-count {color:#a1a1a8;margin-right:2em;min-width:3.5em;display:inline-block;}
 #category-listing .listitem.collapsed > ul.ml {display: none;}
 #category-listing .course-count .smallicon {width:0.8em;height:0.8em;margin:0 0.3em;}
+.dir-rtl #category-listing .course-count { margin-left: 2em; margin-right: 0; }
 
 #category-listing .listitem > div > .ba-checkbox {vertical-align:middle;width:2.2em;text-align:center;margin:-1px 0;padding-top:2px;margin-right:0.5em;}
 .dir-rtl #category-listing .listitem > div > .ba-checkbox {margin-left:0.5em;margin-right:0.5em;}
index aa77870..7a0a2bc 100644 (file)
 .path-grade-report-user .user-grade.generaltable tbody tr:hover > th {
     background-color: #f5f5f5;
 }
+
+.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell,
+.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell,
+.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell {
+    padding-left: 35px;
+}
+
+.dir-rtl.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell,
+.dir-rtl.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell,
+.dir-rtl.has_dock.path-grade-report-grader .gradeparent .sideonly.floating > .cell {
+    padding-left: 5px;
+    padding-right: 35px;
+}
+
+.content-only.path-grade-report-grader .gradeparent table {
+    margin-left: 30px;
+}
+
+.dir-rtl.content-only.path-grade-report-grader .gradeparent table {
+    margin-left: 0;
+    margin-right: 30px;
+}
+
index 1a4de5b..7b6c009 100644 (file)
@@ -75,7 +75,6 @@ echo $OUTPUT->doctype() ?>
             <div class="breadcrumb-button"><?php echo $OUTPUT->page_heading_button(); ?></div>
         </div>
         <?php echo $OUTPUT->page_heading(); ?>
-        <?php echo $OUTPUT->user_menu(); ?>
         <div id="course-header">
             <?php echo $OUTPUT->course_header(); ?>
         </div>
index 615c172..a100f4a 100644 (file)
 }
 .ical-link:hover,
 .ical-link:active,
-.ical-link:focus {
+.ical-link:focus,
+.ical-link:visited {
     color: #fff;
     text-decoration: none;
 }
index 20177e1..f0ccfbc 100644 (file)
@@ -2066,7 +2066,8 @@ img#persona_signin {
             padding-left: 4px;
             padding-right: 4px;
 
-            .iconsmall {
+            .iconsmall,
+            .smallicon {
                 margin: 4px 4px 4px 0px;
                 padding: 8px 4px 0px 2px;
                 vertical-align: text-bottom;
@@ -2143,8 +2144,10 @@ img#persona_signin {
         img {
             vertical-align: middle;
         }
-        .iconsmall {
+        .iconsmall,
+        .smallicon {
             margin: 4px 4px 4px -24px;
+            padding: 4px;
         }
         > li {
             display:block;
@@ -2183,9 +2186,13 @@ img#persona_signin {
             text-align: right;
             left: 0;
             right: auto;
-            .iconsmall {
-                margin-right:0;
-                margin-left:8px;
+            a {
+                padding: 2px 28px 2px 1em;
+            }
+            .iconsmall,
+            .smallicon {
+                margin-right: -24px;
+                margin-left: 4px;
             }
 
             /** bottom left of button **/
index eb068b4..b8f31d1 100644 (file)
@@ -941,15 +941,16 @@ span.editinstructions {
         margin-right:1em;
         display:inline-block;
         display:initial;
-        img {
+        > a img,
+        .menubar img {
             margin: 0 4px;
             height:12px;
             padding: 0;
             vertical-align: inherit;
         }
         &.show .menu {
-            a {
-                padding:4px 1em 4px 4px;
+            li {
+                line-height: 20px;
             }
             img {
                 width: 12px;
@@ -1194,6 +1195,11 @@ span.editinstructions {
         }
         .course-count {
             margin-left:2rem;
+
+            .smallicon {
+                margin-left: 0px;
+                margin-right: 4px;
+            }
         }
         .bulk-action-checkbox {
             margin-left: -3px;
index 51e8b74..d5e964d 100644 (file)
         }
     }
 }
+
+.has_dock.path-grade-report-grader {
+    .gradeparent .sideonly.floating > .cell,
+    .gradeparent .sideonly.floating > .cell,
+    .gradeparent .sideonly.floating > .cell {
+        padding-left: 5 + (@dockWidth + (@dockTitleMargin * 2));
+    }
+
+    &.dir-rtl {
+        .gradeparent .sideonly.floating > .cell,
+        .gradeparent .sideonly.floating > .cell,
+        .gradeparent .sideonly.floating > .cell {
+            padding-left: 5px;
+            padding-right: 5 + (@dockWidth + (@dockTitleMargin * 2));
+        }
+    }
+}
+
+.content-only.path-grade-report-grader {
+    .gradeparent table {
+        margin-left: (@dockWidth + (@dockTitleMargin * 2));
+    }
+
+    &.dir-rtl {
+        .gradeparent table {
+            margin-left: 0;
+            margin-right: (@dockWidth + (@dockTitleMargin * 2));
+        }
+    }
+}
index 5e49376..4f33510 100644 (file)
@@ -1,4 +1,4 @@
-.layout-option-noheader #page-header,.layout-option-nonavbar #page-navbar,.layout-option-nofooter #page-footer,.layout-option-nocourseheader .course-content-header,.layout-option-nocoursefooter .course-content-footer{display:none}.empty-region-side-pre #block-region-side-pre,.empty-region-side-post #block-region-side-post,.jsenabled.docked-region-side-post #block-region-side-post,.jsenabled.docked-region-side-pre #block-region-side-pre{display:none}.content-only #region-main.span9,.empty-region-side-post #region-bs-main-and-pre.span9,.empty-region-side-pre #region-bs-main-and-post.span9,.empty-region-side-post #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-post #region-bs-main-and-pre.span9,.jsenabled.docked-region-side-post #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-pre #region-bs-main-and-post.span9{width:100%}.empty-region-side-pre #region-bs-main-and-pre.span9 #region-main,.jsenabled.docked-region-side-pre #region-bs-main-and-pre.span9 #region-main{float:none;width:100%}.empty-region-side-post.used-region-side-pre #region-main.span8,.jsenabled.docked-region-side-post.used-region-side-pre #region-main.span8{width:74.46808510638297%;*width:74.41489361702126%}.empty-region-side-post.used-region-side-pre #block-region-side-pre.span4,.jsenabled.docked-region-side-post.used-region-side-pre #block-region-side-pre.span4{width:23.404255319148934%;*width:23.351063829787233%}.empty-region-side-pre #region-bs-main-and-post.span9 #region-main.span8,.jsenabled.docked-region-side-pre #region-bs-main-and-post.span9 #region-main.span8{float:right}.dir-ltr,.mdl-left,.dir-rtl .mdl-right{text-align:left}.dir-rtl,.mdl-right,.dir-rtl .mdl-left{text-align:right}#add,#remove,.centerpara,.mdl-align{text-align:center}a.dimmed,a.dimmed:link,a.dimmed:visited,a.dimmed_text,a.dimmed_text:link,a.dimmed_text:visited,.dimmed_text,.dimmed_text a,.dimmed_text a:link,.dimmed_text a:visited,.usersuspended,.usersuspended a,.usersuspended a:link,.usersuspended a:visited,.dimmed_category,.dimmed_category a{color:#999}.activity.label .dimmed_text{opacity:.5;filter:alpha(opacity=50)}.unlist,.unlist li,.inline-list,.inline-list li,.block .list,.block .list li,.section li.activity,.section li.movehere,.tabtree li{padding:0;margin:0;list-style:none}.inline,.inline-list li{display:inline}.notifytiny{font-size:10.5px}.notifytiny li,.notifytiny td{font-size:100%}.red,.notifyproblem{color:#b94a48}.green,.notifysuccess{color:#468847}.highlight{background:#d9edf7}.reportlink{text-align:right}a.autolink.glossary:hover{cursor:help}.collapsibleregioncaption{white-space:nowrap}.collapsibleregioncaption img{vertical-align:middle}.jsenabled .hiddenifjs{display:none}.visibleifjs{display:none}.jsenabled .visibleifjs{display:inline}.jsenabled .collapsibleregion{overflow:hidden}.jsenabled .collapsed .collapsibleregioninner{visibility:hidden}.collapsible-actions{display:none;text-align:right}.dir-rtl .collapsible-actions{text-align:left}.jsenabled .collapsible-actions{display:block}.collapsible-actions .collapseexpand{padding-left:20px;background:url([[pix:t/collapsed]]) 2px center no-repeat}.dir-rtl .collapsible-actions .collapseexpand{padding-right:20px;padding-left:0;background:url([[pix:t/collapsed_rtl]]) right center no-repeat}.collapsible-actions .collapse-all,.dir-rtl .collapsible-actions .collapse-all{background-image:url([[pix:t/expanded]])}.yui-overlay .yui-widget-bd{position:relative;top:0;left:0;z-index:1;padding:2px 5px;color:#000;background-color:#ffee69;border:1px solid #a6982b;border-top-color:#d4c237}.clearer{display:block;height:1px;padding:0;margin:0;clear:both;background:transparent;border-width:0}.bold,.warning,.errorbox .title,.pagingbar .title,.pagingbar .thispage{font-weight:bold}img.resize{width:1em;height:1em}.block img.resize,.breadcrumb img.resize{width:.8em;height:.9em}img.icon{width:16px;height:16px;padding-right:6px;vertical-align:text-bottom}.dir-rtl img.icon{padding-right:0;padding-left:6px}img.iconsmall{width:12px;height:12px;margin-right:3px;vertical-align:middle}img.iconhelp,.helplink img{width:16px;height:16px;padding-left:3px;vertical-align:text-bottom}h1 img.iconhelp,h1 img.icon,h2 img.iconhelp,h2 img.icon,h3 img.iconhelp,h3 img.icon,h4 img.iconhelp,h4 img.icon,h5 img.iconhelp,h5 img.icon,h6 img.iconhelp,h6 img.icon{padding:4px;vertical-align:middle}.dir-rtl img.iconhelp,.dir-rtl .helplink img{padding-right:3px;padding-left:0}img.iconlarge{width:24px;height:24px;vertical-align:middle}img.iconsort{padding-left:.3em;margin-bottom:.15em;vertical-align:text-bottom}.dir-rtl img.iconsort{padding-right:.3em;padding-left:0}img.icontoggle{width:50px;height:17px;vertical-align:middle}img.iconkbhelp{width:49px;height:17px}img.icon-pre,.dir-rtl img.icon-post{padding-right:3px;padding-left:0}img.icon-post,.dir-rtl img.icon-pre{padding-right:0;padding-left:3px}.boxaligncenter{margin-right:auto;margin-left:auto}.boxalignright{margin-right:0;margin-left:auto}.boxalignleft{margin-right:auto;margin-left:0}.boxwidthnarrow{width:30%}.boxwidthnormal{width:50%}.boxwidthwide{width:80%}.headermain{font-weight:bold}#maincontent{display:block;height:1px;overflow:hidden}img.uihint{cursor:help}#addmembersform table{margin-right:auto;margin-left:auto}table.flexible .emptyrow{display:none}img.emoticon{width:15px;height:15px;vertical-align:middle}form.popupform,form.popupform div{display:inline}.arrow_button input{overflow:hidden}.action-icon img.smallicon{margin:0 .3em;vertical-align:text-bottom}.no-overflow{padding-bottom:1px;overflow:auto}.pagelayout-report .no-overflow{overflow:visible}.no-overflow>.generaltable{margin-bottom:0}.accesshide{position:absolute;left:-10000px;font-size:1em;font-weight:normal}.dir-rtl .accesshide{top:-30000px;left:auto}span.hide,div.hide{display:none}a.skip-block,a.skip{position:absolute;top:-1000em;font-size:.85em;text-decoration:none}a.skip-block:focus,a.skip-block:active,a.skip:focus,a.skip:active{position:static;display:block}.skip-block-to{display:block;height:1px;overflow:hidden}.addbloglink{text-align:center}.blog_entry .audience{padding-right:4px;text-align:right}.blog_entry .tags{margin-top:15px}.blog_entry .tags .action-icon img.smallicon{width:16px;height:16px}.blog_entry .content{margin-left:43px}#page-group-index #groupeditform{text-align:center}#doc-contents h1{margin:1em 0 0 0}#doc-contents ul{width:90%;padding:0;margin:0}#doc-contents ul li{list-style-type:none}.groupmanagementtable td{vertical-align:top}.groupmanagementtable #existingcell,.groupmanagementtable #potentialcell{width:42%}.groupmanagementtable #buttonscell{width:16%}.groupmanagementtable #buttonscell p.arrow_button input{width:auto;min-width:80%;margin:0 auto}.groupmanagementtable #removeselect_wrapper,.groupmanagementtable #addselect_wrapper{width:100%}.groupmanagementtable #removeselect_wrapper label,.groupmanagementtable #addselect_wrapper label{font-weight:normal}.dir-rtl .groupmanagementtable p{text-align:right}#group-usersummary{width:14em}.groupselector{display:inline-block;margin-top:3px;margin-bottom:3px}.groupselector label{display:inline-block}.loginbox{margin:15px;overflow:visible}.loginbox.twocolumns{margin:15px}.loginbox h2,.loginbox .subcontent{padding:10px;margin:5px;text-align:center}.loginbox .loginpanel .desc{padding:0;margin:0;margin-top:15px;margin-bottom:5px}.loginbox .signuppanel .subcontent{text-align:left}.dir-rtl .loginbox .signuppanel .subcontent{text-align:right}.loginbox .loginsub{margin-right:0;margin-left:0}.loginbox .guestsub,.loginbox .forgotsub,.loginbox .potentialidps{margin:5px 12%}.loginbox .potentialidps .potentialidplist{margin-left:40%}.loginbox .potentialidps .potentialidplist div{text-align:left}.loginbox .loginform{margin-top:1em;text-align:left}.loginbox .loginform .form-label{float:left;width:49%;text-align:right;white-space:nowrap}.loginbox .loginform .form-input{float:right;width:50%}.loginbox .loginform .form-input input{width:6em}.loginbox .signupform{margin-top:1em;text-align:center}.loginbox.twocolumns .loginpanel,.loginbox.twocolumns .signuppanel{display:block;float:left;width:48%;min-height:30px;padding:0;padding-bottom:2000px;margin:0;margin-bottom:-2000px;margin-left:2.76243%;border:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.dir-rtl .loginbox.twocolumns .loginpanel,.dir-rtl .loginbox.twocolumns .signuppanel{float:right}.loginbox .potentialidp .smallicon{margin:0 .3em;vertical-align:text-bottom}.notepost{margin-bottom:1em}.notepost .userpicture{float:left;margin-right:5px}.notepost .content,.notepost .footer{clear:both}.notesgroup{margin-left:20px}.path-my .coursebox .overview{margin:15px 30px 10px 30px}.path-my .coursebox .info{float:none;margin:0}.mod_introbox{padding:10px}table.mod_index{width:100%}.comment-ctrl{display:none;padding:0;margin:0;font-size:12px}.comment-ctrl h5{padding:5px;margin:0}.comment-area{max-width:400px;padding:5px}.comment-area textarea{width:100%;overflow:auto}.comment-area .fd{text-align:right}.comment-meta span{color:gray}.comment-link img{vertical-align:text-bottom}.comment-list{padding:0;margin:0;overflow:auto;font-size:11px;list-style:none}.comment-list li{position:relative;padding:.3em;margin:2px;margin-bottom:5px;clear:both;list-style:none}.comment-list li.first{display:none}.comment-paging{text-align:center}.comment-paging .pageno{padding:2px}.comment-paging .curpage{border:1px solid #CCC}.comment-message .picture{float:left;width:20px}.dir-rtl .comment-message .picture{float:right}.comment-message .text{padding:0;margin:0}.comment-message .text p{padding:0;margin:0 18px 0 0}.comment-delete{position:absolute;top:0;right:0;margin:.3em}.dir-rtl .comment-delete{position:absolute;right:auto;left:0;margin:.3em}.comment-report-selectall{display:none}.comment-link{display:none}.jsenabled .comment-link{display:block}.jsenabled .showcommentsnonjs{display:none}.jsenabled .comment-report-selectall{display:inline}.completion-expired{background:#f2dede}.completion-expected{font-size:10.5px}.completion-sortchoice,.completion-identifyfield{font-size:10.5px;vertical-align:bottom}.completion-progresscell{text-align:right}.completion-expired .completion-expected{font-weight:bold}#page-tag-coursetags_edit .coursetag_edit_centered{position:relative;width:600px;margin:20px auto}#page-tag-coursetags_edit .coursetag_edit_row{clear:both}#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_left{float:left;width:50%;text-align:right}#page-tag-coursetags_edit .coursetag_edit_row .coursetag_edit_right{margin-left:50%}#page-tag-coursetags_edit .coursetag_edit_input3{display:none}#page-tag-coursetags_more .coursetag_more_large{font-size:120%}#page-tag-coursetags_more .coursetag_more_small{font-size:80%}#page-tag-coursetags_more .coursetag_more_link{font-size:80%}#tag-description,#tag-blogs{width:100%}#tag-management-box{margin-bottom:10px;line-height:20px}#tag-user-table{width:100%;padding:3px;clear:both}#tag-user-table{*zoom:1}#tag-user-table:before,#tag-user-table:after{display:table;line-height:0;content:""}#tag-user-table:after{clear:both}img.user-image{width:100px;height:100px}#small-tag-cloud-box{width:300px;margin:0 auto}#big-tag-cloud-box{float:none;width:600px;margin:0 auto}ul#tag-cloud-list{padding:5px;margin:0;list-style:none}ul#tag-cloud-list li{display:inline;margin:0;list-style-type:none}#tag-search-box{margin:10px auto;text-align:center}#tag-search-results-container{width:100%;padding:0}#tag-search-results{display:block;float:left;width:60%;padding:0;margin:15px 20% 0 20%}#tag-search-results li{float:left;width:30%;padding-right:1%;padding-left:1%;line-height:20px;text-align:left;list-style:none}span.flagged-tag,span.flagged-tag a{color:#b94a48}table#tag-management-list{width:100%;text-align:left}table#tag-management-list td,table#tag-management-list th{padding:4px;text-align:left;vertical-align:middle}.tag-management-form{text-align:center}#relatedtags-autocomplete-container{width:100%;min-height:4.6em;margin-right:auto;margin-left:auto}#relatedtags-autocomplete{position:relative;display:block;width:60%;margin-right:auto;margin-left:auto}#relatedtags-autocomplete .yui-ac-content{position:absolute;left:20%;z-index:9050;width:420px;overflow:hidden;background:#fff;border:1px solid rgba(0,0,0,0.2)}#relatedtags-autocomplete .ysearchquery{position:absolute;right:10px;z-index:10;color:#808080}#relatedtags-autocomplete .yui-ac-shadow{position:absolute;z-index:9049;width:100%;margin:.3em;background:#a0a0a0}#relatedtags-autocomplete ul{width:100%;padding:0;margin:0;list-style-type:none}#relatedtags-autocomplete li{padding:0 5px;white-space:nowrap;cursor:default}#relatedtags-autocomplete li.yui-ac-highlight{color:#fff;background:#0070a8}h2.tag-heading,div#tag-description,div#tag-blogs,body.tag .managelink{padding:5px}.tag_cloud .s20{font-size:1.5em;font-weight:bold}.tag_cloud .s19{font-size:1.5em}.tag_cloud .s18{font-size:1.4em;font-weight:bold}.tag_cloud .s17{font-size:1.4em}.tag_cloud .s16{font-size:1.3em;font-weight:bold}.tag_cloud .s15{font-size:1.3em}.tag_cloud .s14{font-size:1.2em;font-weight:bold}.tag_cloud .s13{font-size:1.2em}.tag_cloud .s12,.tag_cloud .s11{font-size:1.1em;font-weight:bold}.tag_cloud .s10,.tag_cloud .s9{font-size:1.1em}.tag_cloud .s8,.tag_cloud .s7{font-size:1em;font-weight:bold}.tag_cloud .s6,.tag_cloud .s5{font-size:1em}.tag_cloud .s4,.tag_cloud .s3{font-size:.9em;font-weight:bold}.tag_cloud .s2,.tag_cloud .s1{font-size:.9em}.tag_cloud .s0{font-size:.8em}#webservice-doc-generator td{text-align:left;border:0 solid black}.smartselect{position:absolute}.smartselect .smartselect_mask{background-color:#fff}.smartselect ul{padding:0;margin:0}.smartselect ul li{list-style:none}.smartselect .smartselect_menu{margin-right:5px}.safari .smartselect .smartselect_menu{margin-left:2px}.smartselect .smartselect_menu,.smartselect .smartselect_submenu{display:none;background-color:#FFF;border:1px solid #000}.smartselect .smartselect_menu.visible,.smartselect .smartselect_submenu.visible{display:block}.smartselect .smartselect_menu_content ul li{position:relative;padding:2px 5px}.smartselect .smartselect_menu_content ul li a{color:#333;text-decoration:none}.smartselect .smartselect_menu_content ul li a.selectable{color:inherit}.smartselect .smartselect_submenuitem{background-image:url([[pix:moodle|t/collapsed]]);background-position:100%;background-repeat:no-repeat}.smartselect.spanningmenu .smartselect_submenu{position:absolute;top:-1px;left:100%}.smartselect.spanningmenu .smartselect_submenu a{padding-right:16px;white-space:nowrap}.smartselect.spanningmenu .smartselect_menu_content ul li a.selectable:hover{text-decoration:underline}.smartselect.compactmenu .smartselect_submenu{position:relative;z-index:1010;display:none;margin:2px -3px;margin-left:10px;border-width:0}.smartselect.compactmenu .smartselect_submenu.visible{display:block}.smartselect.compactmenu .smartselect_menu{z-index:1000;overflow:hidden}.smartselect.compactmenu .smartselect_submenu .smartselect_submenu{z-index:1020}.smartselect.compactmenu .smartselect_submenuitem:hover>.smartselect_menuitem_label{font-weight:bold}#page-admin-registration-register .registration_textfield{width:300px}.userenrolment{width:100%;border-collapse:collapse}.userenrolment tr{vertical-align:top}.userenrolment td{height:41px;padding:0}.userenrolment .subfield{margin-right:5px}.userenrolment .col_userdetails .subfield_picture{float:left}.userenrolment .col_lastseen{width:150px}.userenrolment .col_role{width:262px}.userenrolment .col_role .roles,.userenrolment .col_group .groups{margin-right:30px}.userenrolment .col_role .role,.userenrolment .col_group .group{float:left;padding:3px;margin:3px;white-space:nowrap}.userenrolment .col_role .role a,.userenrolment .col_group .group a{margin-left:3px;cursor:pointer}.userenrolment .col_role .addrole,.userenrolment .col_group .addgroup{float:right;padding:3px;margin:3px}.userenrolment .col_role .addrole>*:hover,.userenrolment .col_group .addgroup>*:hover{border-bottom:1px solid #666}.userenrolment .col_role .addrole img,.userenrolment .col_group .addgroup img{vertical-align:baseline}.dir-rtl .userenrolment .col_role .role{float:right}.userenrolment .hasAllRoles .col_role .addrole{display:none}.userenrolment .col_enrol .enrolment{float:left;padding:3px;margin:3px}.userenrolment .col_enrol .enrolment a{float:right;margin-left:3px}#page-enrol-users .enrol_user_buttons{float:right}#page-enrol-users .enrol_user_buttons .enrolusersbutton{display:inline}#page-enrol-users .enrol_user_buttons .enrolusersbutton div,#page-enrol-users .enrol_user_buttons .enrolusersbutton form{display:inline;margin-right:0}#page-enrol-users #filterform{display:inline-block;min-height:20px;padding:19px;padding:9px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-color:#e3e3e3;-webkit-border-radius:4px;-webkit-border-radius:3px;-moz-border-radius:4px;-moz-border-radius:3px;border-radius:4px;border-radius:3px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}#page-enrol-users #filterform blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}#page-enrol-users #filterform .fitem{display:inline-block;margin-right:.3em;line-height:40px;white-space:nowrap}#page-enrol-users #filterform .fitem label{display:inline;padding-right:.3em;line-height:20px}#page-enrol-users #filterform .fitem :before,#page-enrol-users #filterform .fitem:after{display:inline}#page-enrol-users #filterform div,#page-enrol-users #filterform fieldset{display:inline;float:none;width:auto;margin:0;clear:none}#page-enrol-users #filterform select,#page-enrol-users #filterform .ftext input{width:7em}#page-enrol-users #filterform input,#page-enrol-users #filterform select{margin-bottom:0}#page-enrol-users .user-enroller-panel .uep-search-results .user .details{width:237px}#page-enrol-users .user-enroller-panel .uep-search-results .cohort .details{width:237px}.dir-rtl#page-enrol-users .col_userdetails .subfield_picture{float:right}.dir-rtl#page-enrol-users .enrol_user_buttons{float:left}.dir-rtl#page-enrol-users .enrol_user_buttons .enrolusersbutton{margin-right:1em;margin-left:0}.dir-rtl#page-enrol-users .enrol_user_buttons .enrolusersbutton div{margin-left:0}.dir-rtl#page-enrol-users #filterform .fitem{margin-right:0;margin-left:.3em}.dir-rtl#page-enrol-users #filterform .fitem label{padding-right:0;padding-left:.3em}.dir-rtl .headermain{float:right}.dir-rtl .headermenu{float:left}.dir-rtl .loginbox .loginform .form-label{float:right;text-align:left}.dir-rtl .loginbox .loginform .form-input{margin-right:1%;text-align:right}.dir-rtl .yui3-menu-hidden{left:0}#page-admin-roles-define.dir-rtl #rolesform .felement{margin-right:180px}#page-message-edit.dir-rtl table.generaltable th.c0{text-align:right}.corelightbox{position:absolute;top:0;left:0;width:100%;height:100%;text-align:center;background-color:#CCC}.corelightbox img{position:fixed;top:50%;left:50%}.mod-indent-outer{display:table}.mod-indent{display:table-cell}.label .mod-indent{float:left;padding-top:20px}.mod-indent-1{width:30px}.mod-indent-2{width:60px}.mod-indent-3{width:90px}.mod-indent-4{width:120px}.mod-indent-5{width:150px}.mod-indent-6{width:180px}.mod-indent-7{width:210px}.mod-indent-8{width:240px}.mod-indent-9{width:270px}.mod-indent-10{width:300px}.mod-indent-11{width:330px}.mod-indent-12{width:360px}.mod-indent-13{width:390px}.mod-indent-14{width:420px}.mod-indent-15{width:450px}.mod-indent-16{width:480px}.mod-indent-huge{width:480px}.resourcecontent .mediaplugin_mp3 object{width:600px;height:25px}.resourcecontent audio.mediaplugin_html5audio{width:600px}.resourceimage{max-width:100%}.mediaplugin_mp3 object{width:300px;height:15px}audio.mediaplugin_html5audio{width:300px}.core_media_preview.pagelayout-embedded #content{padding:0}.core_media_preview.pagelayout-embedded #maincontent{height:0}body#page-lib-editor-tinymce-plugins-moodlemedia-preview{min-width:0;padding:0;margin:0;background:0}.dir-rtl .ygtvtn,.dir-rtl .ygtvtm,.dir-rtl .ygtvtmh,.dir-rtl .ygtvtmhh,.dir-rtl .ygtvtp,.dir-rtl .ygtvtph,.dir-rtl .ygtvtphh,.dir-rtl .ygtvln,.dir-rtl .ygtvlm,.dir-rtl .ygtvlmh,.dir-rtl .ygtvlmhh,.dir-rtl .ygtvlp,.dir-rtl .ygtvlph,.dir-rtl .ygtvlphh,.dir-rtl .ygtvdepthcell,.dir-rtl .ygtvok,.dir-rtl .ygtvok:hover,.dir-rtl .ygtvcancel,.dir-rtl .ygtvcancel:hover{width:18px;height:22px;cursor:pointer;background-image:url([[pix:theme|yui2-treeview-sprite-rtl]]);background-repeat:no-repeat}.dir-rtl .ygtvtn{background-position:0 -5600px}.dir-rtl .ygtvtm{background-position:0 -4000px}.dir-rtl .ygtvtmh,.dir-rtl .ygtvtmhh{background-position:0 -4800px}.dir-rtl .ygtvtp{background-position:0 -6400px}.dir-rtl .ygtvtph,.dir-rtl .ygtvtphh{background-position:0 -7200px}.dir-rtl .ygtvln{background-position:0 -1600px}.dir-rtl .ygtvlm{background-position:0 0}.dir-rtl .ygtvlmh,.dir-rtl .ygtvlmhh{background-position:0 -800px}.dir-rtl .ygtvlp{background-position:0 -2400px}.dir-rtl .ygtvlph,.dir-rtl .ygtvlphh{background-position:0 -3200px}.dir-rtl .ygtvdepthcell{background-position:0 -8000px}.dir-rtl .ygtvok{background-position:0 -8800px}.dir-rtl .ygtvok:hover{background-position:0 -8844px}.dir-rtl .ygtvcancel{background-position:0 -8822px}.dir-rtl .ygtvcancel:hover{background-position:0 -8866px}.dir-rtl.yui-skin-sam .yui-panel .hd{text-align:right}.dir-rtl .yui-skin-sam .yui-layout .yui-layout-unit div.yui-layout-bd{text-align:right}.dir-rtl .clearlooks2.ie9 .mceAlert .mceMiddle span,.dir-rtl .clearlooks2 .mceConfirm .mceMiddle span{top:44px}.dir-rtl .o2k7Skin table,.dir-rtl .o2k7Skin tbody,.dir-rtl .o2k7Skin a,.dir-rtl .o2k7Skin img,.dir-rtl .o2k7Skin tr,.dir-rtl .o2k7Skin div,.dir-rtl .o2k7Skin td,.dir-rtl .o2k7Skin iframe,.dir-rtl .o2k7Skin span,.dir-rtl .o2k7Skin *,.dir-rtl .o2k7Skin .mceText,.dir-rtl .o2k7Skin .mceListBox .mceText{text-align:right}.path-rating .ratingtable{width:100%;margin-bottom:1em}.path-rating .ratingtable th.rating{width:100%}.path-rating .ratingtable td.rating,.path-rating .ratingtable td.time{text-align:center;white-space:nowrap}.initialbar a,.initialbar strong{padding-right:3px;padding-left:3px}.moodle-dialogue-base .moodle-dialogue-lightbox{background-color:#AAA}.moodle-dialogue-base .hidden,.moodle-dialogue-base .moodle-dialogue-hidden{display:none}.no-scrolling{overflow:hidden}.moodle-dialogue-base .moodle-dialogue-fullscreen{position:fixed;top:0;right:0;bottom:-50px;left:0}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-content{overflow:auto}.moodle-dialogue-base .moodle-dialogue-fullscreen .closebutton{width:28px;height:16px;background-size:100%}.moodle-dialogue-base .moodle-dialogue{z-index:600;padding:0;margin:0;background:0;border:0;outline:#000 dotted 0}.moodle-dialogue-base .moodle-dialogue-wrap{margin-top:-3px;margin-left:-3px;background-color:#fff;border:1px solid #ccc;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd,.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd.yui3-widget-hd{padding:5px;margin:0;font-size:12px;font-weight:normal;letter-spacing:1px;color:#333;text-align:center;text-shadow:1px 1px 1px #fff;background:#ccc;background-color:#ebebeb;background-image:-moz-linear-gradient(top,#fff,#ccc);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#ccc));background-image:-webkit-linear-gradient(top,#fff,#ccc);background-image:-o-linear-gradient(top,#fff,#ccc);background-image:linear-gradient(to bottom,#fff,#ccc);background-repeat:repeat-x;border-bottom:1px solid #bbb;-webkit-border-radius:10px 10px 0 0;-moz-border-radius:10px 10px 0 0;border-radius:10px 10px 0 0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffcccccc',GradientType=0);filter:0}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd h1{display:inline;padding:0;margin:0;font-size:100%;font-weight:bold}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd .yui3-widget-buttons{padding:5px}.moodle-dialogue-base .closebutton{display:inline-block;float:right;width:25px;height:15px;padding:0;vertical-align:middle;cursor:pointer;background-image:url([[pix:theme|sprite]]);background-repeat:no-repeat;border-style:none}.dir-rtl .moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-hd .yui3-widget-buttons{right:auto;left:0}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-bd{padding:1em;font-size:12px;line-height:2em;color:#555}.moodle-dialogue-base .moodle-dialogue-wrap .moodle-dialogue-content{padding:0;background:#FFF}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-hd{padding:10px;font-size:16px}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-content{position:absolute;top:0;right:0;bottom:50px;left:0;margin:0;overflow:auto;border:0}.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-hd,.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-wrap{border-radius:0}.moodle-dialogue-confirm .confirmation-dialogue{text-align:center}.moodle-dialogue-confirm .confirmation-dialogue input{text-align:center}.moodle-dialogue-exception .moodle-exception-message{text-align:center}.moodle-dialogue-exception .moodle-exception-param label{font-weight:bold}.moodle-dialogue-exception .param-stacktrace label{background-color:#EEE;border:1px solid #ccc;border-bottom-width:0}.moodle-dialogue-exception .param-stacktrace pre{background-color:#fff;border:1px solid #ccc}.moodle-dialogue-exception .param-stacktrace .stacktrace-file{font-size:11.9px;color:navy}.moodle-dialogue-exception .param-stacktrace .stacktrace-line{font-size:11.9px;color:#b94a48}.moodle-dialogue-exception .param-stacktrace .stacktrace-call{font-size:90%;color:#333;border-bottom:1px solid #eee}.moodle-dialogue-base .moodle-dialogue .moodle-dialogue-content .moodle-dialogue-ft{padding:0;margin:.7em 1em;font-size:12px;text-align:right;background-color:#FFF}.moodle-dialogue-confirm .confirmation-message{margin:.5em 1em}.moodle-dialogue-confirm .confirmation-dialogue input{min-width:80px}.moodle-dialogue-exception .moodle-exception-message{margin:1em}.moodle-dialogue-exception .moodle-exception-param{margin-bottom:.5em}.moodle-dialogue-exception .moodle-exception-param label{width:150px}.moodle-dialogue-exception .param-stacktrace label{display:block;padding:4px 1em;margin:0}.moodle-dialogue-exception .param-stacktrace pre{display:block;height:200px;overflow:auto}.moodle-dialogue-exception .param-stacktrace .stacktrace-file{display:inline-block;margin:4px 0}.moodle-dialogue-exception .param-stacktrace .stacktrace-line{display:inline-block;width:50px;margin:4px 1em}.moodle-dialogue-exception .param-stacktrace .stacktrace-call{padding-bottom:4px;padding-left:25px;margin-bottom:4px}.moodle-dialogue .moodle-dialogue-bd .content-lightbox{top:0;left:0;width:100%;height:100%;padding:10% 0;text-align:center;background-color:white;opacity:.75;filter:alpha(opacity=75)}.moodle-dialogue .tooltiptext{max-height:300px}.moodle-dialogue-base .moodle-dialogue.moodle-dialogue-tooltip{z-index:3001}.moodle-dialogue-base .moodle-dialogue.moodle-dialogue-tooltip .moodle-dialogue-bd{overflow:auto}#page-question-edit.dir-rtl a.container-close{right:auto;left:6px}.chooserdialoguebody,.choosertitle{display:none}.moodle-dialogue.chooserdialogue .moodle-dialogue-content .moodle-dialogue-ft{margin:0}.chooserdialogue .moodle-dialogue-wrap .moodle-dialogue-bd{padding:0;background:#f2f2f2;-webkit-border-bottom-right-radius:10px;border-bottom-right-radius:10px;-webkit-border-bottom-left-radius:10px;border-bottom-left-radius:10px;-moz-border-radius-bottomright:10px;-moz-border-radius-bottomleft:10px}.choosercontainer #chooseform .submitbuttons{padding:.7em 0;text-align:center}@media(max-height:639px){.ios.safari .choosercontainer #chooseform .submitbuttons{padding:45px 0}}.choosercontainer #chooseform .submitbuttons input{min-width:100px;margin:0 .5em}.choosercontainer #chooseform .options{position:relative;border-bottom:1px solid #bbb}.jschooser .choosercontainer #chooseform .alloptions{max-width:20.3em;overflow-x:hidden;overflow-y:auto;-webkit-box-shadow:inset 0 0 30px 0 #ccc;-moz-box-shadow:inset 0 0 30px 0 #ccc;box-shadow:inset 0 0 30px 0 #ccc}.dir-rtl.jschooser .choosercontainer #chooseform .alloptions{max-width:18.3em}.choosercontainer #chooseform .moduletypetitle,.choosercontainer #chooseform .option,.choosercontainer #chooseform .nonoption{padding:0 1.6em 0 1.6em;margin-bottom:0}.choosercontainer #chooseform .moduletypetitle{padding-top:1.2em;padding-bottom:.4em;text-transform:uppercase}.choosercontainer #chooseform .option .typename,.choosercontainer #chooseform .option span.modicon img.icon,.choosercontainer #chooseform .nonoption .typename,.choosercontainer #chooseform .nonoption span.modicon img.icon{padding:0 0 0 .5em}.dir-rtl .choosercontainer #chooseform .option .typename,.dir-rtl .choosercontainer #chooseform .option span.modicon img.icon,.dir-rtl .choosercontainer #chooseform .nonoption .typename,.dir-rtl .choosercontainer #chooseform .nonoption span.modicon img.icon{padding:0 .5em 0 0}.chooserdialogue-course-modchooser .choosercontainer #chooseform .option span.modicon img.icon,.chooserdialogue-course-modchooser .choosercontainer #chooseform .nonoption span.modicon img.icon{width:24px;height:24px}.choosercontainer #chooseform .option input[type=radio],.choosercontainer #chooseform .option span.typename,.choosercontainer #chooseform .option span.modicon{vertical-align:middle}.choosercontainer #chooseform .option label{display:block;padding:.3em 0 .1em 0;border-bottom:1px solid #fff}.choosercontainer #chooseform .nonoption{padding-top:.3em;padding-bottom:.1em;padding-left:2.7em}.dir-rtl .choosercontainer #chooseform .nonoption{padding-right:2.7em;padding-left:0}.choosercontainer #chooseform .subtype{padding:0 1.6em 0 3.2em;margin-bottom:0}.dir-rtl .choosercontainer #chooseform .subtype{padding:0 3.2em 0 1.6em}.choosercontainer #chooseform .subtype .typename{margin:0 0 0 .2em}.dir-rtl .choosercontainer #chooseform .subtype .typename{margin:0 .2em 0 0}.jschooser .choosercontainer #chooseform .instruction,.jschooser .choosercontainer #chooseform .typesummary{position:absolute;top:0;right:0;bottom:0;left:20.3em;display:none;padding:1.6em;margin:0;overflow-x:hidden;overflow-y:auto;line-height:2em;background-color:#fff}.dir-rtl.jschooser .choosercontainer #chooseform .instruction,.dir-rtl.jschooser .choosercontainer #chooseform .typesummary{right:18.5em;left:0;border-right:1px solid grey}.jschooser .choosercontainer #chooseform .instruction,.choosercontainer #chooseform .selected .typesummary{display:block}.choosercontainer #chooseform .selected{background-color:#fff;-webkit-box-shadow:0 0 10px 0 #ccc;-moz-box-shadow:0 0 10px 0 #ccc;box-shadow:0 0 10px 0 #ccc}.section-modchooser-link img.smallicon{padding:3px}.formlistingradio{padding-right:10px;padding-bottom:25px}.formlistinginputradio{float:left}.formlistingmain{min-height:225px}.formlisting{position:relative;padding:1px 19px 14px;margin:15px 0;background-color:white;border:1px solid #DDD;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingmore{position:absolute;right:-1px;bottom:-1px;padding:3px 7px;font-size:12px;font-weight:bold;color:#9da0a4;cursor:pointer;background-color:whiteSmoke;border:1px solid #ddd;-webkit-border-radius:4px 0 4px 0;-moz-border-radius:4px 0 4px 0;border-radius:4px 0 4px 0}.formlistingall{padding:0;margin:15px 0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.formlistingrow{top:50%;left:50%;float:left;width:150px;min-height:34px;padding:6px;cursor:pointer;background-color:#f7f7f9;border-right:1px solid #e1e1e8;border-bottom:1px solid;border-left:1px solid #e1e1e8;border-color:#e1e1e8;-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}body.jsenabled .formlistingradio{display:none}body.jsenabled .formlisting{display:block}table.collection{width:100%;margin-bottom:20px;border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}table.collection th,table.collection td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}table.collection th{font-weight:bold}table.collection thead th{vertical-align:bottom}table.collection caption+thead tr:first-child th,table.collection caption+thead tr:first-child td,table.collection colgroup+thead tr:first-child th,table.collection colgroup+thead tr:first-child td,table.collection thead:first-child tr:first-child th,table.collection thead:first-child tr:first-child td{border-top:0}table.collection tbody+tbody{border-top:2px solid #ddd}table.collection .table{background-color:#fff}table.collection th,table.collection td{border-left:1px solid #ddd}table.collection caption+thead tr:first-child th,table.collection caption+tbody tr:first-child th,table.collection caption+tbody tr:first-child td,table.collection colgroup+thead tr:first-child th,table.collection colgroup+tbody tr:first-child th,table.collection colgroup+tbody tr:first-child td,table.collection thead:first-child tr:first-child th,table.collection tbody:first-child tr:first-child th,table.collection tbody:first-child tr:first-child td{border-top:0}table.collection thead:first-child tr:first-child>th:first-child,table.collection tbody:first-child tr:first-child>td:first-child,table.collection tbody:first-child tr:first-child>th:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}table.collection thead:first-child tr:first-child>th:last-child,table.collection tbody:first-child tr:first-child>td:last-child,table.collection tbody:first-child tr:first-child>th:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}table.collection thead:last-child tr:last-child>th:first-child,table.collection tbody:last-child tr:last-child>td:first-child,table.collection tbody:last-child tr:last-child>th:first-child,table.collection tfoot:last-child tr:last-child>td:first-child,table.collection tfoot:last-child tr:last-child>th:first-child{-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}table.collection thead:last-child tr:last-child>th:last-child,table.collection tbody:last-child tr:last-child>td:last-child,table.collection tbody:last-child tr:last-child>th:last-child,table.collection tfoot:last-child tr:last-child>td:last-child,table.collection tfoot:last-child tr:last-child>th:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}table.collection tfoot+tbody:last-child tr:last-child td:first-child{-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-moz-border-radius-bottomleft:0}table.collection tfoot+tbody:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0;-moz-border-radius-bottomright:0}table.collection caption+thead tr:first-child th:first-child,table.collection caption+tbody tr:first-child td:first-child,table.collection colgroup+thead tr:first-child th:first-child,table.collection colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}table.collection caption+thead tr:first-child th:last-child,table.collection caption+tbody tr:first-child td:last-child,table.collection colgroup+thead tr:first-child th:last-child,table.collection colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}table.collection tbody>tr:nth-child(odd)>td,table.collection tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}table.collection .name{text-align:left;vertical-align:middle}table.collection .awards{width:10%;text-align:center;vertical-align:middle}table.collection .criteria{width:40%;text-align:left;vertical-align:top}table.collection .badgeimage,table.collection .status{width:15%;text-align:center;vertical-align:middle}table.collection .description{width:25%;text-align:left}table.collection .actions{width:11em;text-align:center;vertical-align:middle}a.criteria-action{float:right;padding:0 3px}ul.badges{margin:0;list-style:none}.badges li{position:relative;display:inline-block;width:150px;padding-bottom:2em;text-align:center;vertical-align:top}.badges li .badge-name{display:block;padding:5px}.badges li>img{position:absolute}.badges li .badge-image{top:0;left:10px;z-index:1;width:100px;height:100px}.badges li .badge-actions{position:relative}.badges li .expireimage{position:absolute;top:0;left:25px;z-index:10;width:100px;height:100px;opacity:.85}#badge-image{position:relative;display:inline-block;width:20%;min-width:100px;padding:0;margin-top:17px;vertical-align:top;background-color:transparent}#badge-image .expireimage{position:absolute;top:0;left:0;z-index:10;width:100px;height:100px;opacity:.85;filter:alpha(opacity=85)}#badge-image .singlebutton{padding-top:5px}#badge-image .singlebutton input{margin-left:0}.dir-rtl #badge-image{float:right}.dir-rtl #badge-image .expireimage{left:41px}#badge-details{display:inline-block;width:79%}#badge-overview dl,#badge-details dl{margin:0}#badge-overview dl dt,#badge-details dl dt,#badge-overview dl dd,#badge-details dl dd{padding:3px 0;vertical-align:top}#badge-overview dl dt,#badge-details dl dt{display:inline-block;width:20%;min-width:100px;clear:both}#badge-overview dl dd,#badge-details dl dd{display:inline-block;width:79%;margin-left:1%}.badge-profile{vertical-align:top}.connected{color:#468847}.notconnected{color:#b94a48}.connecting{color:#c09853}#page-badges-award .recipienttable tr td{vertical-align:top}#page-badges-award .recipienttable tr td.actions .actionbutton{width:100%;padding:.5em 0;margin:.3em 0}#page-badges-award .recipienttable tr td.existing,#page-badges-award .recipienttable tr td.potential{width:42%}#issued-badge-table .activatebadge{display:inline-block}.statusbox.active{background-color:#dff0d8}.statusbox.inactive{background-color:#fcf8e3}.statusbox{padding:5px;margin-bottom:5px;text-align:center}.statusbox .activatebadge{display:inline-block}.statusbox .activatebadge input[type=submit]{margin:3px}.activatebadge{margin:0;text-align:left;vertical-align:middle}.dir-rtl .activatebadge{text-align:right}img#persona_signin{cursor:pointer}.addcourse{float:right}.invisiblefieldset{display:inline;padding:0;margin:0;border-width:0}.breadcrumb-nav{float:left;margin-bottom:10px}.dir-rtl .breadcrumb-nav{float:right}.breadcrumb-button .singlebutton div{margin-right:0}.breadcrumb-nav .breadcrumb{margin:0}.moodle-actionmenu,.moodle-actionmenu>ul,.moodle-actionmenu>ul>li{display:inline-block}.moodle-actionmenu ul{padding:0;margin:0;list-style-type:none}.moodle-actionmenu .toggle-display,.moodle-actionmenu .menu-action-text{display:none}.jsenabled .moodle-actionmenu[data-enhance]{display:block}.jsenabled .moodle-actionmenu[data-enhance] .menu{display:none}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display{display:inline;opacity:.5;filter:alpha(opacity=50)}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu{display:block;padding-right:4px;padding-left:4px;margin-left:4px}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .iconsmall{padding:8px 4px 0 2px;margin:4px 4px 4px 0;vertical-align:text-bottom}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-top:8px;margin-left:2px;border-top-color:#777}.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret:hover,.jsenabled .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret:active{border-top-color:#555}.jsenabled .moodle-actionmenu[data-enhanced] .toggle-display{opacity:1;filter:alpha(opacity=100)}.jsenabled .moodle-actionmenu[data-enhanced] .menu-action-text{display:inline}.jsenabled.dir-rtl .moodle-actionmenu[data-enhance] .toggle-display.textmenu{margin-right:4px;margin-left:initial}.jsenabled.dir-rtl .moodle-actionmenu[data-enhance] .toggle-display.textmenu .caret{margin-right:2px;margin-left:initial}.moodle-actionmenu[data-enhanced].show{position:relative}.moodle-actionmenu[data-enhanced].show .menu{position:absolute;z-index:1000;display:block;text-align:left;background-color:#fff;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px;-webkit-box-shadow:5px 5px 20px 0 #666;-moz-box-shadow:5px 5px 20px 0 #666;box-shadow:5px 5px 20px 0 #666}.moodle-actionmenu[data-enhanced].show .menu a{display:block;padding:2px 1em 2px 28px;color:#333}.moodle-actionmenu[data-enhanced].show .menu a:hover{color:#fff;background-color:#0070a8}.moodle-actionmenu[data-enhanced].show .menu a:first-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.moodle-actionmenu[data-enhanced].show .menu a:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.moodle-actionmenu[data-enhanced].show .menu a.hidden{display:none}.moodle-actionmenu[data-enhanced].show .menu img{vertical-align:middle}.moodle-actionmenu[data-enhanced].show .menu .iconsmall{margin:4px 4px 4px -24px}.moodle-actionmenu[data-enhanced].show .menu>li{display:block}.moodle-actionmenu[data-enhanced].show .menu.align-tl-bl{top:100%;left:0;margin-top:4px}.moodle-actionmenu[data-enhanced].show .menu.align-tr-bl{top:100%;right:100%}.moodle-actionmenu[data-enhanced].show .menu.align-bl-bl{bottom:100%;left:0}.moodle-actionmenu[data-enhanced].show .menu.align-br-bl{right:100%;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-br{top:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tr-br{top:100%;right:0;margin-top:4px}.moodle-actionmenu[data-enhanced].show .menu.align-bl-br{bottom:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-br-br{right:0;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-tl{top:0;left:0}.moodle-actionmenu[data-enhanced].show .menu.align-tr-tl{top:0;right:100%;margin-right:4px}.moodle-actionmenu[data-enhanced].show .menu.align-bl-tl{bottom:100%;left:0;margin-bottom:4px}.moodle-actionmenu[data-enhanced].show .menu.align-br-tl{right:100%;bottom:100%}.moodle-actionmenu[data-enhanced].show .menu.align-tl-tr{top:0;left:100%;margin-left:4px}.moodle-actionmenu[data-enhanced].show .menu.align-tr-tr{top:0;right:0}.moodle-actionmenu[data-enhanced].show .menu.align-bl-tr{bottom:100%;left:100%}.moodle-actionmenu[data-enhanced].show .menu.align-br-tr{right:0;bottom:100%;margin-bottom:4px}.block .moodle-actionmenu{text-align:right}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu{right:auto;left:0;text-align:right}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu .iconsmall{margin-right:0;margin-left:8px}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-bl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-bl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-bl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-bl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-br{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-br{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-br{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-br{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-tl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-tl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-tl{right:0;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-tl{right:auto;left:100%}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tl-tr{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-tr-tr{right:auto;left:0}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-bl-tr{right:100%;left:auto}.dir-rtl .moodle-actionmenu[data-enhanced].show .menu.align-br-tr{right:auto;left:0}.dir-rtl .block .moodle-actionmenu{text-align:right}ul.dragdrop-keyboard-drag li{list-style-type:none}.block-control-actions .moodle-core-dragdrop-draghandle img{width:12px;height:12px}a.disabled:hover,a.disabled{font-style:italic;color:#808080;text-decoration:none;cursor:default}body.lockscroll{height:100%;overflow:hidden}.dir-rtl ul,.dir-rtl ol{margin-right:25px;margin-left:0}.progressbar_container{max-width:500px;margin:0 auto}.ie10 .yui3-calendar-header-label{display:inline-block}dd:before,dd:after{display:block;content:" "}dd:after{clear:both}.formtable tbody th{font-weight:normal;text-align:right}.path-admin #assignrole{width:60%;margin-right:auto;margin-left:auto}.path-admin .admintable .leftalign{text-align:left}.environmenttable p.warn{color:#c09853;background-color:#fcf8e3}.environmenttable .error,.environmenttable span.warn,.environmenttable .ok{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.environmenttable .error:empty,.environmenttable span.warn:empty,.environmenttable .ok:empty{display:none}.environmenttable .error-important,.environmenttable span.warn-important,.environmenttable .ok-important{background-color:#b94a48}.environmenttable .error-important[href],.environmenttable span.warn-important[href],.environmenttable .ok-important[href]{background-color:#953b39}.environmenttable .error-warning,.environmenttable span.warn-warning,.environmenttable .ok-warning{background-color:#f89406}.environmenttable .error-warning[href],.environmenttable span.warn-warning[href],.environmenttable .ok-warning[href]{background-color:#c67605}.environmenttable .error-success,.environmenttable span.warn-success,.environmenttable .ok-success{background-color:#468847}.environmenttable .error-success[href],.environmenttable span.warn-success[href],.environmenttable .ok-success[href]{background-color:#356635}.environmenttable .error-info,.environmenttable span.warn-info,.environmenttable .ok-info{background-color:#3a87ad}.environmenttable .error-info[href],.environmenttable span.warn-info[href],.environmenttable .ok-info[href]{background-color:#2d6987}.environmenttable .error-inverse,.environmenttable span.warn-inverse,.environmenttable .ok-inverse{background-color:#333}.environmenttable .error-inverse[href],.environmenttable span.warn-inverse[href],.environmenttable .ok-inverse[href]{background-color:#1a1a1a}.environmenttable .error{background-color:#b94a48}.environmenttable span.warn{background-color:#f89406}.environmenttable .ok{background-color:#468847}.path-admin .admintable.environmenttable .name,.path-admin .admintable.environmenttable .info,.path-admin #assignrole .admintable .role,.path-admin #assignrole .admintable .userrole,.path-admin #assignrole .admintable .roleholder{white-space:nowrap}.path-admin .incompatibleblockstable td.c0{font-weight:bold}#page-admin-course-category .addcategory{padding:10px}#page-admin-course-index .editcourse{margin:20px auto}#page-admin-course-index .editcourse th,#page-admin-course-index .editcourse td{padding-right:10px;padding-left:10px}.timewarninghidden{display:none}.statusok,.statuswarning,.statusserious,.statuscritical{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.statusok:empty,.statuswarning:empty,.statusserious:empty,.statuscritical:empty{display:none}.statusok-important,.statuswarning-important,.statusserious-important,.statuscritical-important{background-color:#b94a48}.statusok-important[href],.statuswarning-important[href],.statusserious-important[href],.statuscritical-important[href]{background-color:#953b39}.statusok-warning,.statuswarning-warning,.statusserious-warning,.statuscritical-warning{background-color:#f89406}.statusok-warning[href],.statuswarning-warning[href],.statusserious-warning[href],.statuscritical-warning[href]{background-color:#c67605}.statusok-success,.statuswarning-success,.statusserious-success,.statuscritical-success{background-color:#468847}.statusok-success[href],.statuswarning-success[href],.statusserious-success[href],.statuscritical-success[href]{background-color:#356635}.statusok-info,.statuswarning-info,.statusserious-info,.statuscritical-info{background-color:#3a87ad}.statusok-info[href],.statuswarning-info[href],.statusserious-info[href],.statuscritical-info[href]{background-color:#2d6987}.statusok-inverse,.statuswarning-inverse,.statusserious-inverse,.statuscritical-inverse{background-color:#333}.statusok-inverse[href],.statuswarning-inverse[href],.statusserious-inverse[href],.statuscritical-inverse[href]{background-color:#1a1a1a}.statusok{background-color:#468847}.statuswarning{background-color:#c09853}.statusserious{background-color:#f89406}.statuscritical{background-color:#b94a48}#page-admin-report-capability-index #capabilitysearch{width:30em}#page-admin-report-backups-index .backup-error,#page-admin-report-backups-index .backup-unfinished{color:#b94a48}#page-admin-report-backups-index .backup-skipped,#page-admin-report-backups-index .backup-ok,#page-admin-report-backups-index .backup-notyetrun{color:#468847}#page-admin-report-backups-index .backup-warning{color:#c09853}#page-admin-qtypes .disabled,#page-admin-qbehaviours .disabled{color:#999}#page-admin-qtypes #qtypes div,#page-admin-qtypes #qtypes form,#page-admin-qbehaviours #qbehaviours div,#page-admin-qbehaviours #qbehaviours form{display:inline}#page-admin-qtypes #qtypes img.spacer,#page-admin-qbehaviours #qbehaviours img.spacer{width:16px}img.iconsmall{padding:.3em;margin:0}#page-admin-qbehaviours .cell.c3,#page-admin-qtypes .cell.c3{font-size:10.5px}#page-admin-lang .generalbox,#page-admin-course-index .singlebutton,#page-admin-course-index .addcategory,#page-course-index .buttons,#page-course-index-category .buttons,#page-admin-course-category .addcategory,#page-admin-stickyblocks .generalbox,#page-admin-maintenance .buttons,#page-admin-course-index .buttons,#page-admin-course-category .buttons,#page-admin-index .copyright,#page-admin-index .copyrightnotice,#page-admin-index .adminerror .singlebutton,#page-admin-index .adminwarning .singlebutton,#page-admin-index #layout-table .singlebutton{margin-bottom:1em;text-align:center}.path-admin-roles .capabilitysearchui{margin-right:auto;margin-left:auto;text-align:left}#page-admin-roles-define .topfields{margin:1em 0 2em}#page-admin-roles-define .capdefault{background-color:#f5f5f5;border:1px solid #ddd}#page-filter-manage .backlink,.path-admin-roles .backlink{margin-top:1em}#page-admin-roles-explain #chooseuser h3,#page-admin-roles-usersroles .contextname{margin-top:0}#page-admin-roles-explain #chooseusersubmit{margin-top:0;text-align:center}#page-admin-roles-usersroles p{margin:0}#page-admin-roles-override .cell.c1,#page-admin-roles-assign .cell.c3,#page-admin-roles-assign .cell.c1{padding-top:.75em}#page-admin-roles-override .overridenotice,#page-admin-roles-define .definenotice{margin:1em 10% 2em 10%;text-align:left}#notice{width:60%;min-width:220px;margin:auto}#page-admin-index .releasenoteslink,#page-admin-index .adminwarning,#page-admin-index .adminerror{width:60%;min-width:220px;padding:8px 35px 8px 14px;margin:auto;margin-bottom:20px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}#page-admin-index .adminerror{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}#page-admin-index .releasenoteslink{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo span{display:block}#page-admin-index .updateplugin div,#page-admin-plugins .updateplugin div{margin-bottom:.5em}#page-admin-index .updateplugin .updatepluginconfirmexternal,#page-admin-plugins .updateplugin .updatepluginconfirmexternal{padding:1em;background-color:#f2dede;border:1px solid #eed3d7}#page-admin-user-user_bulk #users .fgroup{white-space:nowrap}#page-admin-report-stats-index .graph{margin-bottom:1em;text-align:center}#page-admin-report-courseoverview-index .graph{margin-bottom:1em;text-align:center}#page-admin-lang .translator{border-style:solid;border-width:1px}.path-admin .roleassigntable{width:100%}.path-admin .roleassigntable td{padding:.2em .3em;vertical-align:top}.path-admin .roleassigntable p{margin:.2em 0;text-align:left}.path-admin .roleassigntable #existingcell,.path-admin .roleassigntable #potentialcell{width:42%}.path-admin .roleassigntable #existingcell p>label:first-child,.path-admin .roleassigntable #potentialcell p>label:first-child{font-weight:bold}.path-admin .roleassigntable #buttonscell{width:16%}.path-admin .roleassigntable #buttonscell #assignoptions{font-size:10.5px}.path-admin .roleassigntable #removeselect_wrapper,.path-admin .roleassigntable #addselect_wrapper{width:100%}.path-admin table.rolecap tr.rolecap th{font-weight:normal;text-align:left}.path-admin.dir-rtl table.rolecap tr.rolecap th{text-align:right}.path-admin .rolecap .hiddenrow{display:none}.path-admin #defineroletable .rolecap .inherit,.path-admin #defineroletable .rolecap .allow,.path-admin #defineroletable .rolecap .prevent,.path-admin #defineroletable .rolecap .prohibit{min-width:3.5em;padding:0;text-align:center}.path-admin .rolecap .cap-name,.path-admin .rolecap .note{display:block;font-size:10.5px;font-weight:normal;white-space:nowrap}.path-admin .rolecap label{display:block;padding:.5em;margin:0;text-align:center}.plugincheckwrapper{width:100%}.environmentbox{margin-top:1em}#mnetconfig table{margin-right:auto;margin-left:auto}.environmenttable .cell{padding:.15em .5em}.environmenttable img.iconhelp{padding-right:.3em}.dir-rtl .environmenttable img.iconhelp{padding-right:0;padding-left:.3em}#trustedhosts .generaltable{width:500px;margin-right:auto;margin-left:auto}#trustedhosts .standard{width:auto}#adminsettings legend{display:none}#adminsettings fieldset.error{margin:.2em 0 .5em 0}#adminsettings fieldset.error legend{display:block}.dir-rtl #admin-spelllanguagelist textarea,#page-admin-setting-editorsettingstinymce.dir-rtl .form-textarea textarea{text-align:left;direction:ltr}.adminsettingsflags{float:right}.dir-rtl .adminsettingsflags{float:left}.adminsettingsflags label{margin-right:7px}.dir-rtl .adminsettingsflags label{margin-left:7px}.form-description{clear:right}.dir-rtl .form-description{clear:left}.form-item .form-setting .form-htmlarea{display:inline;width:640px}.form-item .form-setting .form-htmlarea .htmlarea{display:block;width:640px}.form-item .form-setting .form-multicheckbox ul{padding:0;margin:7px 0 0 0;list-style:none}.form-item .form-setting .defaultsnext{display:inline;margin-right:.5em}.dir-rtl .form-item .form-setting .defaultsnext{margin-right:0;margin-left:.5em}.form-item .form-setting .locked-checkbox{display:inline;margin-right:.2em;margin-left:.5em}.dir-rtl .form-item .form-setting .locked-checkbox{display:inline;margin-right:.5em;margin-left:.2em}.form-item .form-setting .form-password .unmask,.form-item .form-setting .form-defaultinfo{display:inline-block}.form-item .pathok,.form-item .patherror{margin-left:.5em}#admin-emoticons td input{width:8em}#admin-emoticons td.c0 input{width:4em}#adminthemeselector .selectedtheme td.c0{border:1px solid #000;border-right-width:0}#adminthemeselector .selectedtheme td.c1{border:1px solid #000;border-left-width:0}.admin_colourpicker,.admin_colourpicker_preview{display:none}.jsenabled .admin_colourpicker_preview{display:inline}.jsenabled .admin_colourpicker{display:block;width:410px;height:102px;margin-bottom:10px}.admin_colourpicker .loadingicon{margin-left:auto;vertical-align:middle}.admin_colourpicker .colourdialogue{float:left;border:1px solid #000}.admin_colourpicker .previewcolour{margin-left:301px;border:1px solid #000}.admin_colourpicker .currentcolour{margin-left:301px;border:1px solid #000;border-top-width:0}.dir-rtl .form-item .form-setting,.dir-rtl .form-item .form-label,.dir-rtl .form-item .form-description,.dir-rtl.path-admin .roleassigntable p{text-align:right}#page-admin-index #notice .checkforupdates{text-align:center}#plugins-check-info{margin:1em;text-align:center}#plugins-check .displayname .pluginicon{width:16px}#plugins-check .status-new .status{background-color:#dff0d8}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity200 .info.release,#plugins-check .status-upgrade .status,#plugins-check .status-delete .status{background-color:#d9edf7}#plugins-control-panel .extension .source,#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity100 .info.release,#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity150 .info.release,.pluginupdateinfo.maturity100,.pluginupdateinfo.maturity150,#plugins-check .extension .source{background-color:#fcf8e3}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo.maturity50 .info.release,.pluginupdateinfo.maturity50,#plugins-check .requires-failed,#plugins-check .missingfromdisk .displayname,#plugins-check .status-missing .status,#plugins-check .status-downgrade .status{background-color:#f2dede}#plugins-control-panel .statusmsg{padding:3px;background-color:#eee;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}#plugins-control-panel .status-missing .pluginname{background-color:#f2dede}#plugins-control-panel .status-missing .statusmsg{color:#b94a48}#plugins-control-panel .status-new .pluginname{background-color:#dff0d8}#plugins-control-panel .status-new .statusmsg{color:#468847}#plugins-control-panel .disabled .availability{background-color:#eee}#plugins-check .standard .source,#plugins-check .status-nodb .status,#plugins-check .status-uptodate .status,#plugins-check .requires-ok{color:#999}#plugins-check .requires ul{margin:0;font-size:10.5px}#plugins-check .status .pluginupdateinfo{padding:5px 10px;margin:10px;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-check .status .pluginupdateinfo span,#plugins-check .status .pluginupdateinfo a{padding-right:1em}#page-admin-index .upgradepluginsinfo{text-align:center}#page-admin-plugins .checkforupdates{margin:0 auto 1em;text-align:center}#plugins-control-panel .requiredby,#plugins-control-panel .pluginname .componentname{font-size:11.9px;color:#999}#plugins-control-panel .pluginname .componentname{margin-left:22px}#plugins-overview-filter .filter-item,#plugins-overview-panel .info{padding:0 10px}#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo .separator,#plugins-check .status .pluginupdateinfo .separator,#page-admin-plugins .separator{border-left:1px dotted #999}#plugins-control-panel .msg td{text-align:center}#plugins-overview-filter,#plugins-overview-panel{margin:1em auto;text-align:center}#plugins-overview-panel .info.updatable{margin-left:10px;font-weight:bold;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-overview-filter .filter-item.active{font-weight:bold}#plugins-control-panel .displayname img.icon{padding-top:0;padding-bottom:0}#plugins-control-panel .uninstall a{color:#b94a48}#plugins-control-panel .notes .pluginupdateinfo{padding:5px 10px;margin:10px;background-color:#d9edf7;-webkit-border-radius:10px;-moz-border-radius:10px;border-radius:10px}#plugins-control-panel .notes .pluginupdateinfo span,#plugins-control-panel .notes .pluginupdateinfo a{padding-right:1em}.dir-rtl #plugins-check .pluginupdateinfo{text-align:center;direction:ltr}.dir-rtl #plugins-check .rootdir,.dir-rtl #plugins-check .requires-ok{text-align:left;direction:ltr}#page-admin-mnet-peers .box.deletedhosts{margin-bottom:1em;font-size:11.9px}#page-admin-mnet-peers .mform .deletedhostinfo{padding:4px;margin-bottom:5px;background-color:#f2dede;border:2px solid #eed3d7}#core-cache-plugin-summaries table,#core-cache-store-summaries table{width:100%}#core-cache-lock-summary table,#core-cache-definition-summaries table,#core-cache-mode-mappings table{margin:0 auto}#core-cache-store-summaries .default-store td{font-style:italic}#core-cache-rescan-definitions,#core-cache-mode-mappings .edit-link,#core-cache-lock-summary .new-instance{margin-top:.5em;text-align:center}.tinymcesubplugins img.icon{padding-top:0;padding-bottom:0}.maintenancewarning{position:fixed;right:0;bottom:0;z-index:1;padding:3px 1em;overflow:hidden;text-align:center}.maintenancewarning.error{font-weight:bold;color:#b94a48;background-color:#f2dede;border:2px solid #eed3d7}.maintenancewarning.warning{color:#c09853;background-color:#fcf8e3;border:2px solid #fbeed5}#adminsettings .form-overridden{color:#3a87ad;background-color:#d9edf7}.calendar_event_course{background-color:#ffd3bd}.calendar_event_global{background-color:#d6f8cd}.calendar_event_group{background-color:#fee7ae}.calendar_event_user{background-color:#dce7ec}.path-calendar .calendartable{width:100%}.path-calendar .calendartable th,.path-calendar .calendartable td{width:14%;text-align:center;vertical-align:top;border:0}.path-calendar .calendar-controls .previous,.path-calendar .calendar-controls .next,.path-calendar .calendar-controls .current{display:block;float:left;width:12%}.path-calendar .calendar-controls .previous{text-align:left}.path-calendar .calendar-controls .current{width:76%;text-align:center}.path-calendar .calendar-controls .next{text-align:right}.path-calendar .filters table{width:100%;border-collapse:separate;border-spacing:2px}.path-calendar .cal_courses_flt{float:left}.path-calendar .cal_courses_flt label{margin-right:.45em}.path-calendar .maincalendar{padding:0;vertical-align:top}.path-calendar .maincalendar .bottom{padding:5px 0 0 0;text-align:center}.path-calendar .maincalendar .heightcontainer{position:relative;height:100%}.path-calendar .maincalendar .calendarmonth{width:98%;margin:10px auto}.path-calendar .maincalendar .calendarmonth ul{margin:0}.path-calendar .maincalendar .calendarmonth ul li{margin-top:4px;list-style-type:none}.path-calendar .maincalendar .calendarmonth td{height:5em}.path-calendar .maincalendar .calendar-controls .previous,.path-calendar .maincalendar .calendar-controls .next{width:30%}.path-calendar .maincalendar .calendar-controls .current{width:39.95%}.path-calendar .maincalendar .controls{width:98%;margin:10px auto}.path-calendar .maincalendar .calendar_event_course,.path-calendar .maincalendar .calendar_event_global,.path-calendar .maincalendar .calendar_event_group,.path-calendar .maincalendar .calendar_event_user{border-style:solid;border-width:1px 1px 1px 12px}.path-calendar .maincalendar .calendar_event_course{border-color:#ffd3bd}.path-calendar .maincalendar .calendar_event_global{border-color:#d6f8cd}.path-calendar .maincalendar .calendar_event_group{border-color:#fee7ae}.path-calendar .maincalendar .calendar_event_user{border-color:#dce7ec}.path-calendar .maincalendar .calendar-event-panel{background-color:#eee;border:2px solid #eee}.path-calendar .maincalendar .calendar-event-panel .yui3-overlay-content{padding:19px;background-color:#fdfdfd;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.path-calendar .maincalendar .calendar-controls .current{font-family:inherit;font-size:25px;font-weight:bold;line-height:1.2;color:inherit}.path-calendar .maincalendar .calendartable td,.path-calendar .maincalendar .calendartable li{padding:5px}.path-calendar .maincalendar .calendartable li{padding-left:10px;text-align:left}.path-calendar .maincalendar .header{overflow:hidden}.path-calendar .maincalendar .header .buttons{fl