--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?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));
+ }
+}
--- /dev/null
+<?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'.";
+ }
+}
--- /dev/null
+<?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.');
+ }
+ }
+}
--- /dev/null
+<?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'.";
+ }
+}
*/
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);
}
}
$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)) {
$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()));
$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;
}
$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);
}
* 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;
}
/**
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;
}
/**
}
$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;
}
/**
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;
}
/**
* 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;
}
/**
$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['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}"?';
*/
function tool_monitor_extend_navigation_user_settings($navigation, $user, $usercontext, $course, $coursecontext) {
global $USER;
- if (($USER->id == $user->id)) {
+ if (($USER->id == $user->id) && (has_capability('tool/monitor:subscribe', $coursecontext))) {
$url = new moodle_url('/admin/tool/monitor/index.php', array('courseid' => $course->id));
$subsnode = navigation_node::create(get_string('managesubscriptions', 'tool_monitor'), $url,
navigation_node::TYPE_SETTING, null, null, new pix_icon('i/settings', ''));
$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.
$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;
// 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.
}
$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.
$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;
$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;
$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;
--- /dev/null
+<?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);
+ }
+}
$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++) {
$ruleids[] = $rule->id;
$rule = $monitorgenerator->create_rule($record2); // Create rules in a different course.
}
- $ruledata = \tool_monitor\rule_manager::get_rules_by_courseid(3);
+ $ruledata = \tool_monitor\rule_manager::get_rules_by_courseid($course1->id);
$this->assertEquals($ruleids, array_keys($ruledata));
$this->assertCount(20, $ruledata);
}
\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();
*/
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;
*/
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);
*/
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.
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().
*
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
*/
$returnval .= print_natural_aggregation_upgrade_notice($courseid,
context_course::instance($courseid),
- '/grade/report/' . $active_plugin . '/index.php',
+ $PAGE->url,
$return);
if ($return) {
$grade->feedbackformat = $grade_grades[$userid]->feedbackformat;
$grade->usermodified = $grade_grades[$userid]->usermodified;
$grade->dategraded = $grade_grades[$userid]->get_dategraded();
+ $grade->datesubmitted = $grade_grades[$userid]->get_datesubmitted();
// create text representation of grade
if ($grade_item->needsupdate) {
}
$USER->grade_last_report[$course->id] = 'history';
-$select = "itemtype != 'course' AND itemname != '' AND courseid = :courseid";
+$select = "itemtype <> 'course' AND courseid = :courseid AND " . $DB->sql_isnotempty('grade_items', 'itemname', true, true);
$itemids = $DB->get_records_select_menu('grade_items', $select, array('courseid' => $course->id), 'itemname ASC', 'id, itemname');
$itemids = array(0 => get_string('allgradeitems', 'gradereport_history')) + $itemids;
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
}
}
+ // 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
/**
$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;
$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
// 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
$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.
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;
}
}
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"
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 | 0.00 % |
+ | 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
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"
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 % |
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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
# 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 | - |
# 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 | - |
| 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 % |
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 % |
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"
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 % | - |
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 % |
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"
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 |
// Grade above max. Should be pulled down to max.
$toobig = 200.00;
$data->grade[$student->id][$forum1->id] = $toobig;
+ $data->timepageload = time();
$warnings = $report->process_data($data);
$this->assertEquals(count($warnings), 1);
// Grade below min. Should be pulled up to min.
$toosmall = -10.00;
$data->grade[$student->id][$forum1->id] = $toosmall;
+ $data->timepageload = time();
$warnings = $report->process_data($data);
$this->assertEquals(count($warnings), 1);
$CFG->unlimitedgrades = 1;
$data->grade[$student->id][$forum1->id] = $toobig;
+ $data->timepageload = time();
$warnings = $report->process_data($data);
$this->assertEquals(count($warnings), 0);
$string['downloadedfilecheckfailed'] = 'Sjekk av nedlastet fil mislykkes.';
$string['invalidmd5'] = 'Ugyldig md5, prøv igjen';
$string['missingrequiredfield'] = 'Noen påkrevde felt mangler';
-$string['remotedownloaderror'] = 'Mislykkes i å laste ned komponenten til din server, vennligst sjekk proxy-innstillingene. PHP cURL tillegget er sterkt anbefalt. <br /><br />Du må laste ned <a href="{$a->url}">{$a->url}</a> filen manuelt, kopiere den til "{$a->dest}" på serveren din og pakke den ut der.';
+$string['remotedownloaderror'] = '<p>Mislykkes i å laste ned komponenten til din server, vennligst sjekk proxy-innstillingene. PHP cURL tillegget er sterkt anbefalt. </p>
+<p>Du må laste ned <a href="{$a->url}">{$a->url}</a> filen manuelt, kopiere den til "{$a->dest}" på serveren din og pakke den ut der.</p>';
$string['wrongdestpath'] = 'Gal målmappe';
$string['wrongsourcebase'] = 'Feil kilde URL base';
$string['wrongzipfilename'] = 'Galt ZIP-filnavn.';
$string['chooselanguagehead'] = 'Velg et språk';
$string['chooselanguagesub'] = 'Velg språk (bare for INSTALLASJONEN). Du vil kunne velge språk for nettsted og bruker på et skjermbilde senere.';
$string['clialreadyconfigured'] = 'Filen config.php finnes allerede. Vennligst bruk admin/cli/install_database.php hvis du vil installere denne portalen.';
-$string['clialreadyinstalled'] = 'Filen config.php eksisterer allerede. Vennligst bruk admin/cli/upgrade.php hvis du vil oppgradere denne portalen.';
+$string['clialreadyinstalled'] = 'Filen config.php eksisterer allerede. Vennligst bruk admin/cli/install_database.php hvis du vil oppgradere Moodle på denne portalen.';
$string['cliinstallheader'] = 'Moodle {$a} kommandolinje installasjonsprogram';
$string['databasehost'] = 'Databasevert';
$string['databasename'] = 'Databasenavn';
$string['pathsroparentdataroot'] = 'Overordnet katalog ({$a->parent}) er ikke skrivbar. Datakatalogen ({$a->dataroot}) kan ikke opprettes av installasjonsprogrammet.';
$string['pathssubadmindir'] = 'Noen ganske få webhoteller bruker /admin som en egen url for å få tilgang til et kontrollpanel. Dessverre kommer det i konflikt med standard lokalisering av Moodle sine admin-sider. Du kan fikse dette ved å endre navn på admin-mappen og deretter oppgi dette navnet her. F.eks. <em>moodleadmin</em>. Dette vil fikse adminlenkene i Moodle.';
$string['pathssubdataroot'] = 'Du trenger et sted hvor Moodle kan lagre opplastede filer. Denne mappen må være med lese og skriverettigheter for webserver-brukeren (veldig ofte \'nobody\' eller \'apache\'), men denne mappen må IKKE være direkte tilgjengelig via web. Installasjonsprogrammet vil forsøke å opprette den om den ikke finnes fra før.';
-$string['pathssubdirroot'] = 'Full mappesti til moodleinstallasjonen.';
+$string['pathssubdirroot'] = '<p>Full mappesti til moodleinstallasjonen.</p>';
$string['pathssubwwwroot'] = 'Full webadresse til der hvor Moodle skal vises. Det er ikke mulig å bruke Moodle med mer enn en adresse. Dersom portalen din har flere webadresser må du bruke videresending for til den webadressen du oppgir her. Dersom portalen din er tilgjengelig både fra intranett og internett, skal du oppgi den offentlige internettadressen her og sette opp DNS slik at intranettbrukerne også benytter denne offisielle adressen.
Dersom adressen ikke er korrekt, vennligst endre URL i nettleseren slik at at installasjonen restartes med andre verdier.';
$string['pathsunsecuredataroot'] = 'Dataroot plassering er ikke sikker';
$string['downloadedfilecheckfailed'] = 'Ошибка проверки загруженного файла';
$string['invalidmd5'] = 'Некорректная md5';
$string['missingrequiredfield'] = 'Отсутствуют некоторые обязательные поля';
-$string['remotedownloaderror'] = 'Не удалось загрузить компонент на сервер, проверьте настройки прокси-сервера, настоятельно рекомендуется установка расширения cURL языка PHP.<br /> <br />Вам следует вручную загрузить файл по ссылке <a href="{$a->url}">{$a->url}</a>, скопировать его в папку «{$a->dest}» на своем сервере и там его распаковать.';
+$string['remotedownloaderror'] = '<p>Не удалось загрузить компонент на сервер. Проверьте настройки прокси-сервера; настоятельно рекомендуется установка расширения PHP cURL.</p>
+<p>Вам следует вручную загрузить файл по ссылке <a href="{$a->url}">{$a->url}</a>, скопировать его в папку «{$a->dest}» на своем сервере и там его распаковать.</p>';
$string['wrongdestpath'] = 'Ошибочный путь назначения';
$string['wrongsourcebase'] = 'Неправильный адрес источника';
$string['wrongzipfilename'] = 'Неверное имя ZIP-файла';
'<div class="form-file defaultsnext"><input type="text" size="'.$this->size.'" id="'.$this->get_id().'" name="'.$this->get_full_name().'" value="'.s($data).'" />'.$executable.'</div>',
$this->description, true, '', $default, $query);
}
+
/**
- * checks if execpatch has been disabled in config.php
+ * Checks if execpatch has been disabled in config.php
*/
public function write_setting($data) {
global $CFG;
if (!empty($CFG->preventexecpath)) {
- return '';
+ if ($this->get_setting() === null) {
+ // Use default during installation.
+ $data = $this->get_defaultsetting();
+ if ($data === null) {
+ $data = '';
+ }
+ } else {
+ return '';
+ }
}
return parent::write_setting($data);
}
require_once("$CFG->libdir/externallib.php");
require_once("$CFG->libdir/gradelib.php");
+require_once("$CFG->dirroot/grade/querylib.php");
/**
* core grades functions
$params = self::validate_parameters(self::get_grades_parameters(),
array('courseid' => $courseid, 'component' => $component, 'activityid' => $activityid, 'userids' => $userids));
+ $gradesarray = array(
+ 'items' => array(),
+ 'outcomes' => array()
+ );
+
$coursecontext = context_course::instance($params['courseid']);
try {
$itemtype = null;
$itemmodule = null;
+ $iteminstance = null;
+
if (!empty($params['component'])) {
list($itemtype, $itemmodule) = normalize_component($params['component']);
}
$cm = null;
- if (!empty($itemmodule) && !empty($activityid)) {
- if (! $cm = get_coursemodule_from_id($itemmodule, $activityid)) {
+ if (!empty($itemmodule) && !empty($params['activityid'])) {
+ if (!$cm = get_coursemodule_from_id($itemmodule, $params['activityid'])) {
throw new moodle_exception('invalidcoursemodule');
}
+ $iteminstance = $cm->instance;
}
- $cminstanceid = null;
- if (!empty($cm)) {
- $cminstanceid = $cm->instance;
- }
- $grades = grade_get_grades($params['courseid'], $itemtype, $itemmodule, $cminstanceid, $params['userids']);
+ // Load all the module info.
+ $modinfo = get_fast_modinfo($params['courseid']);
+ $activityinstances = $modinfo->get_instances();
- $acitivityinstances = null;
- if (empty($cm)) {
- // If we're dealing with multiple activites load all the module info.
- $modinfo = get_fast_modinfo($params['courseid']);
- $acitivityinstances = $modinfo->get_instances();
+ $gradeparams = array('courseid' => $params['courseid']);
+ if (!empty($itemtype)) {
+ $gradeparams['itemtype'] = $itemtype;
+ }
+ if (!empty($itemmodule)) {
+ $gradeparams['itemmodule'] = $itemmodule;
+ }
+ if (!empty($iteminstance)) {
+ $gradeparams['iteminstance'] = $iteminstance;
}
- foreach ($grades->items as $gradeitem) {
- if (!empty($cm)) {
- // If they only requested one activity we will already have the cm.
- $modulecm = $cm;
- } else if (!empty($gradeitem->itemmodule)) {
- $modulecm = $acitivityinstances[$gradeitem->itemmodule][$gradeitem->iteminstance];
- } else {
- // Course grade item.
- continue;
- }
+ if ($activitygrades = grade_item::fetch_all($gradeparams)) {
+ $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
- // Make student feedback ready for output.
- foreach ($gradeitem->grades as $studentgrade) {
- if (!empty($studentgrade->feedback)) {
- list($studentgrade->feedback, $categoryinfo->feedbackformat) =
- external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
- $modulecm->id, $params['component'], 'feedback', null);
- }
- }
- }
+ foreach ($activitygrades as $activitygrade) {
- // Convert from objects to arrays so all web service clients are supported.
- // While we're doing that we also remove grades the current user can't see due to hiding.
- $gradesarray = array();
- $canviewhidden = has_capability('moodle/grade:viewhidden', context_course::instance($params['courseid']));
-
- $gradesarray['items'] = array();
- foreach ($grades->items as $gradeitem) {
- // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
- $gradeiteminstance = self::get_grade_item(
- $course->id, $gradeitem->itemtype, $gradeitem->itemmodule, $gradeitem->iteminstance, 0);
- if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
- continue;
- }
+ if ($activitygrade->itemtype != 'course' and $activitygrade->itemtype != 'mod') {
+ // This function currently only supports course and mod grade items. Manual and category not supported.
+ continue;
+ }
- // Format mixed bool/integer parameters.
- $gradeitem->hidden = (empty($gradeitem->hidden)) ? 0 : $gradeitem->hidden;
- $gradeitem->locked = (empty($gradeitem->locked)) ? 0 : $gradeitem->locked;
-
- $gradeitemarray = (array)$gradeitem;
- $gradeitemarray['grades'] = array();
-
- if (!empty($gradeitem->grades)) {
- foreach ($gradeitem->grades as $studentid => $studentgrade) {
- if (!$canviewhidden) {
- // Need to load the grade_grade object to check visibility.
- $gradegradeinstance = grade_grade::fetch(
- array(
- 'userid' => $studentid,
- 'itemid' => $gradeiteminstance->id
- )
- );
- // The grade grade may be legitimately missing if the student has no grade.
- if (!empty($gradegradeinstance) && $gradegradeinstance->is_hidden()) {
- continue;
- }
- }
+ if ($activitygrade->itemtype == 'course') {
+ $item = grade_get_course_grades($course->id, $params['userids']);
+ $item->itemnumber = 0;
- // Format mixed bool/integer parameters.
- $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
- $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
- $studentgrade->overridden = (empty($studentgrade->overridden)) ? 0 : $studentgrade->overridden;
+ $grades = new stdClass;
+ $grades->items = array($item);
+ $grades->outcomes = array();
- $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
- // Add the student ID as some WS clients can't access the array key.
- $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
+ } else {
+ $cm = $activityinstances[$activitygrade->itemmodule][$activitygrade->iteminstance];
+ $instance = $cm->instance;
+ $grades = grade_get_grades($params['courseid'], $activitygrade->itemtype,
+ $activitygrade->itemmodule, $instance, $params['userids']);
}
- }
- // If they requested grades for multiple activities load the cm object now.
- $modulecm = $cm;
- if (empty($modulecm) && !empty($gradeiteminstance->itemmodule)) {
- $modulecm = $acitivityinstances[$gradeiteminstance->itemmodule][$gradeiteminstance->iteminstance];
- }
- if ($gradeiteminstance->itemtype == 'course') {
- $gradesarray['items']['course'] = $gradeitemarray;
- $gradesarray['items']['course']['activityid'] = 'course';
- } else {
- $gradesarray['items'][$modulecm->id] = $gradeitemarray;
- // Add the activity ID as some WS clients can't access the array key.
- $gradesarray['items'][$modulecm->id]['activityid'] = $modulecm->id;
- }
- }
+ // Convert from objects to arrays so all web service clients are supported.
+ // While we're doing that we also remove grades the current user can't see due to hiding.
+ foreach ($grades->items as $gradeitem) {
+ // Switch the stdClass instance for a grade item instance so we can call is_hidden() and use the ID.
+ $gradeiteminstance = self::get_grade_item(
+ $course->id, $activitygrade->itemtype, $activitygrade->itemmodule, $activitygrade->iteminstance, 0);
+ if (!$canviewhidden && $gradeiteminstance->is_hidden()) {
+ continue;
+ }
- $gradesarray['outcomes'] = array();
- foreach ($grades->outcomes as $outcome) {
- $modulecm = $cm;
- if (empty($modulecm)) {
- $modulecm = $acitivityinstances[$outcome->itemmodule][$outcome->iteminstance];
- }
+ // Format mixed bool/integer parameters.
+ $gradeitem->hidden = (empty($gradeitem->hidden)) ? 0 : $gradeitem->hidden;
+ $gradeitem->locked = (empty($gradeitem->locked)) ? 0 : $gradeitem->locked;
+
+ $gradeitemarray = (array)$gradeitem;
+ $gradeitemarray['grades'] = array();
- // Format mixed bool/integer parameters.
- $outcome->hidden = (empty($outcome->hidden)) ? 0 : $outcome->hidden;
- $outcome->locked = (empty($outcome->locked)) ? 0 : $outcome->locked;
-
- $gradesarray['outcomes'][$modulecm->id] = (array)$outcome;
- $gradesarray['outcomes'][$modulecm->id]['activityid'] = $modulecm->id;
-
- $gradesarray['outcomes'][$modulecm->id]['grades'] = array();
- if (!empty($outcome->grades)) {
- foreach ($outcome->grades as $studentid => $studentgrade) {
- if (!$canviewhidden) {
- // Need to load the grade_grade object to check visibility.
- $gradeiteminstance = self::get_grade_item(
- $course->id, $outcome->itemtype, $outcome->itemmodule, $outcome->iteminstance, $outcome->itemnumber);
- $gradegradeinstance = grade_grade::fetch(
- array(
- 'userid' => $studentid,
- 'itemid' => $gradeiteminstance->id
- )
- );
- // The grade grade may be legitimately missing if the student has no grade.
- if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
- continue;
+ if (!empty($gradeitem->grades)) {
+ foreach ($gradeitem->grades as $studentid => $studentgrade) {
+ if (!$canviewhidden) {
+ // Need to load the grade_grade object to check visibility.
+ $gradegradeinstance = grade_grade::fetch(
+ array(
+ 'userid' => $studentid,
+ 'itemid' => $gradeiteminstance->id
+ )
+ );
+ // The grade grade may be legitimately missing if the student has no grade.
+ if (!empty($gradegradeinstance) && $gradegradeinstance->is_hidden()) {
+ continue;
+ }
+ }
+
+ // Format mixed bool/integer parameters.
+ $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
+ $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
+ $studentgrade->overridden = (empty($studentgrade->overridden)) ? 0 : $studentgrade->overridden;
+
+ if ($gradeiteminstance->itemtype != 'course' and !empty($studentgrade->feedback)) {
+ list($studentgrade->feedback, $studentgrade->feedbackformat) =
+ external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
+ $cm->id, $params['component'], 'feedback', null);
+ }
+
+ $gradeitemarray['grades'][$studentid] = (array)$studentgrade;
+ // Add the student ID as some WS clients can't access the array key.
+ $gradeitemarray['grades'][$studentid]['userid'] = $studentid;
}
}
- // Format mixed bool/integer parameters.
- $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
- $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
-
- $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid] = (array)$studentgrade;
+ if ($gradeiteminstance->itemtype == 'course') {
+ $gradesarray['items']['course'] = $gradeitemarray;
+ $gradesarray['items']['course']['activityid'] = 'course';
+ } else {
+ $gradesarray['items'][$cm->id] = $gradeitemarray;
+ // Add the activity ID as some WS clients can't access the array key.
+ $gradesarray['items'][$cm->id]['activityid'] = $cm->id;
+ }
+ }
- // Add the student ID into the grade structure as some WS clients can't access the key.
- $gradesarray['outcomes'][$modulecm->id]['grades'][$studentid]['userid'] = $studentid;
+ foreach ($grades->outcomes as $outcome) {
+ // Format mixed bool/integer parameters.
+ $outcome->hidden = (empty($outcome->hidden)) ? 0 : $outcome->hidden;
+ $outcome->locked = (empty($outcome->locked)) ? 0 : $outcome->locked;
+
+ $gradesarray['outcomes'][$cm->id] = (array)$outcome;
+ $gradesarray['outcomes'][$cm->id]['activityid'] = $cm->id;
+
+ $gradesarray['outcomes'][$cm->id]['grades'] = array();
+ if (!empty($outcome->grades)) {
+ foreach ($outcome->grades as $studentid => $studentgrade) {
+ if (!$canviewhidden) {
+ // Need to load the grade_grade object to check visibility.
+ $gradeiteminstance = self::get_grade_item($course->id, $activitygrade->itemtype,
+ $activitygrade->itemmodule, $activitygrade->iteminstance,
+ $activitygrade->itemnumber);
+ $gradegradeinstance = grade_grade::fetch(
+ array(
+ 'userid' => $studentid,
+ 'itemid' => $gradeiteminstance->id
+ )
+ );
+ // The grade grade may be legitimately missing if the student has no grade.
+ if (!empty($gradegradeinstance ) && $gradegradeinstance->is_hidden()) {
+ continue;
+ }
+ }
+
+ // Format mixed bool/integer parameters.
+ $studentgrade->hidden = (empty($studentgrade->hidden)) ? 0 : $studentgrade->hidden;
+ $studentgrade->locked = (empty($studentgrade->locked)) ? 0 : $studentgrade->locked;
+
+ if (!empty($studentgrade->feedback)) {
+ list($studentgrade->feedback, $studentgrade->feedbackformat) =
+ external_format_text($studentgrade->feedback, $studentgrade->feedbackformat,
+ $cm->id, $params['component'], 'feedback', null);
+ }
+
+ $gradesarray['outcomes'][$cm->id]['grades'][$studentid] = (array)$studentgrade;
+
+ // Add the student ID into the grade structure as some WS clients can't access the key.
+ $gradesarray['outcomes'][$cm->id]['grades'][$studentid]['userid'] = $studentid;
+ }
+ }
}
}
}
$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) {
$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).
response_json = JSON.parse(o.responseText);
// Revert untouched editor contents to an empty string.
- if (response_json.result === '<p></p>' || response_json.result === '<p><br></p>') {
+ // Check for FF and Chrome.
+ if (response_json.result === '<p></p>' || response_json.result === '<p><br></p>' ||
+ response_json.result === '<br>') {
+ response_json.result = '';
+ }
+
+ // Check for IE 9 and 10.
+ if (response_json.result === '<p> </p>' || response_json.result === '<p><br> </p>') {
response_json.result = '';
}
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.
} 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 {
}
/**
- * Returns grading information for one or more activities, optionally with user grades
+ * Returns grading information for given activity, optionally with user grades
* Manual, course or category items can not be queried.
*
* @category grade
* @param mixed $userid_or_ids Either a single user ID, an array of user IDs or null. If user ID or IDs are not supplied returns information about grade_item
* @return array Array of grade information objects (scaleid, name, grade and locked status, etc.) indexed with itemnumbers
*/
-function grade_get_grades($courseid, $itemtype = null, $itemmodule = null, $iteminstance = null, $userid_or_ids=null) {
+function grade_get_grades($courseid, $itemtype, $itemmodule, $iteminstance, $userid_or_ids=null) {
global $CFG;
$return = new stdClass();
}
}
- $params = array('courseid' => $courseid);
- if (!empty($itemtype)) {
- $params['itemtype'] = $itemtype;
- }
- if (!empty($itemmodule)) {
- $params['itemmodule'] = $itemmodule;
- }
- if (!empty($iteminstance)) {
- $params['iteminstance'] = $iteminstance;
- }
- if ($grade_items = grade_item::fetch_all($params)) {
+ if ($grade_items = grade_item::fetch_all(array('itemtype'=>$itemtype, 'itemmodule'=>$itemmodule, 'iteminstance'=>$iteminstance, 'courseid'=>$courseid))) {
foreach ($grade_items as $grade_item) {
$decimalpoints = null;
return $count;
}
+
+ public function test_preventexecpath() {
+ $this->resetAfterTest();
+
+ set_config('preventexecpath', 0);
+ set_config('execpath', null, 'abc_cde');
+ $this->assertFalse(get_config('abc_cde', 'execpath'));
+ $setting = new admin_setting_configexecutable('abc_cde/execpath', 'some desc', '', '/xx/yy');
+ $setting->write_setting('/oo/pp');
+ $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
+
+ // Prevent changes.
+ set_config('preventexecpath', 1);
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
+
+ // Use default in install.
+ set_config('execpath', null, 'abc_cde');
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('/xx/yy', get_config('abc_cde', 'execpath'));
+
+ // Use empty value if no default.
+ $setting = new admin_setting_configexecutable('abc_cde/execpath', 'some desc', '', null);
+ set_config('execpath', null, 'abc_cde');
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('', get_config('abc_cde', 'execpath'));
+
+ // This also (most probably incorrectly) affects admin_setting_configfile.
+
+ set_config('preventexecpath', 0);
+ set_config('execpath', null, 'abc_cde');
+ $this->assertFalse(get_config('abc_cde', 'execpath'));
+ $setting = new admin_setting_configfile('abc_cde/execpath', 'some desc', '', '/xx/yy');
+ $setting->write_setting('/oo/pp');
+ $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
+
+ // Prevent changes.
+ set_config('preventexecpath', 1);
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('/oo/pp', get_config('abc_cde', 'execpath'));
+
+ // Use default in install.
+ set_config('execpath', null, 'abc_cde');
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('/xx/yy', get_config('abc_cde', 'execpath'));
+
+ // Use empty value if no default.
+ $setting = new admin_setting_configfile('abc_cde/execpath', 'some desc', '', null);
+ set_config('execpath', null, 'abc_cde');
+ $setting->write_setting('/mm/nn');
+ $this->assertSame('', get_config('abc_cde', 'execpath'));
+
+ }
}
$sink = $this->redirectEvents();
grade_update('mod/quiz', $course->id, 'mod', 'quiz', $quiz->id, 0, $grade);
$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 - for the quiz and for the course total.
+ $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,
$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'),
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;
* Added an extra parameter to the function get_formatted_help_string() (default null) which is used to specify
additional string parameters.
* User settings node and course node in navigation now support callbacks from admin tools.
+* grade_get_grades() optional parameteres $itemtype, $itemmodule, $iteminstance are now required.
DEPRECATIONS:
* completion_info->get_incomplete_criteria() is deprecated and will be removed in Moodle 3.0.
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,
$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;
* @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.
$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);
}
)
),
+ // 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',
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') {
$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';
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);
}
$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);
}
$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) {
--- /dev/null
+@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"
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;
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();
* @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));
if ($pendingstep->response_summary_changed()) {
$this->responsesummary = $pendingstep->get_new_response_summary();
}
+ if ($pendingstep->variant_number_changed()) {
+ $this->variant = $pendingstep->get_new_variant_number();
+ }
}
}
/**
- * 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
$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
*/
/**
- * 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;
$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, ' .
), $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);
));
}
+ /**
+ * 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),
'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);
), $averages);
}
+ /**
+ * This test is executed by {@link test_reporting_queries()}.
+ */
protected function dotest_sum_usage_marks_subquery() {
global $DB;
$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;
--- /dev/null
+<?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));
+ }
+}
$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');
// Print first controls.
report_participation_print_filter_form($course, $timefrom, $minlog, $action, $roleid, $instanceid);
-$baseurl = $CFG->wwwroot.'/report/participation/index.php?id='.$course->id.'&roleid='
- .$roleid.'&instanceid='.$instanceid.'&timefrom='.$timefrom.'&action='.$action.'&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.
$table->print_html();
if ($perpage == SHOW_ALL_PAGE_SIZE) {
- echo '<div id="showall"><a href="'.$baseurl.'&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.'&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">';
<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>
$joins = array("FROM {user} u");
$wheres = array();
-$mainuserfields = user_picture::fields('u', array('username', 'email', 'city', 'country', 'lang', 'timezone', 'maildisplay'));
-$alreadyretrievedfields = explode(',', $mainuserfields);
-$extrasql = get_extra_user_fields_sql($context, 'u', '', $alreadyretrievedfields);
+$userfields = array('username', 'email', 'city', 'country', 'lang', 'timezone', 'maildisplay');
+$mainuserfields = user_picture::fields('u', $userfields);
+$extrasql = get_extra_user_fields_sql($context, 'u', '', $userfields);
if ($isfrontpage) {
$select = "SELECT $mainuserfields, u.lastaccess$extrasql";
defined('MOODLE_INTERNAL') || die();
-$version = 2014102000.00; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2014102200.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
-$release = '2.8beta+ (Build: 20141017)'; // Human-friendly version name
+$release = '2.8beta+ (Build: 20141022)'; // Human-friendly version name
$branch = '28'; // This version's branch.
$maturity = MATURITY_BETA; // This version's maturity level.