--- /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;
}
}
/**
- * Html to add a button for adding a new rul.
+ * Html to add a button for adding a new rule.
*
* @param int $courseid course id.
*
$addurl = new \moodle_url($CFG->wwwroot. '/admin/tool/monitor/edit.php', array('courseid' => $courseid));
return \html_writer::link($addurl, $button);
}
+
+ /**
+ * Html to add a link to go to the subscription page.
+ *
+ * @param moodle_url $manageurl The url of the subscription page.
+ *
+ * @return string html for the link to the subscription page.
+ */
+ public function render_subscriptions_link($manageurl) {
+ echo \html_writer::start_div();
+ $a = \html_writer::link($manageurl, get_string('managesubscriptions', 'tool_monitor'));
+ $link = \html_writer::tag('span', get_string('managesubscriptionslink', 'tool_monitor', $a));
+ echo $link;
+ echo \html_writer::end_div();
+ }
}
return $o;
}
+
+ /**
+ * Html to add a link to go to the rule manager page.
+ *
+ * @param moodle_url $ruleurl The url of the rule manager page.
+ *
+ * @return string html for the link to the rule manager page.
+ */
+ public function render_rules_link($ruleurl) {
+ echo \html_writer::start_div();
+ $a = \html_writer::link($ruleurl, get_string('managerules', 'tool_monitor'));
+ $link = \html_writer::tag('span', get_string('manageruleslink', 'tool_monitor', $a));
+ echo $link;
+ echo \html_writer::end_div();
+ }
}
$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;
}
/**
echo $link;
}
echo html_writer::end_div();
+} else if (has_capability('tool/monitor:managerules', $context)) {
+ $manageurl = new moodle_url("/admin/tool/monitor/managerules.php", array('courseid' => $courseid));
+ echo $renderer->render_rules_link($manageurl);
}
echo $OUTPUT->footer();
$string['duplicaterule'] = 'Duplicate rule';
$string['editrule'] = 'Edit rule';
$string['eventnotfound'] = 'Event not found';
+$string['eventrulecreated'] = 'Rule created';
+$string['eventruledeleted'] = 'Rule deleted';
+$string['eventruleupdated'] = 'Rule updated';
+$string['eventsubcreated'] = 'Subscription created';
+$string['eventsubcriteriamet'] = 'Subscription criteria met';
+$string['eventsubdeleted'] = 'Subscription deleted';
$string['errorincorrectevent'] = 'Please select an event related to the selected plugin';
$string['freqdesc'] = '{$a->freq} times in {$a->mins} minutes';
$string['frequency'] = 'Frequency';
$string['invalidmodule'] = 'Invalid module';
$string['norules'] = 'There are no rules you can subscribe to.';
$string['manageruleslink'] = 'You can manage rules from {$a} page.';
+$string['managesubscriptionslink'] = 'You can subscribe to rules from the {$a} page.';
$string['moduleinstance'] = 'Module instance';
$string['manage'] = 'Manage';
$string['managesubscriptions'] = 'Event monitoring';
$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}"?';
$renderable = new \tool_monitor\output\managerules\renderable('toolmonitorrules', $manageurl, $courseid);
$renderer = $PAGE->get_renderer('tool_monitor', 'managerules');
echo $renderer->render($renderable);
+if (has_capability('tool/monitor:subscribe', $context)) {
+ $manageurl = new moodle_url("/admin/tool/monitor/index.php", array('courseid' => $courseid));
+ echo $renderer->render_subscriptions_link($manageurl);
+}
echo $OUTPUT->footer();
When I follow "Messages"
And I follow "Do not reply to this email (1)"
Then I should see "The course was viewed."
+
+ Scenario: Navigating via quick link to rules
+ Given I log in as "admin"
+ And I navigate to "Event monitoring" node in "My profile settings"
+ Then I should see "You can manage rules from Event monitoring rules page."
+ And I follow "Event monitoring rules"
+ And I should see "Event monitor"
+ And I should see "You can subscribe to rules from the Event monitoring page."
+ And I log out
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I navigate to "Event monitoring" node in "My profile settings"
+ Then I should not see "You can manage rules from Event monitoring rules page."
$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
*/
$gradeletter = get_string('gradeletter', 'grades');
$gradeboundary = get_string('gradeboundary', 'grades');
- $percentages = array(-1 => get_string('unused', 'grades'));
- for ($i=100; $i > -1; $i--) {
- $percentages[$i] = "$i %";
- }
-
- for($i=1; $i<$num+1; $i++) {
+ for ($i=1; $i<$num+1; $i++) {
$gradelettername = 'gradeletter'.$i;
$gradeboundaryname = 'gradeboundary'.$i;
- $mform->addElement('text', $gradelettername, $gradeletter." $i");
- if ($i == 1) {
- $mform->addHelpButton($gradelettername, 'gradeletter', 'grades');
- }
+ $entry = array();
+ $entry[] = $mform->createElement('text', $gradelettername, $gradeletter . " $i");
$mform->setType($gradelettername, PARAM_TEXT);
if (!$admin) {
$mform->disabledIf($gradelettername, 'override', 'notchecked');
- $mform->disabledIf($gradelettername, $gradeboundaryname, 'eq', -1);
}
- $mform->addElement('select', $gradeboundaryname, $gradeboundary." $i", $percentages);
- if ($i == 1) {
- $mform->addHelpButton($gradeboundaryname, 'gradeboundary', 'grades');
- }
- $mform->setDefault($gradeboundaryname, -1);
- $mform->setType($gradeboundaryname, PARAM_INT);
+ $entry[] = $mform->createElement('static', '', '', '≥');
+ $entry[] = $mform->createElement('text', $gradeboundaryname, $gradeboundary." $i");
+ $entry[] = $mform->createElement('static', '', '', '%');
+ $mform->addGroup($entry, 'gradeentry'.$i, $gradeletter." $i", array(' '), false);
+
+ $mform->setType($gradeboundaryname, PARAM_FLOAT);
if (!$admin) {
$mform->disabledIf($gradeboundaryname, 'override', 'notchecked');
}
}
+ if ($num > 0) {
+ $mform->addHelpButton('gradeentry1', 'gradeletter', 'grades');
+ }
+
// hidden params
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
if ($letter == '') {
continue;
}
- $letters[$data->$gradeboundaryname] = $letter;
+ // The keys need to be strings so floats are not truncated.
+ $letters[strval($data->$gradeboundaryname)] = $letter;
}
}
krsort($letters, SORT_NUMERIC);
$returnval .= print_natural_aggregation_upgrade_notice($courseid,
context_course::instance($courseid),
- '/grade/report/' . $active_plugin . '/index.php',
+ $PAGE->url,
$return);
if ($return) {
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-файла';
$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).
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 {
$user = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($user->id, $course->id);
$quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
+ $quizitemparams = array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id,
+ 'courseid' => $course->id);
+ $gradeitem = grade_item::fetch($quizitemparams);
+ $courseitem = grade_item::fetch_course_item($course->id);
// Now mark the quiz using grade_update as this is the function that modules use.
$grade = array();
$grade['userid'] = $user->id;
- $grade['rawgrade'] = 50;
+ $grade['rawgrade'] = 60;
$sink = $this->redirectEvents();
grade_update('mod/quiz', $course->id, 'mod', 'quiz', $quiz->id, 0, $grade);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- // Ensure we have a user_graded event.
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
- // Get the grade item.
- $gradeitem = grade_item::fetch(array('itemtype' => 'mod', 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id,
- 'courseid' => $course->id));
-
- // Let's alter the grade in the DB so when we call regrade_final_grades() it is changed and an event is called.
- $sql = "UPDATE {grade_grades}
- SET finalgrade = '2'
- WHERE itemid = :itemid
- AND userid = :userid";
- $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id));
-
- // Now check when we regrade this that there is a user graded event.
- $sink = $this->redirectEvents();
- $gradeitem->regrade_final_grades();
- $events = $sink->get_events();
- $event = reset($events);
- $sink->close();
-
- // Ensure we have a user_graded event.
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
-
- // Remove the grades.
+ // Remove the grades, force the regrading and re-fetch the item. This is needed because the item
+ // will be set as needing an update when the grades are deleted.
$gradeitem->delete_all_grades();
+ grade_regrade_final_grades($course->id);
+ $gradeitem = grade_item::fetch($quizitemparams);
- // Now, create a grade using update_raw_grade().
+ // Now, create a grade using grade_item::update_final_grade().
$sink = $this->redirectEvents();
- $gradeitem->update_raw_grade($user->id, 50);
+ $gradeitem->update_raw_grade($user->id, 10);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- // Ensure we have a user_graded event.
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
- // Now, update this grade using update_raw_grade().
+ // Now, update this grade using grade_item::update_raw_grade().
$sink = $this->redirectEvents();
- $gradeitem->update_raw_grade($user->id, 100);
+ $gradeitem->update_raw_grade($user->id, 20);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
- // Remove the grades.
+ // Remove the grades, force the regrading and re-fetch the item. This is needed because the item
+ // will be set as needing an update when the grades are deleted.
$gradeitem->delete_all_grades();
+ grade_regrade_final_grades($course->id);
+ $gradeitem = grade_item::fetch($quizitemparams);
- // Now, create a grade using update_final_grade().
+ // Now, create a grade using grade_item::update_final_grade().
$sink = $this->redirectEvents();
- $gradeitem->update_final_grade($user->id, 50);
+ $gradeitem->update_final_grade($user->id, 30);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- // Ensure we have a user_graded event.
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
- // Now, update this grade using update_final_grade().
+ // Now, update this grade using grade_item::update_final_grade().
$sink = $this->redirectEvents();
- $gradeitem->update_final_grade($user->id, 100);
+ $gradeitem->update_final_grade($user->id, 40);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
+
+ // Remove the overridden flag from the grade, this was set by grade_item::update_final_grade().
+ $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id));
+ $gradegrade->set_overridden(false, false);
// Let's change the calculation to anything that won't cause an error.
$calculation = calc_formula::unlocalize("=3");
$gradeitem->set_calculation($calculation);
- // Let's alter the grade in the DB so when we call compute() it is changed and an event is called.
- $sql = "UPDATE {grade_grades}
- SET finalgrade = 2, overridden = 0
- WHERE itemid = :itemid
- AND userid = :userid";
- $DB->execute($sql, array('itemid' => $gradeitem->id, 'userid' => $user->id));
+ // Now force the computation of the grade.
+ $sink = $this->redirectEvents();
+ grade_regrade_final_grades($course->id);
+ $events = $sink->get_events();
+ $sink->close();
+
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
+
+ // Now, let's trick the gradebook, we manually update a grade, and flag the grade item as
+ // needing a regrading, so we can trigger the event in grade_item::regrade_final_grades().
+ $gradeitem = grade_item::fetch($quizitemparams);
+ $gradeitem->set_calculation('');
+ $gradegrade = grade_grade::fetch(array('itemid' => $gradeitem->id, 'userid' => $user->id));
+ $gradegrade->rawgrade = 50;
+ $gradegrade->update();
- // Now check when we compute that there is a user graded event.
$sink = $this->redirectEvents();
- $gradeitem->compute();
+ grade_regrade_final_grades($course->id);
$events = $sink->get_events();
- $event = reset($events);
$sink->close();
- $this->assertEquals(1, count($events));
- $this->assertInstanceOf('\core\event\user_graded', $event);
+ // Ensure we have two user_graded events, one for the item, one for the course.
+ $this->assertEquals(2, count($events));
+ $this->assertInstanceOf('\core\event\user_graded', $events[0]);
+ $this->assertEquals($gradeitem->id, $events[0]->other['itemid']);
+ $this->assertInstanceOf('\core\event\user_graded', $events[1]);
+ $this->assertEquals($courseitem->id, $events[1]->other['itemid']);
}
}
$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;
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.