Merge remote-tracking branch 'moodle/master' into MDL-20636_master_new_question_engine
authorTim Hunt <T.J.Hunt@open.ac.uk>
Mon, 6 Jun 2011 16:14:59 +0000 (17:14 +0100)
committerTim Hunt <T.J.Hunt@open.ac.uk>
Mon, 6 Jun 2011 16:14:59 +0000 (17:14 +0100)
Conflicts:
lib/db/upgrade.php
mod/quiz/lib.php

290 files changed:
admin/roles/lib.php
backup/moodle2/backup_coursereport_plugin.class.php [new file with mode: 0644]
backup/moodle2/backup_plan_builder.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/restore_coursereport_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_plan_builder.class.php
backup/moodle2/restore_plugin.class.php
backup/moodle2/restore_stepslib.php
backup/util/plan/restore_structure_step.class.php
backup/util/plan/restore_task.class.php
backup/util/xml/parser/processors/grouped_parser_processor.class.php
backup/util/xml/parser/processors/simplified_parser_processor.class.php
backup/util/xml/parser/simpletest/fixtures/test5.xml [new file with mode: 0644]
backup/util/xml/parser/simpletest/testparser.php
blocks/dock.js
blocks/navigation/renderer.php
blocks/navigation/yui/navigation/navigation.js
calendar/export_execute.php
comment/lib.php
course/moodleform_mod.php
lang/en/error.php
lib/db/install.xml
lib/enrollib.php
lib/modinfolib.php
lib/navigationlib.php
lib/outputrenderers.php
lib/pluginlib.php
lib/rsslib.php
lib/simpletest/testnavigationlib.php
lib/statslib.php
lib/xhprof/xhprof_moodle.php
message/lib.php
mod/assignment/lang/en/assignment.php
mod/assignment/lib.php
mod/assignment/styles.css
mod/chat/lib.php
mod/choice/backup/moodle2/backup_choice_stepslib.php
mod/choice/lib.php
mod/choice/renderer.php
mod/data/backup/moodle2/backup_data_stepslib.php
mod/data/backup/moodle2/restore_data_stepslib.php
mod/data/db/upgrade.php
mod/data/lib.php
mod/data/version.php
mod/data/view.php
mod/feedback/db/messages.php
mod/feedback/lib.php
mod/feedback/show_nonrespondents.php
mod/feedback/version.php
mod/folder/lib.php
mod/forum/backup/moodle2/backup_forum_stepslib.php
mod/forum/backup/moodle2/restore_forum_stepslib.php
mod/forum/db/upgrade.php
mod/forum/lib.php
mod/forum/user.php
mod/forum/version.php
mod/forum/view.php
mod/glossary/backup/moodle2/backup_glossary_stepslib.php
mod/glossary/backup/moodle2/restore_glossary_stepslib.php
mod/glossary/db/upgrade.php
mod/glossary/deleteentry.php
mod/glossary/lib.php
mod/glossary/version.php
mod/glossary/view.php
mod/imscp/lib.php
mod/label/lib.php
mod/lesson/essay.php
mod/lesson/lib.php
mod/page/lib.php
mod/quiz/lib.php
mod/resource/lib.php
mod/scorm/lib.php
mod/survey/lib.php
mod/url/lib.php
mod/wiki/lib.php
mod/workshop/lib.php
rating/index.php
rating/lib.php
rating/rate.php
rating/rate_ajax.php
repository/webdav/lib.php
theme/afterburner/config.php [new file with mode: 0644]
theme/afterburner/lang/en/theme_afterburner.php [new file with mode: 0644]
theme/afterburner/layout/default.php [new file with mode: 0644]
theme/afterburner/layout/embedded.php [new file with mode: 0644]
theme/afterburner/pix/core/bg.png [new file with mode: 0644]
theme/afterburner/pix/core/bground.jpg [new file with mode: 0644]
theme/afterburner/pix/core/h2grad.jpg [new file with mode: 0644]
theme/afterburner/pix/favicon.ico [new file with mode: 0644]
theme/afterburner/pix/footer/moodle-logo.png [new file with mode: 0644]
theme/afterburner/pix/forum/gradient.png [new file with mode: 0644]
theme/afterburner/pix/images/light3.png [new file with mode: 0644]
theme/afterburner/pix/menu/ab-arrowover.png [new file with mode: 0644]
theme/afterburner/pix/menu/nav-arrow-right.png [new file with mode: 0644]
theme/afterburner/pix/screenshot.jpg [new file with mode: 0644]
theme/afterburner/pix/sideblocks/sidegrad.jpg [new file with mode: 0644]
theme/afterburner/pix/tab/left.gif [new file with mode: 0644]
theme/afterburner/pix/tab/left_active.gif [new file with mode: 0644]
theme/afterburner/pix/tab/left_active_hover.gif [new file with mode: 0644]
theme/afterburner/pix/tab/left_hover.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right_active.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right_active_hover.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right_end.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right_hover.gif [new file with mode: 0644]
theme/afterburner/pix/tab/right_last.gif [new file with mode: 0644]
theme/afterburner/pix/tab/rtlbg.gif [new file with mode: 0644]
theme/afterburner/pix/tab/tabrow1.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/em1_bwgreater.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/em1_greater.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/em1_lesser.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/em1_raquo.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/help.png [new file with mode: 0644]
theme/afterburner/pix_core/a/l_breadcrumb.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/logout.png [new file with mode: 0644]
theme/afterburner/pix_core/a/r_breadcrumb.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/r_go.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/r_next.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/r_previous.gif [new file with mode: 0644]
theme/afterburner/pix_core/a/refresh.png [new file with mode: 0644]
theme/afterburner/pix_core/a/search.png [new file with mode: 0644]
theme/afterburner/pix_core/a/setting.png [new file with mode: 0644]
theme/afterburner/pix_core/c/course.png [new file with mode: 0644]
theme/afterburner/pix_core/c/event.png [new file with mode: 0644]
theme/afterburner/pix_core/c/groups.png [new file with mode: 0644]
theme/afterburner/pix_core/c/site.png [new file with mode: 0644]
theme/afterburner/pix_core/c/user.png [new file with mode: 0644]
theme/afterburner/pix_core/docs.png [new file with mode: 0644]
theme/afterburner/pix_core/f/access.png [new file with mode: 0644]
theme/afterburner/pix_core/f/avi.png [new file with mode: 0644]
theme/afterburner/pix_core/f/excel.png [new file with mode: 0644]
theme/afterburner/pix_core/f/flash.png [new file with mode: 0644]
theme/afterburner/pix_core/f/folder-32.png [new file with mode: 0644]
theme/afterburner/pix_core/f/folder.png [new file with mode: 0644]
theme/afterburner/pix_core/f/help.png [new file with mode: 0644]
theme/afterburner/pix_core/f/html.png [new file with mode: 0644]
theme/afterburner/pix_core/f/image-32.png [new file with mode: 0644]
theme/afterburner/pix_core/f/image.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odb.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odf.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odg.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odm.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odp.png [new file with mode: 0644]
theme/afterburner/pix_core/f/ods.png [new file with mode: 0644]
theme/afterburner/pix_core/f/odt.png [new file with mode: 0644]
theme/afterburner/pix_core/f/pdf.png [new file with mode: 0644]
theme/afterburner/pix_core/f/powerpoint.png [new file with mode: 0644]
theme/afterburner/pix_core/f/text-32.png [new file with mode: 0644]
theme/afterburner/pix_core/f/text.png [new file with mode: 0644]
theme/afterburner/pix_core/f/unknown-32.png [new file with mode: 0644]
theme/afterburner/pix_core/f/unknown.png [new file with mode: 0644]
theme/afterburner/pix_core/f/video.png [new file with mode: 0644]
theme/afterburner/pix_core/f/web.png [new file with mode: 0644]
theme/afterburner/pix_core/f/word.png [new file with mode: 0644]
theme/afterburner/pix_core/f/xml.png [new file with mode: 0644]
theme/afterburner/pix_core/f/zip.png [new file with mode: 0644]
theme/afterburner/pix_core/g/f1.png [new file with mode: 0644]
theme/afterburner/pix_core/g/f2.png [new file with mode: 0644]
theme/afterburner/pix_core/g/user100.png [new file with mode: 0644]
theme/afterburner/pix_core/g/user35.png [new file with mode: 0644]
theme/afterburner/pix_core/help.png [new file with mode: 0644]
theme/afterburner/pix_core/i/all.png [new file with mode: 0644]
theme/afterburner/pix_core/i/backup.png [new file with mode: 0644]
theme/afterburner/pix_core/i/calc.png [new file with mode: 0644]
theme/afterburner/pix_core/i/checkpermissions.png [new file with mode: 0644]
theme/afterburner/pix_core/i/course.png [new file with mode: 0644]
theme/afterburner/pix_core/i/db.png [new file with mode: 0644]
theme/afterburner/pix_core/i/edit.gif [new file with mode: 0644]
theme/afterburner/pix_core/i/email.png [new file with mode: 0644]
theme/afterburner/pix_core/i/feedback.png [new file with mode: 0644]
theme/afterburner/pix_core/i/feedback_add.png [new file with mode: 0644]
theme/afterburner/pix_core/i/files.png [new file with mode: 0644]
theme/afterburner/pix_core/i/filter.png [new file with mode: 0644]
theme/afterburner/pix_core/i/flagged.png [new file with mode: 0644]
theme/afterburner/pix_core/i/grades.png [new file with mode: 0644]
theme/afterburner/pix_core/i/group.png [new file with mode: 0644]
theme/afterburner/pix_core/i/hide.png [new file with mode: 0644]
theme/afterburner/pix_core/i/info.png [new file with mode: 0644]
theme/afterburner/pix_core/i/lock.png [new file with mode: 0644]
theme/afterburner/pix_core/i/marker.png [new file with mode: 0644]
theme/afterburner/pix_core/i/menu.png [new file with mode: 0644]
theme/afterburner/pix_core/i/new.png [new file with mode: 0644]
theme/afterburner/pix_core/i/news.png [new file with mode: 0644]
theme/afterburner/pix_core/i/one.png [new file with mode: 0644]
theme/afterburner/pix_core/i/payment.png [new file with mode: 0644]
theme/afterburner/pix_core/i/permissions.png [new file with mode: 0644]
theme/afterburner/pix_core/i/publish.png [new file with mode: 0644]
theme/afterburner/pix_core/i/questions.png [new file with mode: 0644]
theme/afterburner/pix_core/i/report.png [new file with mode: 0644]
theme/afterburner/pix_core/i/restore.png [new file with mode: 0644]
theme/afterburner/pix_core/i/return.png [new file with mode: 0644]
theme/afterburner/pix_core/i/roles.png [new file with mode: 0644]
theme/afterburner/pix_core/i/scales.png [new file with mode: 0644]
theme/afterburner/pix_core/i/settings.png [new file with mode: 0644]
theme/afterburner/pix_core/i/show.png [new file with mode: 0644]
theme/afterburner/pix_core/i/unflagged.png [new file with mode: 0644]
theme/afterburner/pix_core/i/user.png [new file with mode: 0644]
theme/afterburner/pix_core/i/users.png [new file with mode: 0644]
theme/afterburner/pix_core/icon.png [new file with mode: 0644]
theme/afterburner/pix_core/m/USD.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/SMILEYS [new file with mode: 0644]
theme/afterburner/pix_core/s/angry.png [new file with mode: 0644]
theme/afterburner/pix_core/s/approve.png [new file with mode: 0644]
theme/afterburner/pix_core/s/biggrin.png [new file with mode: 0644]
theme/afterburner/pix_core/s/blackeye.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/blush.png [new file with mode: 0644]
theme/afterburner/pix_core/s/clown.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/cool.png [new file with mode: 0644]
theme/afterburner/pix_core/s/dead.png [new file with mode: 0644]
theme/afterburner/pix_core/s/egg.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/evil.png [new file with mode: 0644]
theme/afterburner/pix_core/s/heart.png [new file with mode: 0644]
theme/afterburner/pix_core/s/kiss.png [new file with mode: 0644]
theme/afterburner/pix_core/s/martin.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/mixed.png [new file with mode: 0644]
theme/afterburner/pix_core/s/no.gif [new file with mode: 0644]
theme/afterburner/pix_core/s/sad.png [new file with mode: 0644]
theme/afterburner/pix_core/s/shy.png [new file with mode: 0644]
theme/afterburner/pix_core/s/sleepy.png [new file with mode: 0644]
theme/afterburner/pix_core/s/smiley.png [new file with mode: 0644]
theme/afterburner/pix_core/s/surprise.png [new file with mode: 0644]
theme/afterburner/pix_core/s/thoughtful.png [new file with mode: 0644]
theme/afterburner/pix_core/s/tongueout.png [new file with mode: 0644]
theme/afterburner/pix_core/s/wideeyes.png [new file with mode: 0644]
theme/afterburner/pix_core/s/wink.png [new file with mode: 0644]
theme/afterburner/pix_core/s/yes.png [new file with mode: 0644]
theme/afterburner/pix_core/t/adddir.png [new file with mode: 0644]
theme/afterburner/pix_core/t/addfile.png [new file with mode: 0644]
theme/afterburner/pix_core/t/addgreen.png [new file with mode: 0644]
theme/afterburner/pix_core/t/backup.png [new file with mode: 0644]
theme/afterburner/pix_core/t/block.png [new file with mode: 0644]
theme/afterburner/pix_core/t/calendar.png [new file with mode: 0644]
theme/afterburner/pix_core/t/clear.png [new file with mode: 0644]
theme/afterburner/pix_core/t/copy.png [new file with mode: 0644]
theme/afterburner/pix_core/t/delete.png [new file with mode: 0644]
theme/afterburner/pix_core/t/down.png [new file with mode: 0644]
theme/afterburner/pix_core/t/download.png [new file with mode: 0644]
theme/afterburner/pix_core/t/edit.png [new file with mode: 0644]
theme/afterburner/pix_core/t/email.png [new file with mode: 0644]
theme/afterburner/pix_core/t/emailno.png [new file with mode: 0644]
theme/afterburner/pix_core/t/feedback.png [new file with mode: 0644]
theme/afterburner/pix_core/t/feedback_add.png [new file with mode: 0644]
theme/afterburner/pix_core/t/go.png [new file with mode: 0644]
theme/afterburner/pix_core/t/groupn.png [new file with mode: 0644]
theme/afterburner/pix_core/t/groups.png [new file with mode: 0644]
theme/afterburner/pix_core/t/hide.png [new file with mode: 0644]
theme/afterburner/pix_core/t/lock.png [new file with mode: 0644]
theme/afterburner/pix_core/t/move.png [new file with mode: 0644]
theme/afterburner/pix_core/t/preview.png [new file with mode: 0644]
theme/afterburner/pix_core/t/restore.png [new file with mode: 0644]
theme/afterburner/pix_core/t/show.png [new file with mode: 0644]
theme/afterburner/pix_core/t/stop.png [new file with mode: 0644]
theme/afterburner/pix_core/t/unlock.png [new file with mode: 0644]
theme/afterburner/pix_core/t/up.png [new file with mode: 0644]
theme/afterburner/pix_core/t/user.png [new file with mode: 0644]
theme/afterburner/pix_core/t/userblue.png [new file with mode: 0644]
theme/afterburner/pix_core/t/usernot.png [new file with mode: 0644]
theme/afterburner/pix_core/u/f1.png [new file with mode: 0644]
theme/afterburner/pix_core/u/f2.png [new file with mode: 0644]
theme/afterburner/pix_plugins/enrol/self/withoutkey.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/assignment/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/chat/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/choice/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/data/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/folder/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/forum/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/glossary/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/imscp/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/label/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/lesson/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/page/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/quiz/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/resource/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/scorm/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/survey/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/url/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/wiki/icon.png [new file with mode: 0644]
theme/afterburner/pix_plugins/mod/workshop/icon.png [new file with mode: 0644]
theme/afterburner/renderers.php [new file with mode: 0644]
theme/afterburner/style/afterburner_blocks.css [new file with mode: 0644]
theme/afterburner/style/afterburner_calendar.css [new file with mode: 0644]
theme/afterburner/style/afterburner_dock.css [new file with mode: 0644]
theme/afterburner/style/afterburner_layout.css [new file with mode: 0644]
theme/afterburner/style/afterburner_menu.css [new file with mode: 0644]
theme/afterburner/style/afterburner_mod.css [new file with mode: 0644]
theme/afterburner/style/afterburner_styles.css [new file with mode: 0644]
theme/afterburner/style/rtl.css [new file with mode: 0644]
theme/base/style/core.css
theme/canvas/style/core.css
theme/canvas/style/text.css

index 3e9624e..7fc5782 100644 (file)
@@ -1316,6 +1316,7 @@ abstract class role_allow_role_page {
         foreach ($rs as $allow) {
             $this->allowed[$allow->roleid][$allow->{$this->targetcolname}] = true;
         }
+        $rs->close();
     }
 
     /**
diff --git a/backup/moodle2/backup_coursereport_plugin.class.php b/backup/moodle2/backup_coursereport_plugin.class.php
new file mode 100644 (file)
index 0000000..8d89b31
--- /dev/null
@@ -0,0 +1,34 @@
+<?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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Base class for course report backup plugins.
+ *
+ * NOTE: When you back up a course, it potentially may run backup for all
+ * course reports. In order to control whether a particular report gets
+ * backed up, a course report should make use of the second and third
+ * parameters in get_plugin_element().
+ *
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright  2011 onwards The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class backup_coursereport_plugin extends backup_plugin {
+    // Use default parent behaviour
+}
index b85e2b1..3f66183 100644 (file)
@@ -36,6 +36,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_theme_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plagiarism_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_settingslib.php');
index 93e2c0d..a0acb19 100644 (file)
@@ -417,6 +417,10 @@ class backup_course_structure_step extends backup_structure_step {
         // save course data (in case of user theme, legacy theme, etc)
         $this->add_plugin_structure('theme', $course, true);
 
+        // attach course report plugin structure to $course element; multiple
+        // course reports can save course data if required
+        $this->add_plugin_structure('coursereport', $course, true);
+
         // attach plagiarism plugin structure to $course element, only one allowed
         $this->add_plugin_structure('plagiarism', $course, false);
 
diff --git a/backup/moodle2/restore_coursereport_plugin.class.php b/backup/moodle2/restore_coursereport_plugin.class.php
new file mode 100644 (file)
index 0000000..9a94e23
--- /dev/null
@@ -0,0 +1,29 @@
+<?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/>.
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Restore for course plugin: course report.
+ *
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright 2011 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+abstract class restore_coursereport_plugin extends restore_plugin {
+    // Use default parent behaviour
+}
index a2883d0..5232b5b 100644 (file)
@@ -35,11 +35,13 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_qtype_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_theme_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/restore_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_plagiarism_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_qtype_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_format_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_theme_plugin.class.php');
+require_once($CFG->dirroot . '/backup/moodle2/backup_coursereport_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_plagiarism_plugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_settingslib.php');
index 0203987..5ed177e 100644 (file)
@@ -80,6 +80,23 @@ abstract class restore_plugin {
         }
     }
 
+    /**
+     * after_restore dispatcher for any restore_plugin class
+     *
+     * This method will dispatch execution to the corresponding
+     * after_restore_xxx() method when available, with xxx
+     * being the connection point of the instance, so plugin
+     * classes with multiple connection points will support
+     * multiple after_restore methods, one for each connection point
+     */
+    public function launch_after_restore_methods() {
+        // Check if the after_restore method exists and launch it
+        $afterrestore = 'after_restore_' . basename($this->connectionpoint->get_path());
+        if (method_exists($this, $afterrestore)) {
+            $this->$afterrestore();
+        }
+    }
+
     /**
      * Returns one array with all the decode contents
      * to be processed by the links decoder
index 1f3ec0a..d37030b 100644 (file)
@@ -1010,6 +1010,9 @@ class restore_course_structure_step extends restore_structure_step {
         // Apply for 'theme' plugins optional paths at course level
         $this->add_plugin_structure('theme', $course);
 
+        // Apply for 'course report' plugins optional paths at course level
+        $this->add_plugin_structure('coursereport', $course);
+
         // Apply for plagiarism plugins optional paths at course level
         $this->add_plugin_structure('plagiarism', $course);
 
index ae648bc..ddb2fe5 100644 (file)
@@ -360,6 +360,43 @@ abstract class restore_structure_step extends restore_step {
 
     }
 
+    /**
+     * Launch all the after_restore methods present in all the processing objects
+     *
+     * This method will launch all the after_restore methods that can be defined
+     * both in restore_plugin class
+     *
+     * For restore_plugin classes the name of the method to be executed will be
+     * "after_restore_" + connection point (as far as can be multiple connection
+     * points in the same class)
+     */
+    public function launch_after_restore_methods() {
+        $alreadylaunched = array(); // To avoid multiple executions
+        foreach ($this->pathelements as $pathelement) {
+            // Get the processing object
+            $pobject = $pathelement->get_processing_object();
+            // Skip null processors (child of grouped ones for sure)
+            if (is_null($pobject)) {
+                continue;
+            }
+            // Skip restore structure step processors (this)
+            if ($pobject instanceof restore_structure_step) {
+                continue;
+            }
+            // Skip already launched processing objects
+            if (in_array($pobject, $alreadylaunched, true)) {
+                continue;
+            }
+            // Add processing object to array of launched ones
+            $alreadylaunched[] = $pobject;
+            // If the processing object has support for
+            // launching after_restore methods, use it
+            if (method_exists($pobject, 'launch_after_restore_methods')) {
+                $pobject->launch_after_restore_methods();
+            }
+        }
+    }
+
     /**
      * This method will be executed after the whole structure step have been processed
      *
index cdd525d..5b22b4b 100644 (file)
@@ -100,6 +100,13 @@ abstract class restore_task extends base_task {
      * method if available
      */
     public function execute_after_restore() {
+        if ($this->executed) {
+            foreach ($this->steps as $step) {
+                if (method_exists($step, 'launch_after_restore_methods')) {
+                    $step->launch_after_restore_methods();
+                }
+            }
+        }
         if ($this->executed && method_exists($this, 'after_restore')) {
             $this->after_restore();
         }
index ca5d3f4..d8e260a 100644 (file)
@@ -71,20 +71,23 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
     }
 
     /**
-     * Notify start of path if selected and not under grouped
+     * The parser fires this each time one path is going to be parsed
+     *
+     * @param string $path xml path which parsing has started
      */
     public function before_path($path) {
-        if ($this->path_is_selected($path) && !$this->grouped_parent_exists($path)) {
+        if (!$this->grouped_parent_exists($path)) {
             parent::before_path($path);
         }
     }
 
-
     /**
-     * Dispatch grouped chunks safely once their end tag happens.
-     * Also notify end of path if selected and not under grouped
+     * The parser fires this each time one path has been parsed
+     *
+     * @param string $path xml path which parsing has ended
      */
     public function after_path($path) {
+        // Have finished one grouped path, dispatch it
         if ($this->path_is_grouped($path)) {
             // Any accumulated information must be in
             // currentdata, properly built
@@ -95,7 +98,7 @@ abstract class grouped_parser_processor extends simplified_parser_processor {
         }
         // Normal notification of path end
         // Only if path is selected and not child of grouped
-        if ($this->path_is_selected($path) && !$this->grouped_parent_exists($path)) {
+        if (!$this->grouped_parent_exists($path)) {
             parent::after_path($path);
         }
     }
index e4432af..c3c5cdd 100644 (file)
@@ -41,12 +41,14 @@ abstract class simplified_parser_processor extends progressive_parser_processor
     protected $paths;       // array of paths we are interested on
     protected $parentpaths; // array of parent paths of the $paths
     protected $parentsinfo; // array of parent attributes to be added as child tags
+    protected $startendinfo;// array (stack) of startend information
 
-    public function __construct(array $paths) {
+    public function __construct(array $paths = array()) {
         parent::__construct();
         $this->paths = array();
         $this->parentpaths = array();
         $this->parentsinfo = array();
+        $this->startendinfo = array();
         // Add paths and parentpaths. We are looking for attributes there
         foreach ($paths as $key => $path) {
             $this->add_path($path);
@@ -95,6 +97,10 @@ abstract class simplified_parser_processor extends progressive_parser_processor
 
         // If the path is a registered one, let's process it
         if ($this->path_is_selected($path)) {
+
+            // Send all the pending notify_path_start/end() notifications
+            $this->process_pending_startend_notifications($path, 'start');
+
             // First of all, look for attributes available at parentsinfo
             // in order to get them available as normal tags
             if (isset($this->parentsinfo[$parentpath][$tag]['attrs'])) {
@@ -146,29 +152,86 @@ abstract class simplified_parser_processor extends progressive_parser_processor
         } else {
             $this->chunks--; // Chunk skipped
         }
+
         return true;
     }
 
     /**
      * The parser fires this each time one path is going to be parsed
+     *
+     * @param string $path xml path which parsing has started
      */
     public function before_path($path) {
         if ($this->path_is_selected($path)) {
-            $this->notify_path_start($path);
+            $this->startendinfo[] = array('path' => $path, 'action' => 'start');
         }
     }
 
     /**
      * The parser fires this each time one path has been parsed
+     *
+     * @param string $path xml path which parsing has ended
      */
     public function after_path($path) {
+        $toprocess = false;
+        // If the path being closed matches (same or parent) the first path in the stack
+        // we process pending startend notifications until one matching end is found
+        if ($element = reset($this->startendinfo)) {
+            $elepath = $element['path'];
+            $eleaction = $element['action'];
+            if (strpos($elepath, $path) === 0) {
+                $toprocess = true;
+            }
+
+        // Also, if the stack of startend notifications is empty, we can process current end
+        // path safely
+        } else {
+            $toprocess = true;
+        }
         if ($this->path_is_selected($path)) {
-            $this->notify_path_end($path);
+            $this->startendinfo[] = array('path' => $path, 'action' => 'end');
+        }
+        // Send all the pending startend notifications if decided to do so
+        if ($toprocess) {
+            $this->process_pending_startend_notifications($path, 'end');
         }
     }
 
+
 // Protected API starts here
 
+    /**
+     * Adjust start/end til finding one match start/end path (included)
+     *
+     * This will trigger all the pending {@see notify_path_start} and
+     * {@see notify_path_end} calls for one given path and action
+     *
+     * @param string path the path to look for as limit
+     * @param string action the action to look for as limit
+     */
+    protected function process_pending_startend_notifications($path, $action) {
+
+        // Iterate until one matching path and action is found (or the array is empty)
+        $elecount = count($this->startendinfo);
+        $elematch = false;
+        while ($elecount > 0 && !$elematch) {
+            $element = array_shift($this->startendinfo);
+            $elecount--;
+            $elepath = $element['path'];
+            $eleaction = $element['action'];
+
+            if ($elepath == $path && $eleaction == $action) {
+                $elematch = true;
+            }
+
+            if ($eleaction == 'start') {
+                $this->notify_path_start($elepath);
+            } else {
+                $this->notify_path_end($elepath);
+            }
+        }
+    }
+
     protected function postprocess_chunk($data) {
         $this->dispatch_chunk($data);
     }
diff --git a/backup/util/xml/parser/simpletest/fixtures/test5.xml b/backup/util/xml/parser/simpletest/fixtures/test5.xml
new file mode 100644 (file)
index 0000000..9904c3b
--- /dev/null
@@ -0,0 +1,24 @@
+<MOODLE_BACKUP>
+  <COURSE ID="100">
+    <SECTIONS>
+      <SECTION>
+        <ID>200</ID>
+        <MODS>
+          <MOD>
+            <ID>300</ID>
+            <ROLES_OVERRIDES>
+            </ROLES_OVERRIDES>
+          </MOD>
+          <MOD />
+          <MOD />
+          <MOD ID="400" />
+          <MOD>
+            <ID>500</ID>
+          </MOD>
+          <MOD />
+          <MOD />
+        </MODS>
+      </SECTION>
+    </SECTIONS>
+  </COURSE>
+</MOODLE_BACKUP>
index eccc2c4..cae713f 100644 (file)
@@ -330,6 +330,80 @@ class progressive_parser_test extends UnitTestCase {
         sort($snotifs);
         sort($enotifs);
         $this->assertEqual($snotifs, $enotifs);
+
+        // Now verify that the start/process/end order is correct
+        $allnotifs = $pr->get_all_notifications();
+        $this->assertEqual(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
+        // Check integrity of the notifications
+        $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
+        $this->assertEqual($errcount, 0); // No errors found, plz
+    }
+
+    /**
+     * test how the simplified processor and the order of start/process/end events happens
+     * with one real fragment of one backup 1.9 file, where some problems
+     * were found by David, hence we honor him in the name of the test ;-)
+     */
+    function test_simplified_david_backup19_file_fragment() {
+        global $CFG;
+        // Instantiate progressive_parser
+        $pp =  new progressive_parser();
+        // Instantiate grouped_parser_processor
+        $pr = new mock_simplified_parser_processor();
+        // Add interesting paths
+        $pr->add_path('/MOODLE_BACKUP/COURSE');
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
+        $this->assertTrue($pr instanceof progressive_parser_processor);
+        // Assign processor to parser
+        $pp->set_processor($pr);
+        // Set file from fixtures
+        $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/simpletest/fixtures/test5.xml');
+        // Process the file
+        $pp->process();
+
+        // Get all the simplified chunks and perform various validations
+        $chunks = $pr->get_chunks();
+        $this->assertEqual(count($chunks), 3); // Only 3, because 7 (COURSE, ROLES_OVERRIDES and 5 MOD) are empty, aka no chunk
+
+        // Now check start notifications
+        $snotifs = $pr->get_start_notifications();
+        // Check we have received the correct number of notifications
+        $this->assertEqual(count($snotifs), 10); // Start tags are dispatched for empties (ROLES_OVERRIDES)
+        // Check first and last notifications
+        $this->assertEqual($snotifs[0], '/MOODLE_BACKUP/COURSE');
+        $this->assertEqual($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
+        $this->assertEqual($snotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($snotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
+        $this->assertEqual($snotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($snotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($snotifs[9], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+
+        // Now check end notifications
+        $enotifs = $pr->get_end_notifications();
+        // Check we have received the correct number of notifications
+        $this->assertEqual(count($snotifs), 10); // End tags are dispatched for empties (ROLES_OVERRIDES)
+        // Check first, and last notifications
+        $this->assertEqual($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
+        $this->assertEqual($enotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($enotifs[2], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($enotifs[3], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($enotifs[7], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $this->assertEqual($enotifs[8], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
+        $this->assertEqual($enotifs[9], '/MOODLE_BACKUP/COURSE');
+
+        // Check start and end notifications are balanced
+        sort($snotifs);
+        sort($enotifs);
+        $this->assertEqual($snotifs, $enotifs);
+
+        // Now verify that the start/process/end order is correct
+        $allnotifs = $pr->get_all_notifications();
+        $this->assertEqual(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
+        // Check integrity of the notifications
+        $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
+        $this->assertEqual($errcount, 0); // No errors found, plz
     }
 
     /*
@@ -498,6 +572,127 @@ class progressive_parser_test extends UnitTestCase {
         sort($snotifs);
         sort($enotifs);
         $this->assertEqual($snotifs, $enotifs);
+
+        // Now verify that the start/process/end order is correct
+        $allnotifs = $pr->get_all_notifications();
+        $this->assertEqual(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
+        // Check integrity of the notifications
+        $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
+        $this->assertEqual($errcount, 0); // No errors found, plz
+    }
+
+    /**
+     * test how the grouped processor and the order of start/process/end events happens
+     * with one real fragment of one backup 1.9 file, where some problems
+     * were found by David, hence we honor him in the name of the test ;-)
+     */
+    function test_grouped_david_backup19_file_fragment() {
+        global $CFG;
+        // Instantiate progressive_parser
+        $pp =  new progressive_parser();
+        // Instantiate grouped_parser_processor
+        $pr = new mock_grouped_parser_processor();
+        // Add interesting paths
+        $pr->add_path('/MOODLE_BACKUP/COURSE');
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION', true);
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD');
+        $pr->add_path('/MOODLE_BACKUP/COURSE/SECTIONS/SECTION/MODS/MOD/ROLES_OVERRIDES');
+        $this->assertTrue($pr instanceof progressive_parser_processor);
+        // Assign processor to parser
+        $pp->set_processor($pr);
+        // Set file from fixtures
+        $pp->set_file($CFG->dirroot . '/backup/util/xml/parser/simpletest/fixtures/test5.xml');
+        // Process the file
+        $pp->process();
+
+        // Get all the simplified chunks and perform various validations
+        $chunks = $pr->get_chunks();
+        $this->assertEqual(count($chunks), 1); // Only 1, the SECTION one
+
+        // Now check start notifications
+        $snotifs = $pr->get_start_notifications();
+        // Check we have received the correct number of notifications
+        $this->assertEqual(count($snotifs), 2);
+        // Check first and last notifications
+        $this->assertEqual($snotifs[0], '/MOODLE_BACKUP/COURSE');
+        $this->assertEqual($snotifs[1], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
+
+        // Now check end notifications
+        $enotifs = $pr->get_end_notifications();
+        // Check we have received the correct number of notifications
+        $this->assertEqual(count($snotifs), 2); // End tags are dispatched for empties (ROLES_OVERRIDES)
+        // Check first, and last notifications
+        $this->assertEqual($enotifs[0], '/MOODLE_BACKUP/COURSE/SECTIONS/SECTION');
+        $this->assertEqual($enotifs[1], '/MOODLE_BACKUP/COURSE');
+
+        // Check start and end notifications are balanced
+        sort($snotifs);
+        sort($enotifs);
+        $this->assertEqual($snotifs, $enotifs);
+
+        // Now verify that the start/process/end order is correct
+        $allnotifs = $pr->get_all_notifications();
+        $this->assertEqual(count($allnotifs), count($snotifs) + count($enotifs) + count($chunks)); // The count
+        // Check integrity of the notifications
+        $errcount = $this->helper_check_notifications_order_integrity($allnotifs);
+        $this->assertEqual($errcount, 0); // No errors found, plz
+    }
+
+
+    /**
+     * Helper function that given one array of ordered start/process/end notifications will
+     * check it of integrity like:
+     *    - process only happens if start is the previous notification
+     *    - end only happens if dispatch is the previous notification
+     *    - start only happen with level > than last one and if there is no already started like that
+     *
+     * @param array $notifications ordered array of notifications with format [start|process|end]:path
+     * @return int number of integrity problems found (errors)
+     */
+    function helper_check_notifications_order_integrity($notifications) {
+        $numerrors = 0;
+        $notifpile = array('pilebase' => 'start');
+        $lastnotif = 'start:pilebase';
+        foreach ($notifications as $notif) {
+
+            $lastpiletype = end($notifpile);
+            $lastpilepath = key($notifpile);
+            $lastpilelevel = strlen(preg_replace('/[^\/]/', '', $lastpilepath));
+
+            $lastnotiftype  = preg_replace('/:.*/', '', $lastnotif);
+            $lastnotifpath  = preg_replace('/.*:/', '', $lastnotif);
+            $lastnotiflevel = strlen(preg_replace('/[^\/]/', '', $lastnotifpath));
+
+            $notiftype  = preg_replace('/:.*/', '', $notif);
+            $notifpath  = preg_replace('/.*:/', '', $notif);
+            $notiflevel = strlen(preg_replace('/[^\/]/', '', $notifpath));
+
+            switch ($notiftype) {
+                case 'process':
+                    if ($lastnotifpath != $notifpath or $lastnotiftype != 'start') {
+                        $numerrors++; // Only start for same path from last notification is allowed before process
+                    }
+                    $notifpile[$notifpath] = 'process'; // Update the status in the pile
+                    break;
+                case 'end':
+                    if ($lastpilepath != $notifpath or ($lastpiletype != 'process' and $lastpiletype != 'start')) {
+                        $numerrors++; // Only process and start for same path from last pile is allowed before end
+                    }
+                    unset($notifpile[$notifpath]); // Delete from the pile
+                    break;
+                case 'start':
+                    if (array_key_exists($notifpath, $notifpile) or $notiflevel <= $lastpilelevel) {
+                        $numerrors++; // Only non existing in pile and with level > last pile is allowed on start
+                    }
+                    $notifpile[$notifpath] = 'start'; // Add to the pile
+                    break;
+                default:
+                    $numerrors++; // Incorrect type of notification => error
+            }
+            // Update lastnotif
+            $lastnotif = $notif;
+        }
+        return $numerrors;
     }
 }
 
@@ -572,17 +767,21 @@ class mock_simplified_parser_processor extends simplified_parser_processor {
     private $chunksarr = array(); // To accumulate the found chunks
     private $startarr  = array(); // To accumulate all the notified path starts
     private $endarr    = array(); // To accumulate all the notified path ends
+    private $allnotif  = array(); // To accumulate all the notified and dispatched events in an ordered way
 
     public function dispatch_chunk($data) {
         $this->chunksarr[] = $data;
+        $this->allnotif[] = 'process:' . $data['path'];
     }
 
     public function notify_path_start($path) {
         $this->startarr[] = $path;
+        $this->allnotif[] = 'start:' . $path;
     }
 
     public function notify_path_end($path) {
         $this->endarr[] = $path;
+        $this->allnotif[] = 'end:' . $path;
     }
 
     public function get_chunks() {
@@ -596,6 +795,10 @@ class mock_simplified_parser_processor extends simplified_parser_processor {
     public function get_end_notifications() {
         return $this->endarr;
     }
+
+    public function get_all_notifications() {
+        return $this->allnotif;
+    }
 }
 
 /*
@@ -606,17 +809,21 @@ class mock_grouped_parser_processor extends grouped_parser_processor {
     private $chunksarr = array(); // To accumulate the found chunks
     private $startarr  = array(); // To accumulate all the notified path starts
     private $endarr    = array(); // To accumulate all the notified path ends
+    private $allnotif  = array(); // To accumulate all the notified and dispatched events in an ordered way
 
     public function dispatch_chunk($data) {
         $this->chunksarr[] = $data;
+        $this->allnotif[] = 'process:' . $data['path'];
     }
 
     public function notify_path_start($path) {
         $this->startarr[] = $path;
+        $this->allnotif[] = 'start:' . $path;
     }
 
     public function notify_path_end($path) {
         $this->endarr[] = $path;
+        $this->allnotif[] = 'end:' . $path;
     }
 
     public function get_chunks() {
@@ -630,4 +837,8 @@ class mock_grouped_parser_processor extends grouped_parser_processor {
     public function get_end_notifications() {
         return $this->endarr;
     }
+
+    public function get_all_notifications() {
+        return $this->allnotif;
+    }
 }
index 225c163..5128c65 100644 (file)
@@ -66,7 +66,75 @@ M.core_dock.init = function(Y) {
     // Give the dock item class the event properties/methods
     Y.augment(this.item, Y.EventTarget);
     Y.augment(this, Y.EventTarget, true);
+    /**
+     * A 'dock:actionkey' Event.
+     * The event consists of the left arrow, right arrow, enter and space keys.
+     * More keys can be mapped to action meanings.
+     * actions: collapse , expand, toggle, enter.
+     *
+     * This event is subscribed to by dockitems.
+     * The on() method to subscribe allows specifying the desired trigger actions as JSON.
+     *
+     * This event can also be delegated if needed.
+     * Todo: This could be centralised, a similar Event is defined in blocks/navigation/yui/navigation/navigation.js
+     */
+    Y.Event.define("dock:actionkey", {
+        // Webkit and IE repeat keydown when you hold down arrow keys.
+        // Opera links keypress to page scroll; others keydown.
+        // Firefox prevents page scroll via preventDefault() on either
+        // keydown or keypress.
+        _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
+
+        _keys: {
+            //arrows
+            '37': 'collapse',
+            '39': 'expand',
+            //(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
+            '32': 'toggle',
+            '13': 'enter'
+        },
+
+        _keyHandler: function (e, notifier, args) {
+            if (!args.actions) {
+                var actObj = {collapse:true, expand:true, toggle:true, enter:true};
+            } else {
+                var actObj = args.actions;
+            }
+            if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
+                e.action = this._keys[e.keyCode];
+                notifier.fire(e);
+            }
+        },
+
+        on: function (node, sub, notifier) {
+            // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
+            if (sub.args == null) {
+                //no actions given
+                sub._detacher = node.on(this._event, this._keyHandler,this, notifier, {actions:false});
+            } else {
+                sub._detacher = node.on(this._event, this._keyHandler,this, notifier, sub.args[0]);
+            }
+        },
 
+        detach: function (node, sub, notifier) {
+            //detach our _detacher handle of the subscription made in on()
+            sub._detacher.detach();
+        },
+
+        delegate: function (node, sub, notifier, filter) {
+            // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
+            if (sub.args == null) {
+                //no actions given
+                sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, {actions:false});
+            } else {
+                sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, sub.args[0]);
+            }
+        },
+
+        detachDelegate: function (node, sub, notifier) {
+            sub._delegateDetacher.detach();
+        }
+    });
     // Publish the events the dock has
     this.publish('dock:beforedraw', {prefix:'dock'});
     this.publish('dock:beforeshow', {prefix:'dock'});
@@ -125,9 +193,10 @@ M.core_dock.init = function(Y) {
 
     // Add a removeall button
     // Must set the image src seperatly of we get an error with XML strict headers
-    var removeall = Y.Node.create('<img alt="'+M.str.block.undockall+'" title="'+M.str.block.undockall+'" />');
+    var removeall = Y.Node.create('<img alt="'+M.str.block.undockall+'" title="'+M.str.block.undockall+'" tabindex="0"/>');
     removeall.setAttribute('src',this.cfg.removeallicon);
     removeall.on('removeall|click', this.remove_all, this);
+    removeall.on('dock:actionkey', this.remove_all, this, {actions:{enter:true}});
     this.nodes.buttons.appendChild(Y.Node.create('<div class="'+css.controls+'"></div>').append(removeall));
 
     // Create a manager for the height of the tabs. Once set this can be forgotten about
@@ -590,7 +659,7 @@ M.core_dock.resetFirstItem = function() {
  * @function
  * @return {boolean}
  */
-M.core_dock.remove_all = function() {
+M.core_dock.remove_all = function(e) {
     for (var i in this.items) {
         this.remove(i);
     }
@@ -840,9 +909,10 @@ M.core_dock.genericblock.prototype = {
             }, this);
             // Add a close icon
             // Must set the image src seperatly of we get an error with XML strict headers
-            var closeicon = Y.Node.create('<span class="hidepanelicon"><img alt="" style="width:11px;height:11px;cursor:pointer;" /></span>');
+            var closeicon = Y.Node.create('<span class="hidepanelicon" tabindex="0"><img alt="" style="width:11px;height:11px;cursor:pointer;" /></span>');
             closeicon.one('img').setAttribute('src', M.util.image_url('t/dockclose', 'moodle'));
             closeicon.on('forceclose|click', this.hide, this);
+            closeicon.on('dock:actionkey',this.hide, this, {actions:{enter:true,toggle:true}});
             this.commands.append(closeicon);
         }, dockitem);
         // Register an event so that when it is removed we can put it back as a block
@@ -955,7 +1025,8 @@ M.core_dock.item.prototype = {
 
         this.nodes.docktitle = Y.Node.create('<div id="dock_item_'+this.id+'_title" class="'+css.dockedtitle+'"></div>');
         this.nodes.docktitle.append(this.title);
-        this.nodes.dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+css.dockeditem+'"></div>');
+        this.nodes.dockitem = Y.Node.create('<div id="dock_item_'+this.id+'" class="'+css.dockeditem+'" tabindex="0"></div>');
+        this.nodes.dockitem.on('dock:actionkey', this.toggle, this);
         if (M.core_dock.count === 1) {
             this.nodes.dockitem.addClass('firstdockitem');
         }
@@ -999,6 +1070,19 @@ M.core_dock.item.prototype = {
         M.core_dock.getPanel().hide();
         this.fire('dockeditem:hidecomplete');
     },
+    /**
+     * A toggle between calling show and hide functions based on css.activeitem
+     * Applies rules to key press events (dock:actionkey)
+     * @param {Event} e
+     */
+    toggle : function(e) {
+        var css = M.core_dock.css;
+        if (this.nodes.docktitle.hasClass(css.activeitem) && !(e.type == 'dock:actionkey' && e.action=='expand')) {
+            this.hide();
+        } else if (!this.nodes.docktitle.hasClass(css.activeitem) && !(e.type == 'dock:actionkey' && e.action=='collapse'))  {
+            this.show();
+        }
+    },
     /**
      * This function removes the node and destroys it's bits
      * @param {Event} e
index d59f87c..e28ac28 100644 (file)
@@ -52,6 +52,7 @@ class block_navigation_renderer extends plugin_renderer_base {
                 $attributes['class'] = 'dimmed_text';
             }
             if (is_string($item->action) || empty($item->action) || ($item->type === navigation_node::TYPE_CATEGORY && empty($options['linkcategories']))) {
+                $attributes['tabindex'] = '0'; //add tab support to span but still maintain character stream sequence.
                 $content = html_writer::tag('span', $content, $attributes);
             } else if ($item->action instanceof action_link) {
                 //TODO: to be replaced with something else
index 1dfb76a..21133c1 100644 (file)
@@ -1,5 +1,74 @@
 YUI.add('moodle-block_navigation-navigation', function(Y){
 
+/**
+ * A 'actionkey' Event to help with Y.delegate().
+ * The event consists of the left arrow, right arrow, enter and space keys.
+ * More keys can be mapped to action meanings.
+ * actions: collapse , expand, toggle, enter.
+ *
+ * This event is delegated to branches in the navigation tree.
+ * The on() method to subscribe allows specifying the desired trigger actions as JSON.
+ *
+ * Todo: This could be centralised, a similar Event is defined in blocks/dock.js
+ */
+Y.Event.define("actionkey", {
+   // Webkit and IE repeat keydown when you hold down arrow keys.
+    // Opera links keypress to page scroll; others keydown.
+    // Firefox prevents page scroll via preventDefault() on either
+    // keydown or keypress.
+    _event: (Y.UA.webkit || Y.UA.ie) ? 'keydown' : 'keypress',
+
+    _keys: {
+        //arrows
+        '37': 'collapse',
+        '39': 'expand',
+        //(@todo: lrt/rtl/M.core_dock.cfg.orientation decision to assign arrow to meanings)
+        '32': 'toggle',
+        '13': 'enter'
+    },
+
+    _keyHandler: function (e, notifier, args) {
+        if (!args.actions) {
+            var actObj = {collapse:true, expand:true, toggle:true, enter:true};
+        } else {
+            var actObj = args.actions;
+        }
+        if (this._keys[e.keyCode] && actObj[this._keys[e.keyCode]]) {
+            e.action = this._keys[e.keyCode];
+            notifier.fire(e);
+        }
+    },
+
+    on: function (node, sub, notifier) {
+        // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
+        if (sub.args == null) {
+            //no actions given
+            sub._detacher = node.on(this._event, this._keyHandler,this, notifier, {actions:false});
+        } else {
+            sub._detacher = node.on(this._event, this._keyHandler,this, notifier, sub.args[0]);
+        }
+    },
+
+    detach: function (node, sub, notifier) {
+        //detach our _detacher handle of the subscription made in on()
+        sub._detacher.detach();
+    },
+
+    delegate: function (node, sub, notifier, filter) {
+        // subscribe to _event and ask keyHandler to handle with given args[0] (the desired actions).
+        if (sub.args == null) {
+            //no actions given
+            sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, {actions:false});
+        } else {
+            sub._delegateDetacher = node.delegate(this._event, this._keyHandler,filter, this, notifier, sub.args[0]);
+        }
+    },
+
+    detachDelegate: function (node, sub, notifier) {
+        sub._delegateDetacher.detach();
+    }
+});
+
 var EXPANSIONLIMIT_EVERYTHING = 0,
     EXPANSIONLIMIT_COURSE     = 20,
     EXPANSIONLIMIT_SECTION    = 30,
@@ -36,6 +105,7 @@ TREE.prototype = {
         // Delegate event to toggle expansion
         var self = this;
         Y.delegate('click', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
+        Y.delegate('actionkey', function(e){self.toggleExpansion(e);}, node.one('.block_tree'), '.tree_item.branch');
 
         // Gather the expandable branches ready for initialisation.
         var expansions = [];
@@ -71,8 +141,8 @@ TREE.prototype = {
         // First check if they managed to click on the li iteslf, then find the closest
         // LI ancestor and use that
 
-        if (e.target.test('a')) {
-            // A link has been clicked don't fire any more events just do the default.
+        if (e.target.test('a') && (e.keyCode == 0 || e.keyCode == 13)) {
+            // A link has been clicked (or keypress is 'enter') don't fire any more events just do the default.
             e.stopPropagation();
             return;
         }
@@ -88,7 +158,21 @@ TREE.prototype = {
 
         // Toggle expand/collapse providing its not a root level branch.
         if (!target.hasClass('depth_1')) {
-            target.toggleClass('collapsed');
+            if (e.type == 'actionkey') {
+                switch (e.action) {
+                    case 'expand' :
+                        target.removeClass('collapsed');
+                        break;
+                    case 'collapse' :
+                        target.addClass('collapsed');
+                        break;
+                    default :
+                        target.toggleClass('collapsed');
+                }
+                e.halt();
+            } else {
+                target.toggleClass('collapsed');
+            }
         }
 
         // If the accordian feature has been enabled collapse all siblings.
@@ -152,9 +236,10 @@ BRANCH.prototype = {
      */
     node : null,
     /**
-     * A reference to the ajax load event handle when created.
+     * A reference to the ajax load event handlers when created.
      */
     event_ajaxload : null,
+    event_ajaxload_actionkey : null,
     /**
      * Initialises the branch when it is first created.
      */
@@ -193,8 +278,13 @@ BRANCH.prototype = {
 
         var isbranch = (this.get('expandable') || this.get('haschildren'));
         var branchli = Y.Node.create('<li></li>');
+        var link = this.get('link');
         var branchp = Y.Node.create('<p class="tree_item"></p>').setAttribute('id', this.get('id'));
-
+        if (!link) {
+            //add tab focus if not link (so still one focus per menu node).
+            // it was suggested to have 2 foci. one for the node and one for the link in MDL-27428.
+            branchp.setAttribute('tabindex', '0');
+        }
         if (isbranch) {
             branchli.addClass('collapsed').addClass('contains_branch');
             branchp.addClass('branch');
@@ -220,7 +310,6 @@ BRANCH.prototype = {
             }
         }
 
-        var link = this.get('link');
         if (!link) {
             if (branchicon) {
                 branchp.appendChild(branchicon);
@@ -253,6 +342,7 @@ BRANCH.prototype = {
         }
         if (this.get('expandable')) {
             this.event_ajaxload = this.node.on('ajaxload|click', this.ajaxLoad, this);
+            this.event_ajaxload_actionkey = this.node.on('actionkey', this.ajaxLoad, this);
         }
         return this;
     },
@@ -274,7 +364,16 @@ BRANCH.prototype = {
      * request made here.
      */
     ajaxLoad : function(e) {
-        e.stopPropagation();
+        if (e.type == 'actionkey' && e.action != 'enter') {
+            e.halt();
+        } else {
+            e.stopPropagation();
+        }
+        if (e.type = 'actionkey' && e.action == 'enter' && e.target.test('A')) {
+            this.event_ajaxload_actionkey.detach();
+            this.event_ajaxload.detach();
+            return true; // no ajaxLoad for enter
+        }
 
         if (this.node.hasClass('loadingbranch')) {
             return true;
@@ -307,6 +406,7 @@ BRANCH.prototype = {
     ajaxProcessResponse : function(tid, outcome) {
         this.node.removeClass('loadingbranch');
         this.event_ajaxload.detach();
+        this.event_ajaxload_actionkey.detach();
         try {
             var object = Y.JSON.parse(outcome.responseText);
             if (object.children && object.children.length > 0) {
@@ -455,4 +555,4 @@ M.block_navigation = M.block_navigation || {
     }
 };
 
-}, '@VERSION@', {requires:['base', 'core_dock', 'io', 'node', 'dom', 'event-custom', 'event-delegate', 'json-parse']});
\ No newline at end of file
+}, '@VERSION@', {requires:['base', 'core_dock', 'io', 'node', 'dom', 'event-custom', 'event-delegate', 'json-parse']});
index 3fe48f1..bfda457 100644 (file)
@@ -13,7 +13,7 @@ if (empty($CFG->enablecalendarexport)) {
 }
 
 //Fetch user information
-if (!$user = get_complete_user_data('username', $username)) {
+if (!$user = $DB->get_record('user', array('username' => $username), 'id,password')) {
    //No such user
     die('Invalid authentication');
 }
index 2bf5979..184e320 100644 (file)
@@ -127,16 +127,14 @@ class comment {
      */
     protected $totalcommentcount = null;
     /**
-     * By default a user must have the generic comment capabilities plus any capabilities the
-     * component being commented on requires.
-     * When set to true only the component capabilities are checked, the system capabilities are
-     * ignored.
-     * This can be toggled by the component defining a callback in its lib.php e.g.
-     *    function forum_comment_allow_anonymous_access(comment $comment) {}
-     * Note: On the front page this defaults to true.
+     * When set to true any user to the system is able to view comments.
+     *
+     * This can be set to true by a plugin by implementing a allow_anonymous_access callback.
+     * By default it is false except on the front page.
+     *
      * @var bool
      */
-    protected $ignoresystempermissions = false;
+    protected $allowanonymousaccess = false;
 
     /**#@+
      * static variable will be used by non-js comments UI
index 0482168..e4eb740 100644 (file)
@@ -428,7 +428,7 @@ abstract class moodleform_mod extends moodleform {
 
         if (!empty($CFG->enableavailability)) {
             // Conditional availability
-            $mform->addElement('header', '', get_string('availabilityconditions', 'condition'));
+            $mform->addElement('header', 'availabilityconditionsheader', get_string('availabilityconditions', 'condition'));
             $mform->addElement('date_selector', 'availablefrom', get_string('availablefrom', 'condition'), array('optional'=>true));
             $mform->addHelpButton('availablefrom', 'availablefrom', 'condition');
             $mform->addElement('date_selector', 'availableuntil', get_string('availableuntil', 'condition'), array('optional'=>true));
@@ -522,7 +522,7 @@ abstract class moodleform_mod extends moodleform {
             $completion = new completion_info($COURSE);
         }
         if ($completion->is_enabled()) {
-            $mform->addElement('header', '', get_string('activitycompletion', 'completion'));
+            $mform->addElement('header', 'activitycompletionheader', get_string('activitycompletion', 'completion'));
 
             // Unlock button for if people have completed it (will
             // be removed in definition_after_data if they haven't)
index d7199dc..20004e0 100644 (file)
@@ -303,6 +303,7 @@ $string['invalidpagesize'] = 'Invalid page size';
 $string['invalidpasswordpolicy'] = 'Invalid password policy';
 $string['invalidpaymentmethod'] = 'Invalid payment method: {$a}';
 $string['invalidqueryparam'] = 'ERROR: Incorrect number of query parameters. Expected {$a->expected}, got {$a->actual}.';
+$string['invalidratingarea'] = 'Invalid rating area';
 $string['invalidrecord'] = 'Can not find data record in database table {$a}.';
 $string['invalidrecordunknown'] = 'Can not find data record in database.';
 $string['invalidrequest'] = 'Invalid request';
index ae4edcc..110e24b 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20110209" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20110523" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
     <TABLE NAME="rating" COMMENT="moodle ratings" PREVIOUS="blog_external" NEXT="license">
       <FIELDS>
         <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" NEXT="contextid"/>
-        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="id" NEXT="itemid"/>
-        <FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="contextid" NEXT="scaleid"/>
+        <FIELD NAME="contextid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="id" NEXT="component"/>
+        <FIELD NAME="component" TYPE="char" LENGTH="100" NOTNULL="true" SEQUENCE="false" PREVIOUS="contextid" NEXT="ratingarea"/>
+        <FIELD NAME="ratingarea" TYPE="char" LENGTH="50" NOTNULL="true" SEQUENCE="false" PREVIOUS="component" NEXT="itemid"/>
+        <FIELD NAME="itemid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="ratingarea" NEXT="scaleid"/>
         <FIELD NAME="scaleid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="false" SEQUENCE="false" PREVIOUS="itemid" NEXT="rating"/>
         <FIELD NAME="rating" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="scaleid" NEXT="userid"/>
         <FIELD NAME="userid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" PREVIOUS="rating" NEXT="timecreated"/>
         <KEY NAME="userid" TYPE="foreign" FIELDS="userid" REFTABLE="user" REFFIELDS="id" COMMENT="Relates to user.id" PREVIOUS="contextid"/>
       </KEYS>
       <INDEXES>
-        <INDEX NAME="itemid" UNIQUE="false" FIELDS="itemid"/>
+        <INDEX NAME="uniqueuserrating" UNIQUE="false" FIELDS="component, ratingarea, contextid, itemid" COMMENT="These fields define a unique user rating of an item"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="license" COMMENT="store licenses used by moodle" PREVIOUS="rating" NEXT="registration_hubs">
index 4a6715e..1484e4b 100644 (file)
@@ -1062,7 +1062,7 @@ abstract class enrol_plugin {
                 if (!is_null($status)) {
                     $ue->status   = $status;
                 }
-                $ue->modifier     = $USER->id;
+                $ue->modifierid   = $USER->id;
                 $ue->timemodified = time();
                 $DB->update_record('user_enrolments', $ue);
             }
@@ -1073,7 +1073,7 @@ abstract class enrol_plugin {
             $ue->userid       = $userid;
             $ue->timestart    = $timestart;
             $ue->timeend      = $timeend;
-            $ue->modifier     = $USER->id;
+            $ue->modifierid   = $USER->id;
             $ue->timecreated  = time();
             $ue->timemodified = $ue->timecreated;
             $ue->id = $DB->insert_record('user_enrolments', $ue);
index f9994fe..c2fc0d0 100644 (file)
@@ -1074,6 +1074,8 @@ function get_fast_modinfo(&$course, $userid=0) {
     if (count($cache) > MAX_MODINFO_CACHE_SIZE) {
         reset($cache);
         $key = key($cache);
+        unset($cache[$key]->instances);
+        unset($cache[$key]->cms);
         unset($cache[$key]);
     }
 
index 1e4be90..4fcfd8c 100644 (file)
@@ -237,8 +237,8 @@ class navigation_node implements renderable {
     }
 
     /**
-     * Adds a navigation node as a child of this node.
-     *
+     * Creates a navigation node, ready to add it as a child using add_node
+     * function. (The created node needs to be added before you can use it.)
      * @param string $text
      * @param moodle_url|action_link $action
      * @param int $type
@@ -247,11 +247,8 @@ class navigation_node implements renderable {
      * @param pix_icon $icon
      * @return navigation_node
      */
-    public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
-        // First convert the nodetype for this node to a branch as it will now have children
-        if ($this->nodetype !== self::NODETYPE_BRANCH) {
-            $this->nodetype = self::NODETYPE_BRANCH;
-        }
+    public static function create($text, $action=null, $type=self::TYPE_CUSTOM,
+            $shorttext=null, $key=null, pix_icon $icon=null) {
         // Properties array used when creating the new navigation node
         $itemarray = array(
             'text' => $text,
@@ -269,18 +266,58 @@ class navigation_node implements renderable {
         if ($icon!==null) {
             $itemarray['icon'] = $icon;
         }
-        // Default the key to the number of children if not provided
-        if ($key === null) {
-            $key = $this->children->count();
-        }
         // Set the key
         $itemarray['key'] = $key;
+        // Construct and return
+        return new navigation_node($itemarray);
+    }
+
+    /**
+     * Adds a navigation node as a child of this node.
+     *
+     * @param string $text
+     * @param moodle_url|action_link $action
+     * @param int $type
+     * @param string $shorttext
+     * @param string|int $key
+     * @param pix_icon $icon
+     * @return navigation_node
+     */
+    public function add($text, $action=null, $type=self::TYPE_CUSTOM, $shorttext=null, $key=null, pix_icon $icon=null) {
+        // Create child node
+        $childnode = self::create($text, $action, $type, $shorttext, $key, $icon);
+
+        // Add the child to end and return
+        return $this->add_node($childnode);
+    }
+
+    /**
+     * Adds a navigation node as a child of this one, given a $node object
+     * created using the create function.
+     * @param navigation_node $childnode Node to add
+     * @param int|string $key The key of a node to add this before. If not
+     *   specified, adds at end of list
+     * @return navigation_node The added node
+     */
+    public function add_node(navigation_node $childnode, $beforekey=null) {
+        // First convert the nodetype for this node to a branch as it will now have children
+        if ($this->nodetype !== self::NODETYPE_BRANCH) {
+            $this->nodetype = self::NODETYPE_BRANCH;
+        }
         // Set the parent to this node
-        $itemarray['parent'] = $this;
+        $childnode->parent = $this;
+
+        // Default the key to the number of children if not provided
+        if ($childnode->key === null) {
+            $childnode->key = $this->children->count();
+        }
+
         // Add the child using the navigation_node_collections add method
-        $node = $this->children->add(new navigation_node($itemarray));
-        // If the node is a category node or the user is logged in and its a course
-        // then mark this node as a branch (makes it expandable by AJAX)
+        $node = $this->children->add($childnode, $beforekey);
+
+        // If added node is a category node or the user is logged in and it's a course
+        // then mark added node as a branch (makes it expandable by AJAX)
+        $type = $childnode->type;
         if (($type==self::TYPE_CATEGORY) || (isloggedin() && $type==self::TYPE_COURSE)) {
             $node->nodetype = self::NODETYPE_BRANCH;
         }
@@ -288,7 +325,7 @@ class navigation_node implements renderable {
         if ($this->hidden) {
             $node->hidden = true;
         }
-        // Return the node (reference returned by $this->children->add()
+        // Return added node (reference returned by $this->children->add()
         return $node;
     }
 
@@ -647,13 +684,16 @@ class navigation_node_collection implements IteratorAggregate {
     /**
      * Adds a navigation node to the collection
      *
-     * @param navigation_node $node
-     * @return navigation_node
+     * @param navigation_node $node Node to add
+     * @param string $beforekey If specified, adds before a node with this key,
+     *   otherwise adds at end
+     * @return navigation_node Added node
      */
-    public function add(navigation_node $node) {
+    public function add(navigation_node $node, $beforekey=null) {
         global $CFG;
         $key = $node->key;
         $type = $node->type;
+
         // First check we have a 2nd dimension for this type
         if (!array_key_exists($type, $this->orderedcollection)) {
             $this->orderedcollection[$type] = array();
@@ -662,15 +702,50 @@ class navigation_node_collection implements IteratorAggregate {
         if ($CFG->debug && array_key_exists($key, $this->orderedcollection[$type])) {
             debugging('Navigation node intersect: Adding a node that already exists '.$key, DEBUG_DEVELOPER);
         }
-        // Add the node to the appropriate place in the ordered structure.
+
+        // Find the key to add before
+        $newindex = $this->count;
+        $last = true;
+        if ($beforekey !== null) {
+            foreach ($this->collection as $index => $othernode) {
+                if ($othernode->key === $beforekey) {
+                    $newindex = $index;
+                    $last = false;
+                    break;
+                }
+            }
+            if ($newindex === $this->count) {
+                $allkeys = '';
+                foreach ($this->collection as $othernode) {
+                    $allkeys .= ' ' . $othernode->key;
+                }
+                debugging('Navigation node add_before: Reference node not found ' . $beforekey .
+                        ', options: ' . $allkeys, DEBUG_DEVELOPER);
+            }
+        }
+
+        // Add the node to the appropriate place in the by-type structure (which
+        // is not ordered, despite the variable name)
         $this->orderedcollection[$type][$key] = $node;
+        if (!$last) {
+            // Update existing references in the ordered collection (which is the
+            // one that isn't called 'ordered') to shuffle them along if required
+            for ($oldindex = $this->count; $oldindex > $newindex; $oldindex--) {
+                $this->collection[$oldindex] = $this->collection[$oldindex - 1];
+            }
+        }
         // Add a reference to the node to the progressive collection.
-        $this->collection[$this->count] = &$this->orderedcollection[$type][$key];
+        $this->collection[$newindex] = &$this->orderedcollection[$type][$key];
         // Update the last property to a reference to this new node.
         $this->last = &$this->orderedcollection[$type][$key];
+
+        // Reorder the array by index if needed
+        if (!$last) {
+            ksort($this->collection);
+        }
         $this->count++;
         // Return the reference to the now added node
-        return $this->last;
+        return $node;
     }
 
     /**
@@ -1700,9 +1775,16 @@ class global_navigation extends navigation_node {
         }
 
         // Add a reports tab and then add reports the the user has permission to see.
-        $anyreport  = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
+        $anyreport      = has_capability('moodle/user:viewuseractivitiesreport', $usercontext);
+
+        $outlinetreport = ($anyreport || has_capability('coursereport/outline:view', $coursecontext));
+        $logtodayreport = ($anyreport || has_capability('coursereport/log:viewtoday', $coursecontext));
+        $logreport      = ($anyreport || has_capability('coursereport/log:view', $coursecontext));
+        $statsreport    = ($anyreport || has_capability('coursereport/stats:view', $coursecontext));
+
+        $somereport     = $outlinetreport || $logtodayreport || $logreport || $statsreport;
 
-        $viewreports = ($anyreport || ($course->showreports && $iscurrentuser && $forceforcontext));
+        $viewreports = ($anyreport || $somereport || ($course->showreports && $iscurrentuser && $forceforcontext));
         if ($viewreports) {
             $reporttab = $usernode->add(get_string('activityreports'));
             $reportargs = array('user'=>$user->id);
@@ -1711,21 +1793,21 @@ class global_navigation extends navigation_node {
             } else {
                 $reportargs['id'] = SITEID;
             }
-            if ($viewreports || has_capability('coursereport/outline:view', $coursecontext)) {
+            if ($viewreports || $outlinetreport) {
                 $reporttab->add(get_string('outlinereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'outline'))));
                 $reporttab->add(get_string('completereport'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'complete'))));
             }
 
-            if ($viewreports || has_capability('coursereport/log:viewtoday', $coursecontext)) {
+            if ($viewreports || $logtodayreport) {
                 $reporttab->add(get_string('todaylogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'todaylogs'))));
             }
 
-            if ($viewreports || has_capability('coursereport/log:view', $coursecontext)) {
+            if ($viewreports || $logreport ) {
                 $reporttab->add(get_string('alllogs'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'alllogs'))));
             }
 
             if (!empty($CFG->enablestats)) {
-                if ($viewreports || has_capability('coursereport/stats:view', $coursecontext)) {
+                if ($viewreports || $statsreport) {
                     $reporttab->add(get_string('stats'), new moodle_url('/course/user.php', array_merge($reportargs, array('mode'=>'stats'))));
                 }
             }
index 47435b1..786444f 100644 (file)
@@ -1410,95 +1410,38 @@ class core_renderer extends renderer_base {
     */
     function render_rating(rating $rating) {
         global $CFG, $USER;
-        static $havesetupjavascript = false;
 
-        if( $rating->settings->aggregationmethod == RATING_AGGREGATE_NONE ){
+        if ($rating->settings->aggregationmethod == RATING_AGGREGATE_NONE) {
             return null;//ratings are turned off
         }
 
-        $useajax = !empty($CFG->enableajax);
-
-        //include required Javascript
-        if( !$havesetupjavascript && $useajax ) {
-            $this->page->requires->js_init_call('M.core_rating.init');
-            $havesetupjavascript = true;
-        }
-
-        //check the item we're rating was created in the assessable time window
-        $inassessablewindow = true;
-        if ( $rating->settings->assesstimestart && $rating->settings->assesstimefinish ) {
-            if ($rating->itemtimecreated < $rating->settings->assesstimestart || $rating->itemtimecreated > $rating->settings->assesstimefinish) {
-                $inassessablewindow = false;
-            }
-        }
+        $ratingmanager = new rating_manager();
+        // Initialise the JavaScript so ratings can be done by AJAX.
+        $ratingmanager->initialise_rating_javascript($this->page);
 
         $strrate = get_string("rate", "rating");
         $ratinghtml = ''; //the string we'll return
 
-        //permissions check - can they view the aggregate?
-        $canviewaggregate = false;
+        // permissions check - can they view the aggregate?
+        if ($rating->user_can_view_aggregate()) {
 
-        //if its the current user's item and they have permission to view the aggregate on their own items
-        if ( $rating->itemuserid==$USER->id && $rating->settings->permissions->view && $rating->settings->pluginpermissions->view) {
-            $canviewaggregate = true;
-        }
+            $aggregatelabel = $ratingmanager->get_aggregate_label($rating->settings->aggregationmethod);
+            $aggregatestr   = $rating->get_aggregate_string();
 
-        //if the item doesnt belong to anyone or its another user's items and they can see the aggregate on items they don't own
-        //Note that viewany doesnt mean you can see the aggregate or ratings of your own items
-        if ( (empty($rating->itemuserid) or $rating->itemuserid!=$USER->id) && $rating->settings->permissions->viewany && $rating->settings->pluginpermissions->viewany ) {
-            $canviewaggregate = true;
-        }
-
-        if ($canviewaggregate==true) {
-            $aggregatelabel = '';
-            switch ($rating->settings->aggregationmethod) {
-                case RATING_AGGREGATE_AVERAGE :
-                    $aggregatelabel .= get_string("aggregateavg", "rating");
-                    break;
-                case RATING_AGGREGATE_COUNT :
-                    $aggregatelabel .= get_string("aggregatecount", "rating");
-                    break;
-                case RATING_AGGREGATE_MAXIMUM :
-                    $aggregatelabel .= get_string("aggregatemax", "rating");
-                    break;
-                case RATING_AGGREGATE_MINIMUM :
-                    $aggregatelabel .= get_string("aggregatemin", "rating");
-                    break;
-                case RATING_AGGREGATE_SUM :
-                    $aggregatelabel .= get_string("aggregatesum", "rating");
-                    break;
-            }
-            $aggregatelabel .= get_string('labelsep', 'langconfig');
-
-            //$scalemax = 0;//no longer displaying scale max
-            $aggregatestr = '';
-
-            //only display aggregate if aggregation method isn't COUNT
-            if ($rating->aggregate && $rating->settings->aggregationmethod!= RATING_AGGREGATE_COUNT) {
-                if ($rating->settings->aggregationmethod!= RATING_AGGREGATE_SUM && is_array($rating->settings->scale->scaleitems)) {
-                    $aggregatestr .= $rating->settings->scale->scaleitems[round($rating->aggregate)];//round aggregate as we're using it as an index
-                }
-                else { //aggregation is SUM or the scale is numeric
-                    $aggregatestr .= round($rating->aggregate,1);
-                }
+            $aggregatehtml  = html_writer::tag('span', $aggregatestr, array('id' => 'ratingaggregate'.$rating->itemid)).' ';
+            $aggregatehtml .= html_writer::start_tag('span', array('id'=>"ratingcount{$rating->itemid}"));
+            if ($rating->count > 0) {
+                $aggregatehtml .= "({$rating->count})";
             } else {
-                $aggregatestr = '';
+                $aggregatehtml .= '-';
             }
-
-            $countstr = html_writer::start_tag('span', array('id'=>"ratingcount{$rating->itemid}"));
-            if ($rating->count>0) {
-                $countstr .= "({$rating->count})";
-            }
-            $countstr .= html_writer::end_tag('span');
-
-            //$aggregatehtml = "{$ratingstr} / $scalemax ({$rating->count}) ";
-            $aggregatehtml = "<span id='ratingaggregate{$rating->itemid}'>{$aggregatestr}</span> $countstr ";
+            $aggregatehtml .= html_writer::end_tag('span').' ';
 
             $ratinghtml .= html_writer::tag('span', $aggregatelabel, array('class'=>'rating-aggregate-label'));
             if ($rating->settings->permissions->viewall && $rating->settings->pluginpermissions->viewall) {
-                $url = "/rating/index.php?contextid={$rating->context->id}&itemid={$rating->itemid}&scaleid={$rating->settings->scale->id}";
-                $nonpopuplink = new moodle_url($url);
-                $popuplink = new moodle_url("$url&popup=1");
+
+                $nonpopuplink = $rating->get_view_ratings_url();
+                $popuplink = $rating->get_view_ratings_url(true);
 
                 $action = new popup_action('click', $popuplink, 'ratings', array('height' => 400, 'width' => 600));
                 $ratinghtml .= $this->action_link($nonpopuplink, $aggregatehtml, $action);
@@ -1508,81 +1451,45 @@ class core_renderer extends renderer_base {
         }
 
         $formstart = null;
-        //if the item doesn't belong to the current user, the user has permission to rate
-        //and we're within the assessable period
-        if ($rating->itemuserid!=$USER->id
-            && $rating->settings->permissions->rate
-            && $rating->settings->pluginpermissions->rate
-            && $inassessablewindow) {
-
-            //start the rating form
-            $formstart = html_writer::start_tag('form',
-                array('id'=>"postrating{$rating->itemid}", 'class'=>'postratingform', 'method'=>'post', 'action'=>"{$CFG->wwwroot}/rating/rate.php"));
-
-            $formstart .= html_writer::start_tag('div', array('class'=>'ratingform'));
-
-            //add the hidden inputs
-
-            $attributes = array('type'=>'hidden', 'class'=>'ratinginput', 'name'=>'contextid', 'value'=>$rating->context->id);
-            $formstart .= html_writer::empty_tag('input', $attributes);
-
-            $attributes['name'] = 'component';
-            $attributes['value'] = $rating->settings->component;
-            $formstart .= html_writer::empty_tag('input', $attributes);
-
-            $attributes['name'] = 'itemid';
-            $attributes['value'] = $rating->itemid;
-            $formstart .= html_writer::empty_tag('input', $attributes);
-
-            $attributes['name'] = 'scaleid';
-            $attributes['value'] = $rating->settings->scale->id;
-            $formstart .= html_writer::empty_tag('input', $attributes);
-
-            $attributes['name'] = 'returnurl';
-            $attributes['value'] = $rating->settings->returnurl;
-            $formstart .= html_writer::empty_tag('input', $attributes);
+        // if the item doesn't belong to the current user, the user has permission to rate
+        // and we're within the assessable period
+        if ($rating->user_can_rate()) {
 
-            $attributes['name'] = 'rateduserid';
-            $attributes['value'] = $rating->itemuserid;
-            $formstart .= html_writer::empty_tag('input', $attributes);
+            $rateurl = $rating->get_rate_url();
+            $inputs = $rateurl->params();
 
-            $attributes['name'] = 'aggregation';
-            $attributes['value'] = $rating->settings->aggregationmethod;
-            $formstart .= html_writer::empty_tag('input', $attributes);
-
-            $attributes['name'] = 'sesskey';
-            $attributes['value'] = sesskey();;
-            $formstart .= html_writer::empty_tag('input', $attributes);
+            //start the rating form
+            $formattrs = array(
+                'id'     => "postrating{$rating->itemid}",
+                'class'  => 'postratingform',
+                'method' => 'post',
+                'action' => $rateurl->out_omit_querystring()
+            );
+            $formstart  = html_writer::start_tag('form', $formattrs);
+            $formstart .= html_writer::start_tag('div', array('class' => 'ratingform'));
+
+            // add the hidden inputs
+            foreach ($inputs as $name => $value) {
+                $attributes = array('type' => 'hidden', 'class' => 'ratinginput', 'name' => $name, 'value' => $value);
+                $formstart .= html_writer::empty_tag('input', $attributes);
+            }
 
             if (empty($ratinghtml)) {
                 $ratinghtml .= $strrate.': ';
             }
-
             $ratinghtml = $formstart.$ratinghtml;
 
-            //generate an array of values for numeric scales
-            $scalearray = $rating->settings->scale->scaleitems;
-            if (!is_array($scalearray)) { //almost certainly a numerical scale
-                $intscalearray = intval($scalearray);//just in case they've passed "5" instead of 5
-                $scalearray = array();
-                if( is_int($intscalearray) && $intscalearray>0 ) {
-                    for($i=0; $i<=$rating->settings->scale->scaleitems; $i++) {
-                        $scalearray[$i] = $i;
-                    }
-                }
-            }
-
-            $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $scalearray;
-            $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid));
+            $scalearray = array(RATING_UNSET_RATING => $strrate.'...') + $rating->settings->scale->scaleitems;
+            $scaleattrs = array('class'=>'postratingmenu ratinginput','id'=>'menurating'.$rating->itemid);
+            $ratinghtml .= html_writer::select($scalearray, 'rating', $rating->rating, false, $scaleattrs);
 
             //output submit button
-
             $ratinghtml .= html_writer::start_tag('span', array('class'=>"ratingsubmit"));
 
-            $attributes = array('type'=>'submit', 'class'=>'postratingmenusubmit', 'id'=>'postratingsubmit'.$rating->itemid, 'value'=>s(get_string('rate', 'rating')));
+            $attributes = array('type' => 'submit', 'class' => 'postratingmenusubmit', 'id' => 'postratingsubmit'.$rating->itemid, 'value' => s(get_string('rate', 'rating')));
             $ratinghtml .= html_writer::empty_tag('input', $attributes);
 
-            if (is_array($rating->settings->scale->scaleitems)) {
+            if (!$rating->settings->scale->isnumeric) {
                 $ratinghtml .= $this->help_icon_scale($rating->settings->scale->courseid, $rating->settings->scale);
             }
             $ratinghtml .= html_writer::end_tag('span');
@@ -2424,7 +2331,7 @@ EOD;
             $content = $icon.$content; // use CSS for spacing of icons
         }
         if ($item->helpbutton !== null) {
-            $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton'));
+            $content = trim($item->helpbutton).html_writer::tag('span', $content, array('class'=>'clearhelpbutton', 'tabindex'=>'0'));
         }
         if ($content === '') {
             return '';
@@ -2447,7 +2354,7 @@ EOD;
             $content = html_writer::link($item->action, $content, $attributes);
 
         } else if (is_string($item->action) || empty($item->action)) {
-            $attributes = array();
+            $attributes = array('tabindex'=>'0'); //add tab support to span but still maintain character stream sequence.
             if ($title !== '') {
                 $attributes['title'] = $title;
             }
index 17e8298..ccfe57a 100644 (file)
@@ -347,10 +347,11 @@ class plugin_manager {
             ),
 
             'theme' => array(
-                'anomaly', 'arialist', 'base', 'binarius', 'boxxie', 'brick',
-                'canvas', 'formal_white', 'formfactor', 'fusion',
-                'leatherbound', 'magazine', 'nimble', 'nonzero', 'overlay',
-                'serenity', 'sky_high', 'splash', 'standard', 'standardold'
+                'afterburner', 'anomaly', 'arialist', 'base', 'binarius',
+                'boxxie', 'brick', 'canvas', 'formal_white', 'formfactor',
+                'fusion', 'leatherbound', 'magazine', 'nimble', 'nonzero',
+                'overlay', 'serenity', 'sky_high', 'splash', 'standard',
+                'standardold'
             ),
 
             'webservice' => array(
index 3159960..15dd6eb 100644 (file)
@@ -280,7 +280,7 @@ function rss_add_items($items) {
             $result .= rss_add_enclosures($item);
             $result .= rss_full_tag('pubDate',3,false,gmdate('D, d M Y H:i:s',$item->pubdate).' GMT');  # MDL-12563
             //Include the author if exists
-            if (isset($item->author)) {
+            if (isset($item->author) && !empty($item->author)) {
                 //$result .= rss_full_tag('author',3,false,$item->author);
                 //We put it in the description instead because it's more important
                 //for moodle than most other feeds, and most rss software seems to ignore
index 808ca53..b75d5cc 100644 (file)
@@ -98,6 +98,28 @@ class navigation_node_test extends UnitTestCase {
         $this->assertReference($node3, $this->node->get($node3->key, $node3->type));
     }
 
+    public function test_add_before() {
+        global $CFG;
+        // Create 3 nodes
+        $node1 = navigation_node::create('test_add_1', null, navigation_node::TYPE_CUSTOM,
+                'test 1', 'testadd1');
+        $node2 = navigation_node::create('test_add_2', null, navigation_node::TYPE_CUSTOM,
+                'test 2', 'testadd2');
+        $node3 = navigation_node::create('test_add_3', null, navigation_node::TYPE_CUSTOM,
+                'test 3', 'testadd3');
+        // Add node 2, then node 1 before 2, then node 3 at end
+        $this->node->add_node($node2);
+        $this->node->add_node($node1, 'testadd2');
+        $this->node->add_node($node3);
+        // Check the last 3 nodes are in 1, 2, 3 order and have those indexes
+        foreach($this->node->children as $child) {
+            $keys[] = $child->key;
+        }
+        $this->assertEqual('testadd1', $keys[count($keys)-3]);
+        $this->assertEqual('testadd2', $keys[count($keys)-2]);
+        $this->assertEqual('testadd3', $keys[count($keys)-1]);
+    }
+
     public function test_add_class() {
         $node = $this->node->get('demo1');
         $this->assertIsA($node, 'navigation_node');
index e57e89a..a65b771 100644 (file)
@@ -1333,7 +1333,7 @@ function stats_get_report_options($courseid,$mode) {
     case STATS_MODE_GENERAL:
         $reportoptions[STATS_REPORT_ACTIVITY] = get_string('statsreport'.STATS_REPORT_ACTIVITY);
         if ($courseid != SITEID && $context = get_context_instance(CONTEXT_COURSE, $courseid)) {
-            $sql = 'SELECT r.id, r.name FROM {role} r JOIN {stats_daily} s ON s.roleid = r.id WHERE s.courseid = :courseid GROUP BY s.roleid';
+            $sql = 'SELECT r.id, r.name FROM {role} r JOIN {stats_daily} s ON s.roleid = r.id WHERE s.courseid = :courseid GROUP BY r.id, r.name';
             if ($roles = $DB->get_records_sql($sql, array('courseid' => $courseid))) {
                 foreach ($roles as $role) {
                     $reportoptions[STATS_REPORT_ACTIVITYBYROLE.$role->id] = get_string('statsreport'.STATS_REPORT_ACTIVITYBYROLE). ' '.$role->name;
index 853e5f5..28c41f3 100644 (file)
@@ -152,7 +152,7 @@ function profiling_start() {
  * Stop profiling, gathering results and storing them
  */
 function profiling_stop() {
-    global $CFG, $SCRIPT;
+    global $CFG, $DB, $SCRIPT;
 
     // If profiling isn't available, nothing to stop
     if (!extension_loaded('xhprof') || !function_exists('xhprof_enable')) {
@@ -176,6 +176,14 @@ function profiling_stop() {
     profiling_is_running(false);
     $data = xhprof_disable();
 
+    // We only save the run after ensuring the DB table exists
+    // (this prevents problems with profiling runs enabled in
+    // config.php before Moodle is installed. Rare but...
+    $tables = $DB->get_tables();
+    if (!in_array('profiling', $tables)) {
+        return false;
+    }
+
     $run = new moodle_xhprofrun();
     $run->prepare_run($script);
     $runid = $run->save_run($data, null);
index 280542a..acf231b 100644 (file)
@@ -200,6 +200,8 @@ function message_get_blocked_users($user1=null, $user2=null) {
         $user2->isblocked = false;
     }
 
+    $blockedusers = array();
+
     $userfields = user_picture::fields('u', array('lastaccess'));
     $blockeduserssql = "SELECT $userfields, COUNT(m.id) AS messagecount
                           FROM {message_contacts} mc
@@ -210,18 +212,14 @@ function message_get_blocked_users($user1=null, $user2=null) {
                       ORDER BY u.firstname ASC";
     $rs =  $DB->get_recordset_sql($blockeduserssql, array('user1id1' => $user1->id, 'user1id2' => $user1->id));
 
-    $blockedusers = array();
-    if (!empty($rs)) {
-        foreach($rs as $rd) {
-            $blockedusers[] = $rd;
+    foreach($rs as $rd) {
+        $blockedusers[] = $rd;
 
-            if (!empty($user2) && $user2->id == $rd->id) {
-                $user2->isblocked = true;
-            }
+        if (!empty($user2) && $user2->id == $rd->id) {
+            $user2->isblocked = true;
         }
-        unset($rd);
-        $rs->close();
     }
+    $rs->close();
 
     return $blockedusers;
 }
@@ -1968,6 +1966,8 @@ function message_post_message($userfrom, $userto, $message, $format) {
  * Returns a list of all user ids who have used messaging in the site
  * This was the simple way to code the SQL ... is it going to blow up
  * on large datasets?
+ *
+ * @todo: deprecated - to be deleted in 2.2
  */
 function message_get_participants() {
     global $CFG, $DB;
@@ -2129,6 +2129,8 @@ function message_mark_messages_read($touserid, $fromuserid){
     foreach ($messages as $message) {
         message_mark_message_read($message, time());
     }
+
+    $messages->close();
 }
 
 /**
index b56aa8e..9540c4d 100644 (file)
@@ -144,12 +144,13 @@ $string['noblogs'] = 'You have no blog entries to submit!';
 $string['nofiles'] = 'No files were submitted';
 $string['nofilesyet'] = 'No files submitted yet';
 $string['nomoresubmissions'] = 'No further submissions are allowed.';
-$string['nosubmitusers'] = 'No users were found with permissions to submit this assignment';
 $string['notavailableyet'] = 'Sorry, this assignment is not yet available.<br />Assignment instructions will be displayed here on the date given below.';
 $string['notes'] = 'Notes';
 $string['notesempty'] = 'No entry';
 $string['notesupdateerror'] = 'Error when updating notes';
 $string['notgradedyet'] = 'Not graded yet';
+$string['norequiregrading'] = 'There are no assignments that require grading';
+$string['nosubmisson'] = 'No assignments have been submit';
 $string['notsubmittedyet'] = 'Not submitted yet';
 $string['onceassignmentsent'] = 'Once the assignment is sent for marking, you will no longer be able to delete or attach file(s). Do you want to continue?';
 $string['operation'] = 'Operation';
index c27ca95..eed4da0 100644 (file)
@@ -1128,6 +1128,7 @@ class assignment_base {
         $course     = $this->course;
         $assignment = $this->assignment;
         $cm         = $this->cm;
+        $hassubmission = false;
 
         $tabindex = 1; //tabindex for quick grading tabbing; Not working for dropdowns yet
         add_to_log($course->id, 'assignment', 'view submission', 'submissions.php?id='.$this->cm->id, $this->assignment->id, $this->cm->id);
@@ -1264,16 +1265,7 @@ class assignment_base {
         // Start working -- this is necessary as soon as the niceties are over
         $table->setup();
 
-        if (empty($users)) {
-            echo $OUTPUT->heading(get_string('nosubmitusers','assignment'));
-            echo '</div>';
-            return true;
-        }
-        if ($this->assignment->assignmenttype=='upload' || $this->assignment->assignmenttype=='online' || $this->assignment->assignmenttype=='uploadsingle') { //TODO: this is an ugly hack, where is the plugin spirit? (skodak)
-            echo '<div style="text-align:right"><a href="submissions.php?id='.$this->cm->id.'&amp;download=zip">'.get_string('downloadall', 'assignment').'</a></div>';
-        }
     /// Construct the SQL
-
         list($where, $params) = $table->get_sql_where();
         if ($where) {
             $where .= ' AND ';
@@ -1290,179 +1282,192 @@ class assignment_base {
         }
 
         $ufields = user_picture::fields('u');
+        if (!empty($users)) {
+            $select = "SELECT $ufields,
+                              s.id AS submissionid, s.grade, s.submissioncomment,
+                              s.timemodified, s.timemarked,
+                              COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ";
+            $sql = 'FROM {user} u '.
+                   'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
+                    AND s.assignment = '.$this->assignment->id.' '.
+                   'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
+
+            $ausers = $DB->get_records_sql($select.$sql.$sort, $params, $table->get_page_start(), $table->get_page_size());
+
+            $table->pagesize($perpage, count($users));
+
+            ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
+            $offset = $page * $perpage;
+            $strupdate = get_string('update');
+            $strgrade  = get_string('grade');
+            $grademenu = make_grades_menu($this->assignment->grade);
+
+            if ($ausers !== false) {
+                $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
+                $endposition = $offset + $perpage;
+                $currentposition = 0;
+                foreach ($ausers as $auser) {
+                    if ($currentposition == $offset && $offset < $endposition) {
+                        $final_grade = $grading_info->items[0]->grades[$auser->id];
+                        $grademax = $grading_info->items[0]->grademax;
+                        $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
+                        $locked_overridden = 'locked';
+                        if ($final_grade->overridden) {
+                            $locked_overridden = 'overridden';
+                        }
 
-        $select = "SELECT $ufields,
-                          s.id AS submissionid, s.grade, s.submissioncomment,
-                          s.timemodified, s.timemarked,
-                          COALESCE(SIGN(SIGN(s.timemarked) + SIGN(s.timemarked - s.timemodified)), 0) AS status ";
-        $sql = 'FROM {user} u '.
-               'LEFT JOIN {assignment_submissions} s ON u.id = s.userid
-                AND s.assignment = '.$this->assignment->id.' '.
-               'WHERE '.$where.'u.id IN ('.implode(',',$users).') ';
-
-        $ausers = $DB->get_records_sql($select.$sql.$sort, $params, $table->get_page_start(), $table->get_page_size());
-
-        $table->pagesize($perpage, count($users));
-
-        ///offset used to calculate index of student in that particular query, needed for the pop up to know who's next
-        $offset = $page * $perpage;
-        $strupdate = get_string('update');
-        $strgrade  = get_string('grade');
-        $grademenu = make_grades_menu($this->assignment->grade);
-
-        if ($ausers !== false) {
-            $grading_info = grade_get_grades($this->course->id, 'mod', 'assignment', $this->assignment->id, array_keys($ausers));
-            $endposition = $offset + $perpage;
-            $currentposition = 0;
-            foreach ($ausers as $auser) {
-                if ($currentposition == $offset && $offset < $endposition) {
-                    $final_grade = $grading_info->items[0]->grades[$auser->id];
-                    $grademax = $grading_info->items[0]->grademax;
-                    $final_grade->formatted_grade = round($final_grade->grade,2) .' / ' . round($grademax,2);
-                    $locked_overridden = 'locked';
-                    if ($final_grade->overridden) {
-                        $locked_overridden = 'overridden';
-                    }
+                    /// Calculate user status
+                        $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
+                        $picture = $OUTPUT->user_picture($auser);
 
-                /// Calculate user status
-                    $auser->status = ($auser->timemarked > 0) && ($auser->timemarked >= $auser->timemodified);
-                    $picture = $OUTPUT->user_picture($auser);
+                        if (empty($auser->submissionid)) {
+                            $auser->grade = -1; //no submission yet
+                        }
 
-                    if (empty($auser->submissionid)) {
-                        $auser->grade = -1; //no submission yet
-                    }
+                        if (!empty($auser->submissionid)) {
+                            $hassubmission = true;
+                        ///Prints student answer and student modified date
+                        ///attach file or print link to student answer, depending on the type of the assignment.
+                        ///Refer to print_student_answer in inherited classes.
+                            if ($auser->timemodified > 0) {
+                                $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
+                                                 . userdate($auser->timemodified).'</div>';
+                            } else {
+                                $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
+                            }
+                        ///Print grade, dropdown or text
+                            if ($auser->timemarked > 0) {
+                                $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
+
+                                if ($final_grade->locked or $final_grade->overridden) {
+                                    $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
+                                } else if ($quickgrade) {
+                                    $attributes = array();
+                                    $attributes['tabindex'] = $tabindex++;
+                                    $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
+                                    $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
+                                } else {
+                                    $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
+                                }
 
-                    if (!empty($auser->submissionid)) {
-                    ///Prints student answer and student modified date
-                    ///attach file or print link to student answer, depending on the type of the assignment.
-                    ///Refer to print_student_answer in inherited classes.
-                        if ($auser->timemodified > 0) {
-                            $studentmodified = '<div id="ts'.$auser->id.'">'.$this->print_student_answer($auser->id)
-                                             . userdate($auser->timemodified).'</div>';
+                            } else {
+                                $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
+                                if ($final_grade->locked or $final_grade->overridden) {
+                                    $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
+                                } else if ($quickgrade) {
+                                    $attributes = array();
+                                    $attributes['tabindex'] = $tabindex++;
+                                    $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
+                                    $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
+                                } else {
+                                    $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
+                                }
+                            }
+                        ///Print Comment
+                            if ($final_grade->locked or $final_grade->overridden) {
+                                $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
+
+                            } else if ($quickgrade) {
+                                $comment = '<div id="com'.$auser->id.'">'
+                                         . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
+                                         . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
+                            } else {
+                                $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
+                            }
                         } else {
                             $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
-                        }
-                    ///Print grade, dropdown or text
-                        if ($auser->timemarked > 0) {
-                            $teachermodified = '<div id="tt'.$auser->id.'">'.userdate($auser->timemarked).'</div>';
+                            $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
+                            $status          = '<div id="st'.$auser->id.'">&nbsp;</div>';
 
                             if ($final_grade->locked or $final_grade->overridden) {
-                                $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
-                            } else if ($quickgrade) {
+                                $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
+                                $hassubmission = true;
+                            } else if ($quickgrade) {   // allow editing
                                 $attributes = array();
                                 $attributes['tabindex'] = $tabindex++;
                                 $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
-                                $grade = '<div id="g'.$auser->id.'">'. $menu .'</div>';
+                                $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
+                                $hassubmission = true;
                             } else {
-                                $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
+                                $grade = '<div id="g'.$auser->id.'">-</div>';
                             }
 
-                        } else {
-                            $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
                             if ($final_grade->locked or $final_grade->overridden) {
-                                $grade = '<div id="g'.$auser->id.'" class="'. $locked_overridden .'">'.$final_grade->formatted_grade.'</div>';
+                                $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
                             } else if ($quickgrade) {
-                                $attributes = array();
-                                $attributes['tabindex'] = $tabindex++;
-                                $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
-                                $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
+                                $comment = '<div id="com'.$auser->id.'">'
+                                         . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
+                                         . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
                             } else {
-                                $grade = '<div id="g'.$auser->id.'">'.$this->display_grade($auser->grade).'</div>';
+                                $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
                             }
                         }
-                    ///Print Comment
-                        if ($final_grade->locked or $final_grade->overridden) {
-                            $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($final_grade->str_feedback),15).'</div>';
-
-                        } else if ($quickgrade) {
-                            $comment = '<div id="com'.$auser->id.'">'
-                                     . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
-                                     . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
-                        } else {
-                            $comment = '<div id="com'.$auser->id.'">'.shorten_text(strip_tags($auser->submissioncomment),15).'</div>';
-                        }
-                    } else {
-                        $studentmodified = '<div id="ts'.$auser->id.'">&nbsp;</div>';
-                        $teachermodified = '<div id="tt'.$auser->id.'">&nbsp;</div>';
-                        $status          = '<div id="st'.$auser->id.'">&nbsp;</div>';
-
-                        if ($final_grade->locked or $final_grade->overridden) {
-                            $grade = '<div id="g'.$auser->id.'">'.$final_grade->formatted_grade . '</div>';
-                        } else if ($quickgrade) {   // allow editing
-                            $attributes = array();
-                            $attributes['tabindex'] = $tabindex++;
-                            $menu = html_writer::select(make_grades_menu($this->assignment->grade), 'menu['.$auser->id.']', $auser->grade, array(-1=>get_string('nograde')), $attributes);
-                            $grade = '<div id="g'.$auser->id.'">'.$menu.'</div>';
-                        } else {
-                            $grade = '<div id="g'.$auser->id.'">-</div>';
-                        }
 
-                        if ($final_grade->locked or $final_grade->overridden) {
-                            $comment = '<div id="com'.$auser->id.'">'.$final_grade->str_feedback.'</div>';
-                        } else if ($quickgrade) {
-                            $comment = '<div id="com'.$auser->id.'">'
-                                     . '<textarea tabindex="'.$tabindex++.'" name="submissioncomment['.$auser->id.']" id="submissioncomment'
-                                     . $auser->id.'" rows="2" cols="20">'.($auser->submissioncomment).'</textarea></div>';
+                        if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
+                            $auser->status = 0;
                         } else {
-                            $comment = '<div id="com'.$auser->id.'">&nbsp;</div>';
+                            $auser->status = 1;
                         }
-                    }
-
-                    if (empty($auser->status)) { /// Confirm we have exclusively 0 or 1
-                        $auser->status = 0;
-                    } else {
-                        $auser->status = 1;
-                    }
 
-                    $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
+                        $buttontext = ($auser->status == 1) ? $strupdate : $strgrade;
 
-                    ///No more buttons, we use popups ;-).
-                    $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
-                               . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;filter='.$filter.'&amp;offset='.$offset++;
+                        ///No more buttons, we use popups ;-).
+                        $popup_url = '/mod/assignment/submissions.php?id='.$this->cm->id
+                                   . '&amp;userid='.$auser->id.'&amp;mode=single'.'&amp;filter='.$filter.'&amp;offset='.$offset++;
 
-                    $button = $OUTPUT->action_link($popup_url, $buttontext);
+                        $button = $OUTPUT->action_link($popup_url, $buttontext);
 
-                    $status  = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
+                        $status  = '<div id="up'.$auser->id.'" class="s'.$auser->status.'">'.$button.'</div>';
 
-                    $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
+                        $finalgrade = '<span id="finalgrade_'.$auser->id.'">'.$final_grade->str_grade.'</span>';
 
-                    $outcomes = '';
+                        $outcomes = '';
 
-                    if ($uses_outcomes) {
+                        if ($uses_outcomes) {
 
-                        foreach($grading_info->outcomes as $n=>$outcome) {
-                            $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
-                            $options = make_grades_menu(-$outcome->scaleid);
+                            foreach($grading_info->outcomes as $n=>$outcome) {
+                                $outcomes .= '<div class="outcome"><label>'.$outcome->name.'</label>';
+                                $options = make_grades_menu(-$outcome->scaleid);
 
-                            if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
-                                $options[0] = get_string('nooutcome', 'grades');
-                                $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
-                            } else {
-                                $attributes = array();
-                                $attributes['tabindex'] = $tabindex++;
-                                $attributes['id'] = 'outcome_'.$n.'_'.$auser->id;
-                                $outcomes .= ' '.html_writer::select($options, 'outcome_'.$n.'['.$auser->id.']', $outcome->grades[$auser->id]->grade, array(0=>get_string('nooutcome', 'grades')), $attributes);
+                                if ($outcome->grades[$auser->id]->locked or !$quickgrade) {
+                                    $options[0] = get_string('nooutcome', 'grades');
+                                    $outcomes .= ': <span id="outcome_'.$n.'_'.$auser->id.'">'.$options[$outcome->grades[$auser->id]->grade].'</span>';
+                                } else {
+                                    $attributes = array();
+                                    $attributes['tabindex'] = $tabindex++;
+                                    $attributes['id'] = 'outcome_'.$n.'_'.$auser->id;
+                                    $outcomes .= ' '.html_writer::select($options, 'outcome_'.$n.'['.$auser->id.']', $outcome->grades[$auser->id]->grade, array(0=>get_string('nooutcome', 'grades')), $attributes);
+                                }
+                                $outcomes .= '</div>';
                             }
-                            $outcomes .= '</div>';
                         }
-                    }
 
-                    $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser, has_capability('moodle/site:viewfullnames', $this->context)) . '</a>';
-                    $row = array($picture, $userlink, $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
-                    if ($uses_outcomes) {
-                        $row[] = $outcomes;
+                        $userlink = '<a href="' . $CFG->wwwroot . '/user/view.php?id=' . $auser->id . '&amp;course=' . $course->id . '">' . fullname($auser, has_capability('moodle/site:viewfullnames', $this->context)) . '</a>';
+                        $row = array($picture, $userlink, $grade, $comment, $studentmodified, $teachermodified, $status, $finalgrade);
+                        if ($uses_outcomes) {
+                            $row[] = $outcomes;
+                        }
+                        $table->add_data($row);
                     }
-
-                    $table->add_data($row);
+                    $currentposition++;
                 }
-                $currentposition++;
+            }
+            if ($hassubmission && ($this->assignment->assignmenttype=='upload' || $this->assignment->assignmenttype=='online' || $this->assignment->assignmenttype=='uploadsingle')) { //TODO: this is an ugly hack, where is the plugin spirit? (skodak)
+                echo html_writer::start_tag('div', array('class' => 'mod-assignment-download-link'));
+                echo html_writer::link(new moodle_url('/mod/assignment/submissions.php', array('id' => $this->cm->id, 'download' => 'zip')), get_string('downloadall', 'assignment'));
+                echo html_writer::end_tag('div');
+            }
+            $table->print_html();  /// Print the whole table
+        } else {
+            if ($filter == self::FILTER_SUBMITTED) {
+                echo html_writer::tag('div', get_string('nosubmisson', 'assignment'), array('class'=>'nosubmisson'));
+            } else if ($filter == self::FILTER_REQUIRE_GRADING) {
+                echo html_writer::tag('div', get_string('norequiregrading', 'assignment'), array('class'=>'norequiregrading'));
             }
         }
 
-        $table->print_html();  /// Print the whole table
-
         /// Print quickgrade form around the table
-        if ($quickgrade && $table->started_output){
+        if ($quickgrade && $table->started_output && !empty($users)){
             $mailinfopref = false;
             if (get_user_preferences('assignment_mailinfo', 1)) {
                 $mailinfopref = true;
@@ -2767,6 +2772,8 @@ function assignment_grade_item_delete($assignment) {
 /**
  * Returns the users with data in one assignment (students and teachers)
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param $assignmentid int
  * @return array of user objects
  */
index 62a660e..356ac17 100644 (file)
@@ -23,6 +23,7 @@
 #page-mod-assignment-submissions .submissions .grade,
 #page-mod-assignment-submissions .submissions .outcome,
 #page-mod-assignment-submissions .submissions .finalgrade {text-align: right;}
+#page-mod-assignment-submissions .submissions .header.noheader {display:none;}
 #page-mod-assignment-submissions .qgprefs #optiontable {text-align:right;margin-left:auto;}
 
 /** Styles for view.php **/
@@ -42,4 +43,6 @@
 #page-mod-assignment-submissions.dir-rtl .timemodified,
 #page-mod-assignment-submissions.dir-rtl .timemarked {text-align:right;}
 #page-mod-assignment-submissions.dir-rtl .mform.optionspref .fitem .fitemtitle {text-align:left;}
-#page-mod-assignment-type-uploadsingle-upload.dir-rtl .mdl-left {text-align:right;}
\ No newline at end of file
+#page-mod-assignment-type-uploadsingle-upload.dir-rtl .mdl-left {text-align:right;}
+
+.mod-assignment-download-link {text-align:right;}
\ No newline at end of file
index 6df859d..88bed4f 100644 (file)
@@ -430,7 +430,8 @@ function chat_cron () {
  * Returns the users with data in one chat
  * (users with records in chat_messages, students)
  *
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $chatid
  * @param int $groupid
  * @return array
index d211a2d..bd9b0c2 100644 (file)
@@ -39,7 +39,7 @@ class backup_choice_activity_structure_step extends backup_activity_structure_st
         // Define each element separated
         $choice = new backup_nested_element('choice', array('id'), array(
             'name', 'intro', 'introformat', 'publish',
-            'showresults', 'display', 'allowupdate', 'allowunanswered',
+            'showresults', 'display', 'allowupdate', 'showunanswered',
             'limitanswers', 'timeopen', 'timeclose', 'timemodified',
             'completionsubmit'));
 
index 4efe58b..6667305 100644 (file)
@@ -591,6 +591,8 @@ function choice_delete_instance($id) {
  * Returns the users with data in one choice
  * (users with records in choice_responses, students)
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $choiceid
  * @return array
  */
index 5032052..80f33bb 100644 (file)
@@ -40,7 +40,7 @@ class mod_choice_renderer extends plugin_renderer_base {
             $layoutclass = 'vertical';
         }
         $target = new moodle_url('/mod/choice/view.php');
-        $attributes = array('method'=>'POST', 'target'=>$target, 'class'=> $layoutclass);
+        $attributes = array('method'=>'POST', 'action'=>$target, 'class'=> $layoutclass);
 
         $html = html_writer::start_tag('form', $attributes);
         $html .= html_writer::start_tag('ul', array('class'=>'choices' ));
index 691b059..5a155bd 100644 (file)
@@ -69,7 +69,7 @@ class backup_data_activity_structure_step extends backup_activity_structure_step
         $ratings = new backup_nested_element('ratings');
 
         $rating = new backup_nested_element('rating', array('id'), array(
-            'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
+            'component', 'ratingarea', 'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
 
         // Build the tree
         $data->add_child($fields);
@@ -99,8 +99,10 @@ class backup_data_activity_structure_step extends backup_activity_structure_step
 
             $content->set_source_table('data_content', array('recordid' => backup::VAR_PARENTID));
 
-            $rating->set_source_table('rating', array('contextid' => backup::VAR_CONTEXTID,
-                                                      'itemid'    => backup::VAR_PARENTID));
+            $rating->set_source_table('rating', array('contextid'  => backup::VAR_CONTEXTID,
+                                                      'itemid'     => backup::VAR_PARENTID,
+                                                      'component'  => 'mod_data',
+                                                      'ratingarea' => 'entry'));
             $rating->set_source_alias('rating', 'value');
         }
 
index 15af06a..e22ee2d 100644 (file)
@@ -138,6 +138,14 @@ class restore_data_activity_structure_step extends restore_activity_structure_st
         $data->timecreated = $this->apply_date_offset($data->timecreated);
         $data->timemodified = $this->apply_date_offset($data->timemodified);
 
+        // We need to check that component and ratingarea are both set here.
+        if (empty($data->component)) {
+            $data->component = 'mod_data';
+        }
+        if (empty($data->ratingarea)) {
+            $data->ratingarea = 'entry';
+        }
+
         $newitemid = $DB->insert_record('rating', $data);
     }
 
index 371da31..9e06d45 100644 (file)
@@ -315,6 +315,29 @@ function xmldb_data_upgrade($oldversion) {
         upgrade_mod_savepoint(true, 2010100101, 'data');
     }
 
+    if ($oldversion < 2011052300) {
+        // rating.component and rating.ratingarea have now been added as mandatory fields.
+        // Presently you can only rate data entries so component = 'mod_data' and ratingarea = 'entry'
+        // for all ratings with a data context.
+        // We want to update all ratings that belong to a data context and don't already have a
+        // component set.
+        // This could take a while reset upgrade timeout to 5 min
+        upgrade_set_timeout(60 * 20);
+        $sql = "UPDATE {rating}
+                SET component = 'mod_data', ratingarea = 'entry'
+                WHERE contextid IN (
+                    SELECT ctx.id
+                      FROM {context} ctx
+                      JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                      JOIN {modules} m ON m.id = cm.module
+                     WHERE ctx.contextlevel = 70 AND
+                           m.name = 'data'
+                ) AND component = 'unknown'";
+        $DB->execute($sql);
+
+        upgrade_mod_savepoint(true, 2011052300, 'data');
+    }
+
     return true;
 }
 
index 2437ff3..8eb820a 100644 (file)
@@ -527,10 +527,10 @@ function data_generate_default_template(&$data, $template, $recordid=0, $form=fa
             $cell->attributes['class'] = 'controls';
             $table->data[] = new html_table_row(array($cell));
         } else if ($template == 'asearchtemplate') {
-            $row = new html_table_row(get_string('authorfirstname', 'data').': ', '##firstname##');
+            $row = new html_table_row(array(get_string('authorfirstname', 'data').': ', '##firstname##'));
             $row->attributes['class'] = 'searchcontrols';
             $table->data[] = $row;
-            $row = new html_table_row(get_string('authorlastname', 'data').': ', '##lastname##');
+            $row = new html_table_row(array(get_string('authorlastname', 'data').': ', '##lastname##'));
             $row->attributes['class'] = 'searchcontrols';
             $table->data[] = $row;
         }
@@ -962,7 +962,16 @@ function data_user_outline($course, $user, $mod, $data) {
     } else if ($grade) {
         $result = new stdClass();
         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
-        $result->time = $grade->dategraded;
+
+        //datesubmitted == time created. dategraded == time modified or time overridden
+        //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+        //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+        if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+            $result->time = $grade->dategraded;
+        } else {
+            $result->time = $grade->datesubmitted;
+        }
+
         return $result;
     }
     return NULL;
@@ -1007,9 +1016,10 @@ function data_get_user_grades($data, $userid=0) {
     global $CFG;
 
     require_once($CFG->dirroot.'/rating/lib.php');
-    $rm = new rating_manager();
 
-    $ratingoptions = new stdclass();
+    $ratingoptions = new stdClass;
+    $ratingoptions->component = 'mod_data';
+    $ratingoptions->ratingarea = 'entry';
     $ratingoptions->modulename = 'data';
     $ratingoptions->moduleid   = $data->id;
 
@@ -1019,6 +1029,7 @@ function data_get_user_grades($data, $userid=0) {
     $ratingoptions->itemtable = 'data_records';
     $ratingoptions->itemtableusercolumn = 'userid';
 
+    $rm = new rating_manager();
     return $rm->get_user_grades($ratingoptions);
 }
 
@@ -1135,25 +1146,46 @@ function data_grade_item_delete($data) {
 /**
  * returns a list of participants of this database
  *
- * @global object
+ * Returns the users with data in one data
+ * (users with records in data_records, data_comments and ratings)
+ *
+ * @todo: deprecated - to be deleted in 2.2
+ *
+ * @param int $dataid
  * @return array
  */
 function data_get_participants($dataid) {
-// Returns the users with data in one data
-// (users with records in data_records, data_comments and ratings)
     global $DB;
 
-    $records = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                       FROM {user} u, {data_records} r
-                                      WHERE r.dataid = ? AND u.id = r.userid", array($dataid));
-
-    $comments = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                        FROM {user} u, {data_records} r, {comments} c
-                                       WHERE r.dataid = ? AND u.id = r.userid AND r.id = c.itemid AND c.commentarea='database_entry'", array($dataid));
-
-    $ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                       FROM {user} u, {data_records} r, {ratings} a
-                                      WHERE r.dataid = ? AND u.id = r.userid AND r.id = a.itemid", array($dataid));
+    $params = array('dataid' => $dataid);
+
+    $sql = "SELECT DISTINCT u.id, u.id
+              FROM {user} u,
+                   {data_records} r
+             WHERE r.dataid = :dataid AND
+                   u.id = r.userid";
+    $records = $DB->get_records_sql($sql, $params);
+
+    $sql = "SELECT DISTINCT u.id, u.id
+              FROM {user} u,
+                   {data_records} r,
+                   {comments} c
+             WHERE r.dataid = ? AND
+                   u.id = r.userid AND
+                   r.id = c.itemid AND
+                   c.commentarea = 'database_entry'";
+    $comments = $DB->get_records_sql($sql, $params);
+
+    $sql = "SELECT DISTINCT u.id, u.id
+              FROM {user} u,
+                   {data_records} r,
+                   {ratings} a
+             WHERE r.dataid = ? AND
+                   u.id = r.userid AND
+                   r.id = a.itemid AND
+                   a.component = 'mod_data' AND
+                   a.ratingarea = 'entry'";
+    $ratings = $DB->get_records_sql($sql, $params);
 
     $participants = array();
 
@@ -1348,20 +1380,23 @@ function data_print_template($template, $records, $data, $search='', $page=0, $r
 
 /**
  * Return rating related permissions
- * @param string $options the context id
+ *
+ * @param string $contextid the context id
+ * @param string $component the component to get rating permissions for
+ * @param string $ratingarea the rating area to get permissions for
  * @return array an associative array of the user's rating permissions
  */
-function data_rating_permissions($options) {
-    $contextid = $options;
-    $context = get_context_instance_by_id($contextid);
-
-    if (!$context) {
-        print_error('invalidcontext');
+function data_rating_permissions($contextid, $component, $ratingarea) {
+    $context = get_context_instance_by_id($contextid, MUST_EXIST);
+    if ($component != 'mod_data' || $ratingarea != 'entry') {
         return null;
-    } else {
-        $ret = new stdclass();
-        return array('view'=>has_capability('mod/data:viewrating',$context), 'viewany'=>has_capability('mod/data:viewanyrating',$context), 'viewall'=>has_capability('mod/data:viewallratings',$context), 'rate'=>has_capability('mod/data:rate',$context));
     }
+    return array(
+        'view'    => has_capability('mod/data:viewrating',$context),
+        'viewany' => has_capability('mod/data:viewanyrating',$context),
+        'viewall' => has_capability('mod/data:viewallratings',$context),
+        'rate'    => has_capability('mod/data:rate',$context)
+    );
 }
 
 /**
@@ -1378,11 +1413,22 @@ function data_rating_permissions($options) {
 function data_rating_validate($params) {
     global $DB, $USER;
 
-    if (!array_key_exists('itemid', $params) || !array_key_exists('context', $params) || !array_key_exists('rateduserid', $params)) {
-        throw new rating_exception('missingparameter');
+    // Check the component is mod_data
+    if ($params['component'] != 'mod_data') {
+        throw new rating_exception('invalidcomponent');
+    }
+
+    // Check the ratingarea is entry (the only rating area in data module)
+    if ($params['ratingarea'] != 'entry') {
+        throw new rating_exception('invalidratingarea');
     }
 
-    $datasql = "SELECT d.id as did, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid
+    // Check the rateduserid is not the current user .. you can't rate your own entries
+    if ($params['rateduserid'] == $USER->id) {
+        throw new rating_exception('nopermissiontorate');
+    }
+
+    $datasql = "SELECT d.id as dataid, d.scale, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid
                   FROM {data_records} r
                   JOIN {data} d ON r.dataid = d.id
                  WHERE r.id = :itemid";
@@ -1392,14 +1438,33 @@ function data_rating_validate($params) {
         throw new rating_exception('invaliditemid');
     }
 
-    if ($info->userid == $USER->id) {
-        //user is attempting to rate their own glossary entry
-        throw new rating_exception('nopermissiontorate');
+    if ($info->scale != $params['scaleid']) {
+        //the scale being submitted doesnt match the one in the database
+        throw new rating_exception('invalidscaleid');
     }
 
-    if ($params['rateduserid'] != $info->userid) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    //check that the submitted rating is valid for the scale
+
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
+        throw new rating_exception('invalidnum');
+    }
+
+    // upper limit
+    if ($info->scale < 0) {
+        //its a custom scale
+        $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
+        if ($scalerecord) {
+            $scalearray = explode(',', $scalerecord->scale);
+            if ($params['rating'] > count($scalearray)) {
+                throw new rating_exception('invalidnum');
+            }
+        } else {
+            throw new rating_exception('invalidscaleid');
+        }
+    } else if ($params['rating'] > $info->scale) {
+        //if its numeric and submitted rating is above maximum
+        throw new rating_exception('invalidnum');
     }
 
     if ($info->approval && !$info->approved) {
@@ -1407,30 +1472,24 @@ function data_rating_validate($params) {
         throw new rating_exception('nopermissiontorate');
     }
 
-    //check the item we're rating was created in the assessable time window
+    // check the item we're rating was created in the assessable time window
     if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
         if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
             throw new rating_exception('notavailable');
         }
     }
 
-    $dataid = $info->did;
-    $groupid = $info->groupid;
-    $courseid = $info->course;
-
-    $cm = get_coursemodule_from_instance('data', $dataid);
-    if (empty($cm)) {
-        throw new rating_exception('unknowncontext');
-    }
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+    $course = $DB->get_record('course', array('id'=>$info->course), '*', MUST_EXIST);
+    $cm = get_coursemodule_from_instance('data', $info->dataid, $course->id, false, MUST_EXIST);
+    $context = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
 
-    //if the supplied context doesnt match the item's context
-    if (empty($context) || $context->id != $params['context']->id) {
+    // if the supplied context doesnt match the item's context
+    if ($context->id != $params['context']->id) {
         throw new rating_exception('invalidcontext');
     }
 
     // Make sure groups allow this user to see the item they're rating
-    $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
+    $groupid = $info->groupid;
     if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
         if (!groups_group_exists($groupid)) { // Can't find group
             throw new rating_exception('cannotfindgroup');//something is wrong
@@ -1640,7 +1699,7 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order='
  */
 function data_print_ratings($data, $record) {
     global $OUTPUT;
-    if( !empty($record->rating) ){
+    if (!empty($record->rating)){
         echo $OUTPUT->render($record->rating);
     }
 }
@@ -2487,7 +2546,9 @@ function data_reset_userdata($data) {
                      WHERE d.course=?";
 
     $rm = new rating_manager();
-    $ratingdeloptions = new stdclass();
+    $ratingdeloptions = new stdClass;
+    $ratingdeloptions->component = 'mod_data';
+    $ratingdeloptions->ratingarea = 'entry';
 
     // delete entries if requested
     if (!empty($data->reset_data)) {
index b57666a..deba450 100644 (file)
@@ -5,8 +5,8 @@
 //  This fragment is called by /admin/index.php
 ////////////////////////////////////////////////////////////////////////////////
 
-$module->version  = 2010100101;
-$module->requires = 2010080300;  // Requires this Moodle version
+$module->version  = 2011052300;
+$module->requires = 2011052300;  // Requires this Moodle version
 $module->cron     = 60;
 
 
index ffb074f..de9edb5 100644 (file)
@@ -668,10 +668,11 @@ if ($showactivity) {
                 //data_print_template() only adds ratings for singletemplate which is why we're attaching them here
                 //attach ratings to data records
                 require_once($CFG->dirroot.'/rating/lib.php');
-                if ($data->assessed!=RATING_AGGREGATE_NONE) {
-                    $ratingoptions = new stdclass();
+                if ($data->assessed != RATING_AGGREGATE_NONE) {
+                    $ratingoptions = new stdClass;
                     $ratingoptions->context = $context;
                     $ratingoptions->component = 'mod_data';
+                    $ratingoptions->ratingarea = 'entry';
                     $ratingoptions->items = $records;
                     $ratingoptions->aggregate = $data->assessed;//the aggregation method
                     $ratingoptions->scaleid = $data->scale;
index 0cdead1..e99c919 100644 (file)
@@ -27,6 +27,9 @@ $messageproviders = array (
 
 /// Submitting a feedback
     'submission' => array (
+    ),
+/// Message to nonrespondents
+    'message' => array (
     )
 
 );
index a75d8c9..bc297e5 100644 (file)
@@ -467,6 +467,7 @@ function feedback_cron () {
 }
 
 /**
+ * @todo: deprecated - to be deleted in 2.2
  * @return bool false
  */
 function feedback_get_participants($feedbackid) {
index 4bbc51b..92a3d82 100644 (file)
@@ -75,8 +75,8 @@
             foreach ($messageuser as $userid) {
                 $senduser = $DB->get_record('user', array('id'=>$userid));
                 $eventdata = new stdClass();
-                $eventdata->name             = 'feedback';
-                $eventdata->component        = 'mod';
+                $eventdata->name             = 'message';
+                $eventdata->component        = 'mod_feedback';
                 $eventdata->userfrom         = $USER;
                 $eventdata->userto           = $senduser;
                 $eventdata->subject          = $subject;
index 460a9d5..78178bd 100644 (file)
@@ -9,7 +9,7 @@
 */
 
 
-    $module->version = 2010112302; // The current module version (Date: YYYYMMDDXX)
+    $module->version = 2011051600; // The current module version (Date: YYYYMMDDXX)
     $module->requires = 2010080300;  // Requires this Moodle version
     $feedback_version_intern = 1; //this version is used for restore older backups
     $module->cron = 0; // Period for cron to check this module (secs)
index 3cffd25..49c1bba 100644 (file)
@@ -205,6 +205,8 @@ function folder_user_complete($course, $user, $mod, $folder) {
 /**
  * Returns the users with data in one folder
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $folderid
  * @return bool false
  */
index 9eb48c2..f9c1bc4 100644 (file)
@@ -63,7 +63,7 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
         $ratings = new backup_nested_element('ratings');
 
         $rating = new backup_nested_element('rating', array('id'), array(
-            'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
+            'component', 'ratingarea', 'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
 
         $subscriptions = new backup_nested_element('subscriptions');
 
@@ -125,8 +125,10 @@ class backup_forum_activity_structure_step extends backup_activity_structure_ste
 
             $track->set_source_table('forum_track_prefs', array('forumid' => backup::VAR_PARENTID));
 
-            $rating->set_source_table('rating', array('contextid' => backup::VAR_CONTEXTID,
-                                                      'itemid'    => backup::VAR_PARENTID));
+            $rating->set_source_table('rating', array('contextid'  => backup::VAR_CONTEXTID,
+                                                      'component'  => 'mod_forum',
+                                                      'ratingarea' => 'post',
+                                                      'itemid'     => backup::VAR_PARENTID));
             $rating->set_source_alias('rating', 'value');
         }
 
index 17e8820..40c1b39 100644 (file)
@@ -126,6 +126,14 @@ class restore_forum_activity_structure_step extends restore_activity_structure_s
         $data->timecreated = $this->apply_date_offset($data->timecreated);
         $data->timemodified = $this->apply_date_offset($data->timemodified);
 
+        // We need to check that component and ratingarea are both set here.
+        if (empty($data->component)) {
+            $data->component = 'mod_forum';
+        }
+        if (empty($data->ratingarea)) {
+            $data->ratingarea = 'post';
+        }
+
         $newitemid = $DB->insert_record('rating', $data);
     }
 
index 3a56755..5f9a0db 100644 (file)
@@ -315,6 +315,28 @@ function xmldb_forum_upgrade($oldversion) {
         upgrade_mod_savepoint(true, 2010091900, 'forum');
     }
 
+    if ($oldversion < 2011052300) {
+        // rating.component and rating.ratingarea have now been added as mandatory fields.
+        // Presently you can only rate forum posts so component = 'mod_forum' and ratingarea = 'post'
+        // for all ratings with a forum context.
+        // We want to update all ratings that belong to a forum context and don't already have a
+        // component set.
+        // This could take a while reset upgrade timeout to 5 min
+        upgrade_set_timeout(60 * 20);
+        $sql = "UPDATE {rating}
+                SET component = 'mod_forum', ratingarea = 'post'
+                WHERE contextid IN (
+                    SELECT ctx.id
+                      FROM {context} ctx
+                      JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                      JOIN {modules} m ON m.id = cm.module
+                     WHERE ctx.contextlevel = 70 AND
+                           m.name = 'forum'
+                ) AND component = 'unknown'";
+        $DB->execute($sql);
+
+        upgrade_mod_savepoint(true, 2011052300, 'forum');
+    }
 
     return true;
 }
index 9599e72..b43997b 100644 (file)
@@ -1131,7 +1131,16 @@ function forum_user_outline($course, $user, $mod, $forum) {
     } else if ($grade) {
         $result = new stdClass();
         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
-        $result->time = $grade->dategraded;
+
+        //datesubmitted == time created. dategraded == time modified or time overridden
+        //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+        //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+        if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+            $result->time = $grade->dategraded;
+        } else {
+            $result->time = $grade->datesubmitted;
+        }
+
         return $result;
     }
     return NULL;
@@ -1436,25 +1445,25 @@ function forum_print_recent_activity($course, $viewfullnames, $timestart) {
  * @param int $userid optional user id, 0 means all users
  * @return array array of grades, false if none
  */
-function forum_get_user_grades($forum, $userid=0) {
+function forum_get_user_grades($forum, $userid = 0) {
     global $CFG;
 
     require_once($CFG->dirroot.'/rating/lib.php');
-    $rm = new rating_manager();
 
-    $ratingoptions = new stdclass();
+    $ratingoptions = new stdClass;
+    $ratingoptions->component = 'mod_forum';
+    $ratingoptions->ratingarea = 'post';
 
     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
     $ratingoptions->modulename = 'forum';
     $ratingoptions->moduleid   = $forum->id;
-    //$ratingoptions->cmidnumber = $forum->cmidnumber;
-
     $ratingoptions->userid = $userid;
     $ratingoptions->aggregationmethod = $forum->assessed;
     $ratingoptions->scaleid = $forum->scale;
     $ratingoptions->itemtable = 'forum_posts';
     $ratingoptions->itemtableusercolumn = 'userid';
 
+    $rm = new rating_manager();
     return $rm->get_user_grades($ratingoptions);
 }
 
@@ -1577,8 +1586,8 @@ function forum_grade_item_delete($forum) {
  * Returns the users with data in one forum
  * (users with records in forum_subscriptions, forum_posts, students)
  *
- * @global object
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $forumid
  * @return mixed array or false if none
  */
@@ -1586,31 +1595,35 @@ function forum_get_participants($forumid) {
 
     global $CFG, $DB;
 
+    $params = array('forumid' => $forumid);
+
     //Get students from forum_subscriptions
-    $st_subscriptions = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                         FROM {user} u,
-                                              {forum_subscriptions} s
-                                         WHERE s.forum = ? AND
-                                               u.id = s.userid", array($forumid));
+    $sql = "SELECT DISTINCT u.id, u.id
+              FROM {user} u,
+                   {forum_subscriptions} s
+             WHERE s.forum = :forumid AND
+                   u.id = s.userid";
+    $st_subscriptions = $DB->get_records_sql($sql, $params);
+
     //Get students from forum_posts
-    $st_posts = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                 FROM {user} u,
-                                      {forum_discussions} d,
-                                      {forum_posts} p
-                                 WHERE d.forum = ? AND
-                                       p.discussion = d.id AND
-                                       u.id = p.userid", array($forumid));
+    $sql = "SELECT DISTINCT u.id, u.id
+              FROM {user} u,
+                   {forum_discussions} d,
+                   {forum_posts} p
+              WHERE d.forum = :forumid AND
+                    p.discussion = d.id AND
+                    u.id = p.userid";
+    $st_posts = $DB->get_records_sql($sql, $params);
 
     //Get students from the ratings table
-    $st_ratings = $DB->get_records_sql("SELECT DISTINCT u.id, u.id
-                                   FROM {user} u,
-                                        {forum_discussions} d,
-                                        {forum_posts} p,
-                                        {ratings} r
-                                   WHERE d.forum = ? AND
-                                         p.discussion = d.id AND
-                                         r.post = p.id AND
-                                         u.id = r.userid", array($forumid));
+    $sql = "SELECT DISTINCT r.userid, r.userid AS id
+              FROM {forum_discussions} d
+              JOIN {forum_posts} p ON p.discussion = d.id
+              JOIN {rating} r on r.itemid = p.id
+             WHERE d.forum = :forumid AND
+                   r.component = 'mod_forum' AND
+                   r.ratingarea = 'post'";
+    $st_ratings = $DB->get_records_sql($sql, $params);
 
     //Add st_posts to st_subscriptions
     if ($st_posts) {
@@ -2041,29 +2054,29 @@ function forum_search_posts($searchterms, $courseid=0, $limitfrom=0, $limitnum=5
 /**
  * Returns a list of ratings for a particular post - sorted.
  *
- * @global object
- * @global object
+ * TODO: Check if this function is actually used anywhere.
+ * Up until the fix for MDL-27471 this function wasn't even returning.
+ *
+ * @param stdClass $context
  * @param int $postid
  * @param string $sort
  * @return array Array of ratings or false
  */
-function forum_get_ratings($context, $postid, $sort="u.firstname ASC") {
-    global $PAGE;
-
-    $options = new stdclass();
-    $options->context = $PAGE->context;
+function forum_get_ratings($context, $postid, $sort = "u.firstname ASC") {
+    $options = new stdClass;
+    $options->context = $context;
+    $options->component = 'mod_forum';
+    $options->ratingarea = 'post';
     $options->itemid = $postid;
     $options->sort = "ORDER BY $sort";
 
     $rm = new rating_manager();
-    $rm->get_all_ratings_for_item($options);
+    return $rm->get_all_ratings_for_item($options);
 }
 
 /**
  * Returns a list of all new posts that have not been mailed yet
  *
- * @global object
- * @global object
  * @param int $starttime posts created after this time
  * @param int $endtime posts created before this
  * @param int $now used for timed discussions only
@@ -2447,29 +2460,35 @@ function forum_count_discussions($forum, $cm, $course) {
 /**
  * How many posts by other users are unrated by a given user in the given discussion?
  *
- * @global object
- * @global object
+ * TODO: Is this function still used anywhere?
+ *
  * @param int $discussionid
  * @param int $userid
  * @return mixed
  */
 function forum_count_unrated_posts($discussionid, $userid) {
     global $CFG, $DB;
-    if ($posts = $DB->get_record_sql("SELECT count(*) as num
-                                   FROM {forum_posts}
-                                  WHERE parent > 0
-                                    AND discussion = ?
-                                    AND userid <> ? ", array($discussionid, $userid))) {
-
-        if ($rated = $DB->get_record_sql("SELECT count(*) as num
-                                       FROM {forum_posts} p,
-                                            {rating} r
-                                      WHERE p.discussion = ?
-                                        AND p.id = r.itemid
-                                        AND r.userid = ?", array($discussionid, $userid))) {
-            $difference = $posts->num - $rated->num;
-            if ($difference > 0) {
-                return $difference;
+
+    $sql = "SELECT COUNT(*) as num
+              FROM {forum_posts}
+             WHERE parent > 0
+               AND discussion = :discussionid
+               AND userid <> :userid";
+    $params = array('discussionid' => $discussionid, 'userid' => $userid);
+    $posts = $DB->get_record_sql($sql, $params);
+    if ($posts) {
+        $sql = "SELECT count(*) as num
+                  FROM {forum_posts} p,
+                       {rating} r
+                 WHERE p.discussion = :discussionid AND
+                       p.id = r.itemid AND
+                       r.userid = userid AND
+                       r.component = 'mod_forum' AND
+                       r.ratingarea = 'post'";
+        $rated = $DB->get_record_sql($sql, $params);
+        if ($rated) {
+            if ($posts->num > $rated->num) {
+                return $posts->num - $rated->num;
             } else {
                 return 0;    // Just in case there was a counting error
             }
@@ -3442,24 +3461,31 @@ function forum_print_post($post, $discussion, $forum, &$cm, $course, $ownpost=fa
 
 /**
  * Return rating related permissions
+ *
  * @param string $options the context id
  * @return array an associative array of the user's rating permissions
  */
-function forum_rating_permissions($contextid) {
-    $context = get_context_instance_by_id($contextid);
-
-    if (!$context) {
-        print_error('invalidcontext');
+function forum_rating_permissions($contextid, $component, $ratingarea) {
+    $context = get_context_instance_by_id($contextid, MUST_EXIST);
+    if ($component != 'mod_forum' || $ratingarea != 'post') {
+        // We don't know about this component/ratingarea so just return null to get the
+        // default restrictive permissions.
         return null;
-    } else {
-        return array('view'=>has_capability('mod/forum:viewrating',$context), 'viewany'=>has_capability('mod/forum:viewanyrating',$context), 'viewall'=>has_capability('mod/forum:viewallratings',$context), 'rate'=>has_capability('mod/forum:rate',$context));
     }
+    return array(
+        'view'    => has_capability('mod/forum:viewrating', $context),
+        'viewany' => has_capability('mod/forum:viewanyrating', $context),
+        'viewall' => has_capability('mod/forum:viewallratings', $context),
+        'rate'    => has_capability('mod/forum:rate', $context)
+    );
 }
 
 /**
  * Validates a submitted rating
  * @param array $params submitted data
  *            context => object the context in which the rated items exists [required]
+ *            component => The component for this module - should always be mod_forum [required]
+ *            ratingarea => object the context in which the rated items exists [required]
  *            itemid => int the ID of the object being rated [required]
  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
  *            rating => int the submitted rating [required]
@@ -3470,81 +3496,84 @@ function forum_rating_permissions($contextid) {
 function forum_rating_validate($params) {
     global $DB, $USER;
 
-    if (!array_key_exists('itemid', $params) || !array_key_exists('context', $params) || !array_key_exists('rateduserid', $params)) {
-        throw new rating_exception('missingparameter');
+    // Check the component is mod_forum
+    if ($params['component'] != 'mod_forum') {
+        throw new rating_exception('invalidcomponent');
     }
 
-    $forumsql = "SELECT f.id as fid, f.course, d.id as did, p.userid as userid, p.created, f.assesstimestart, f.assesstimefinish, d.groupid
-                   FROM {forum_posts} p
-                   JOIN {forum_discussions} d ON p.discussion = d.id
-                   JOIN {forum} f ON d.forum = f.id
-                  WHERE p.id = :itemid";
-    $forumparams = array('itemid'=>$params['itemid']);
-    if (!$info = $DB->get_record_sql($forumsql, $forumparams)) {
-        //item doesn't exist
-        throw new rating_exception('invaliditemid');
+    // Check the ratingarea is post (the only rating area in forum)
+    if ($params['ratingarea'] != 'post') {
+        throw new rating_exception('invalidratingarea');
     }
 
-    if ($info->userid == $USER->id) {
-        //user is attempting to rate their own post
+    // Check the rateduserid is not the current user .. you can't rate your own posts
+    if ($params['rateduserid'] == $USER->id) {
         throw new rating_exception('nopermissiontorate');
     }
 
-    if ($params['rateduserid'] != $info->userid) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    // Fetch all the related records ... we need to do this anyway to call forum_user_can_see_post
+    $post = $DB->get_record('forum_posts', array('id' => $params['itemid'], 'userid' => $params['rateduserid']), '*', MUST_EXIST);
+    $discussion = $DB->get_record('forum_discussions', array('id' => $post->discussion), '*', MUST_EXIST);
+    $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
+    $course = $DB->get_record('course', array('id' => $forum->course), '*', MUST_EXIST);
+    $cm = get_coursemodule_from_instance('forum', $forum->id, $course->id , false, MUST_EXIST);
+    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+
+    // Make sure the context provided is the context of the forum
+    if ($context->id != $params['context']->id) {
+        throw new rating_exception('invalidcontext');
+    }
+
+    if ($forum->scale != $params['scaleid']) {
+        //the scale being submitted doesnt match the one in the database
+        throw new rating_exception('invalidscaleid');
     }
 
-    //check the item we're rating was created in the assessable time window
-    if (!empty($info->assesstimestart) && !empty($info->assesstimefinish)) {
-        if ($info->timecreated < $info->assesstimestart || $info->timecreated > $info->assesstimefinish) {
+    // check the item we're rating was created in the assessable time window
+    if (!empty($forum->assesstimestart) && !empty($forum->assesstimefinish)) {
+        if ($post->created < $forum->assesstimestart || $post->created > $forum->assesstimefinish) {
             throw new rating_exception('notavailable');
         }
     }
 
-    $forumid = $info->fid;
-    $discussionid = $info->did;
-    $groupid = $info->groupid;
-    $courseid = $info->course;
+    //check that the submitted rating is valid for the scale
 
-    $cm = get_coursemodule_from_instance('forum', $forumid);
-    if (empty($cm)) {
-        throw new rating_exception('unknowncontext');
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
+        throw new rating_exception('invalidnum');
     }
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
 
-    //if the supplied context doesnt match the item's context
-    if (empty($context) || $context->id != $params['context']->id) {
-        throw new rating_exception('invalidcontext');
+    // upper limit
+    if ($forum->scale < 0) {
+        //its a custom scale
+        $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
+        if ($scalerecord) {
+            $scalearray = explode(',', $scalerecord->scale);
+            if ($params['rating'] > count($scalearray)) {
+                throw new rating_exception('invalidnum');
+            }
+        } else {
+            throw new rating_exception('invalidscaleid');
+        }
+    } else if ($params['rating'] > $forum->scale) {
+        //if its numeric and submitted rating is above maximum
+        throw new rating_exception('invalidnum');
     }
 
     // Make sure groups allow this user to see the item they're rating
-    $course = $DB->get_record('course', array('id'=>$courseid), '*', MUST_EXIST);
-    if ($groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
-        if (!groups_group_exists($groupid)) { // Can't find group
+    if ($discussion->groupid > 0 and $groupmode = groups_get_activity_groupmode($cm, $course)) {   // Groups are being used
+        if (!groups_group_exists($discussion->groupid)) { // Can't find group
             throw new rating_exception('cannotfindgroup');//something is wrong
         }
 
-        if (!groups_is_member($groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
+        if (!groups_is_member($discussion->groupid) and !has_capability('moodle/site:accessallgroups', $context)) {
             // do not allow rating of posts from other groups when in SEPARATEGROUPS or VISIBLEGROUPS
             throw new rating_exception('notmemberofgroup');
         }
     }
 
-    //need to load the full objects here as ajax scripts don't like
-    //the debugging messages produced by forum_user_can_see_post() if you just supply IDs
-    if (!$forum = $DB->get_record('forum',array('id'=>$forumid))) {
-        throw new rating_exception('invalidrecordunknown');
-    }
-    if (!$post = $DB->get_record('forum_posts',array('id'=>$params['itemid']))) {
-        throw new rating_exception('invalidrecordunknown');
-    }
-    if (!$discussion = $DB->get_record('forum_discussions',array('id'=>$discussionid))) {
-        throw new rating_exception('invalidrecordunknown');
-    }
-
-    //perform some final capability checks
-    if( !forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
+    // perform some final capability checks
+    if (!forum_user_can_see_post($forum, $discussion, $post, $USER, $cm)) {
         throw new rating_exception('nopermissiontorate');
     }
 
@@ -4314,8 +4343,10 @@ function forum_delete_post($post, $children, $course, $cm, $forum, $skipcompleti
 
     //delete ratings
     require_once($CFG->dirroot.'/rating/lib.php');
-    $delopt = new stdclass();
+    $delopt = new stdClass;
     $delopt->contextid = $context->id;
+    $delopt->component = 'mod_forum';
+    $delopt->ratingarea = 'post';
     $delopt->itemid = $post->id;
     $rm = new rating_manager();
     $rm->delete_ratings($delopt);
@@ -5350,32 +5381,29 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $di
 
 
 /**
- * @global object
- * @global object
+ * Prints a forum discussion
+ *
  * @uses CONTEXT_MODULE
  * @uses FORUM_MODE_FLATNEWEST
  * @uses FORUM_MODE_FLATOLDEST
  * @uses FORUM_MODE_THREADED
  * @uses FORUM_MODE_NESTED
- * @param object $course
- * @param object $cm
- * @param object $forum
- * @param object $discussion
- * @param object $post
- * @param object $mode
+ * @param stdClass $course
+ * @param stdClass $cm
+ * @param stdClass $forum
+ * @param stdClass $discussion
+ * @param stdClass $post
+ * @param int $mode
  * @param mixed $canreply
- * @param bool $cancreate
+ * @param bool $canrate
  */
 function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode, $canreply=NULL, $canrate=false) {
+    global $USER, $CFG;
 
-    global $USER, $CFG, $DB, $PAGE, $OUTPUT;
     require_once($CFG->dirroot.'/rating/lib.php');
 
-    if (isloggedin()) {
-        $ownpost = ($USER->id == $post->userid);
-    } else {
-        $ownpost = false;
-    }
+    $ownpost = (isloggedin() && $USER->id == $post->userid);
+
     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
     if ($canreply === NULL) {
         $reply = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
@@ -5384,7 +5412,7 @@ function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode,
     }
 
     // $cm holds general cache for forum functions
-    $cm->cache = new stdClass();
+    $cm->cache = new stdClass;
     $cm->cache->groups      = groups_get_all_groups($course->id, 0, $cm->groupingid);
     $cm->cache->usersgroups = array();
 
@@ -5417,10 +5445,11 @@ function forum_print_discussion($course, $cm, $forum, $discussion, $post, $mode,
     }
 
     //load ratings
-    if ($forum->assessed!=RATING_AGGREGATE_NONE) {
-        $ratingoptions = new stdclass();
+    if ($forum->assessed != RATING_AGGREGATE_NONE) {
+        $ratingoptions = new stdClass;
         $ratingoptions->context = $modcontext;
         $ratingoptions->component = 'mod_forum';
+        $ratingoptions->ratingarea = 'post';
         $ratingoptions->items = $posts;
         $ratingoptions->aggregate = $forum->assessed;//the aggregation method
         $ratingoptions->scaleid = $forum->scale;
@@ -7094,10 +7123,14 @@ function forum_reset_userdata($data) {
                            WHERE f.course=? AND f.id=fd.forum AND fd.id=fp.discussion";
 
     $forumssql = $forums = $rm = null;
+
     if( $removeposts || !empty($data->reset_forum_ratings) ) {
         $forumssql      = "$allforumssql $typesql";
         $forums = $forums = $DB->get_records_sql($forumssql, $params);
-        $rm = new rating_manager();
+        $rm = new rating_manager();;
+        $ratingdeloptions = new stdClass;
+        $ratingdeloptions->component = 'mod_forum';
+        $ratingdeloptions->ratingarea = 'post';
     }
 
     if ($removeposts) {
@@ -7106,7 +7139,6 @@ function forum_reset_userdata($data) {
 
         // now get rid of all attachments
         $fs = get_file_storage();
-        $ratingdeloptions = new stdclass();
         if ($forums) {
             foreach ($forums as $forumid=>$unused) {
                 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
@@ -7154,8 +7186,6 @@ function forum_reset_userdata($data) {
 
     // remove all ratings in this course's forums
     if (!empty($data->reset_forum_ratings)) {
-        $ratingdeloptions = new stdclass();
-
         if ($forums) {
             foreach ($forums as $forumid=>$unused) {
                 if (!$cm = get_coursemodule_from_instance('forum', $forumid)) {
index e56910e..4714b34 100644 (file)
@@ -118,7 +118,8 @@ if ($course->id == SITEID) {
 }
 
 // Get the posts.
-if ($posts = forum_search_posts($searchterms, $searchcourse, $page*$perpage, $perpage, $totalcount, $extrasql)) {
+$posts = forum_search_posts($searchterms, $searchcourse, $page*$perpage, $perpage, $totalcount, $extrasql);
+if ($posts) {
 
     require_once($CFG->dirroot.'/rating/lib.php');
 
@@ -127,15 +128,14 @@ if ($posts = forum_search_posts($searchterms, $searchcourse, $page*$perpage, $pe
 
     $discussions = array();
     $forums      = array();
-    $cms         = array();
 
     //todo Rather than retrieving the ratings for each post individually it would be nice to do them in groups
     //however this requires creating arrays of posts with each array containing all of the posts from a particular forum,
     //retrieving the ratings then reassembling them all back into a single array sorted by post.modified (descending)
     $rm = new rating_manager();
-    $ratingoptions = new stdclass();
-    $ratingoptions->plugintype = 'mod';
-    $ratingoptions->pluginname = 'forum';
+    $ratingoptions = new stdClass;
+    $ratingoptions->component = 'mod_forum';
+    $ratingoptions->ratingarea = 'post';
 
     foreach ($posts as $post) {
 
@@ -149,66 +149,57 @@ if ($posts = forum_search_posts($searchterms, $searchcourse, $page*$perpage, $pe
         }
 
         if (!isset($forums[$discussion->forum])) {
-            if (! $forum = $DB->get_record('forum', array('id' => $discussion->forum))) {
-                print_error('invalidforumid', 'forum');
-            }
-            //hold onto forum cm and context for when we load ratings
-            if ($forumcm = get_coursemodule_from_instance('forum', $forum->id)) {
-                $forum->cm = $forumcm;
-                $forumcontext = get_context_instance(CONTEXT_MODULE, $forum->cm->id);
-                $forum->context = $forumcontext;
-            }
+            $forum = $DB->get_record('forum', array('id' => $discussion->forum), '*', MUST_EXIST);
+            $forum->cm = get_coursemodule_from_instance('forum', $forum->id, 0, false, MUST_EXIST);
+            $forum->context = get_context_instance(CONTEXT_MODULE, $forum->cm->id);
             $forums[$discussion->forum] = $forum;
         } else {
             $forum = $forums[$discussion->forum];
         }
 
-        //load ratings
-        if ($forum->assessed!=RATING_AGGREGATE_NONE) {
+        $forumurl = new moodle_url('/mod/forum/view.php', array('id' => $forum->cm->id));
+        $discussionurl = new moodle_url('/mod/forum/discuss.php', array('d' => $discussion->id));
+
+        // load ratings
+        if ($forum->assessed != RATING_AGGREGATE_NONE) {
             $ratingoptions->context = $forum->context;
-            $ratingoptions->component = 'mod_forum';
             $ratingoptions->items = array($post);
             $ratingoptions->aggregate = $forum->assessed;//the aggregation method
             $ratingoptions->scaleid = $forum->scale;
             $ratingoptions->userid = $user->id;
+            $ratingoptions->assesstimestart = $forum->assesstimestart;
+            $ratingoptions->assesstimefinish = $forum->assesstimefinish;
             if ($forum->type == 'single' or !$discussion->id) {
-                $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/view.php?id={$forum->cm->id}";
+                $ratingoptions->returnurl = $forumurl;
             } else {
-                $ratingoptions->returnurl = "$CFG->wwwroot/mod/forum/discuss.php?d=$discussion->id";
+                $ratingoptions->returnurl = $discussionurl;
             }
-            $ratingoptions->assesstimestart = $forum->assesstimestart;
-            $ratingoptions->assesstimefinish = $forum->assesstimefinish;
 
             $updatedpost = $rm->get_ratings($ratingoptions);
             //updating the array this way because we're iterating over a collection and updating them one by one
             $posts[$updatedpost[0]->id] = $updatedpost[0];
         }
 
-        if (!isset($cms[$forum->id])) {
-            $cm = get_coursemodule_from_instance('forum', $forum->id, 0, false, MUST_EXIST);
-            $cms[$forum->id] = $cm;
-            unset($cm); // do not use cm directly, it would break caching
+        $fullsubjects = array();
+        if ($course->id == SITEID && has_capability('moodle/site:config', $syscontext)) {
+            $postcoursename = $DB->get_field('course', 'shortname', array('id'=>$forum->course));
+            $courseurl = new moodle_url('/course/view.php', array('id' => $forum->course));
+            $fullsubjects[] = html_writer::link($courseurl, $postcoursename);
         }
-
-        $fullsubject = "<a href=\"view.php?f=$forum->id\">".format_string($forum->name,true)."</a>";
+        $fullsubjects[] = html_writer::link($forumurl, format_string($forum->name, true));
         if ($forum->type != 'single') {
-            $fullsubject .= " -> <a href=\"discuss.php?d=$discussion->id\">".format_string($discussion->name,true)."</a>";
+            $fullsubjects[] .= html_writer::link($discussionurl, format_string($discussion->name, true));
             if ($post->parent != 0) {
-                $fullsubject .= " -> <a href=\"discuss.php?d=$post->discussion&amp;parent=$post->id\">".format_string($post->subject,true)."</a>";
+                $parenturl = new moodle_url('/mod/forum/discuss.php', array('d' => $post->discussion, 'parent' => $post->id));
+                $fullsubjects[] .= html_writer::link($parenturl, format_string($post->subject, true));
             }
         }
 
-        if ($course->id == SITEID && has_capability('moodle/site:config', $syscontext)) {
-            $postcoursename = $DB->get_field('course', 'shortname', array('id'=>$forum->course));
-            $fullsubject = '<a href="'.$CFG->wwwroot.'/course/view.php?id='.$forum->course.'">'.$postcoursename.'</a> -> '. $fullsubject;
-        }
-
-        $post->subject = $fullsubject;
-
-        $fulllink = "<a href=\"discuss.php?d=$post->discussion#p$post->id\">".
-            get_string("postincontext", "forum")."</a>";
+        $post->subject = join(' -> ', $fullsubjects);
+        $discussionurl->set_anchor('p'.$post->id);
+        $fulllink = html_writer::link($discussionurl, get_string("postincontext", "forum"));
 
-        forum_print_post($post, $discussion, $forum, $cms[$forum->id], $course, false, false, false, $fulllink);
+        forum_print_post($post, $discussion, $forum, $forum->cm, $course, false, false, false, $fulllink);
         echo "<br />";
     }
 
index 52d0a5e..d831c13 100644 (file)
@@ -24,8 +24,6 @@
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$module->version  = 2010111500;
-$module->requires = 2010111002;  // Requires this Moodle version
-$module->cron     = 60;
-
-
+$module->version  = 2011052300;
+$module->requires = 2011052300;  // Requires this Moodle version
+$module->cron     = 60;
\ No newline at end of file
index 70ac8ba..d3c93cd 100644 (file)
     $completion->set_module_viewed($cm);
 
 /// Print header.
-    /// Add ajax-related libs for ratings if required  MDL-20119
-    $PAGE->requires->yui2_lib('event');
-    $PAGE->requires->yui2_lib('connection');
-    $PAGE->requires->yui2_lib('json');
 
     $PAGE->set_title(format_string($forum->name));
     $PAGE->add_body_class('forumtype-'.$forum->type);
index e0186e0..3fa69cc 100644 (file)
@@ -61,7 +61,7 @@ class backup_glossary_activity_structure_step extends backup_activity_structure_
         $ratings = new backup_nested_element('ratings');
 
         $rating = new backup_nested_element('rating', array('id'), array(
-            'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
+            'component', 'ratingarea', 'scaleid', 'value', 'userid', 'timecreated', 'timemodified'));
 
         $categories = new backup_nested_element('categories');
 
@@ -101,8 +101,10 @@ class backup_glossary_activity_structure_step extends backup_activity_structure_
             $alias->set_source_table('glossary_alias', array('entryid' => backup::VAR_PARENTID));
             $alias->set_source_alias('alias', 'alias_text');
 
-            $rating->set_source_table('rating', array('contextid' => backup::VAR_CONTEXTID,
-                                      'itemid'    => backup::VAR_PARENTID));
+            $rating->set_source_table('rating', array('contextid'  => backup::VAR_CONTEXTID,
+                                                      'itemid'     => backup::VAR_PARENTID,
+                                                      'component'  => 'mod_glossary',
+                                                      'ratingarea' => 'entry'));
             $rating->set_source_alias('rating', 'value');
 
             $categoryentry->set_source_table('glossary_entries_categories', array('categoryid' => backup::VAR_PARENTID));
index 74e9f8c..719cfa0 100644 (file)
@@ -117,6 +117,15 @@ class restore_glossary_activity_structure_step extends restore_activity_structur
         $data->timecreated = $this->apply_date_offset($data->timecreated);
         $data->timemodified = $this->apply_date_offset($data->timemodified);
 
+        // Make sure that we have both component and ratingarea set. These were added in 2.1.
+        // Prior to that all ratings were for entries so we know what to set them too.
+        if (empty($data->component)) {
+            $data->component = 'mod_glossary';
+        }
+        if (empty($data->ratingarea)) {
+            $data->ratingarea = 'entry';
+        }
+
         $newitemid = $DB->insert_record('rating', $data);
     }
 
index 722bdf1..b3677de 100644 (file)
@@ -326,6 +326,29 @@ function xmldb_glossary_upgrade($oldversion) {
         upgrade_mod_savepoint(true, 2010111501, 'glossary');
     }
 
+    if ($oldversion < 2011052300) {
+        // rating.component and rating.ratingarea have now been added as mandatory fields.
+        // Presently you can only rate data entries so component = 'mod_glossary' and ratingarea = 'entry'
+        // for all ratings with a glossary context.
+        // We want to update all ratings that belong to a glossary context and don't already have a
+        // component set.
+        // This could take a while reset upgrade timeout to 5 min
+        upgrade_set_timeout(60 * 20);
+        $sql = "UPDATE {rating}
+                SET component = 'mod_glossary', ratingarea = 'entry'
+                WHERE contextid IN (
+                    SELECT ctx.id
+                      FROM {context} ctx
+                      JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                      JOIN {modules} m ON m.id = cm.module
+                     WHERE ctx.contextlevel = 70 AND
+                           m.name = 'glossary'
+                ) AND component = 'unknown'";
+        $DB->execute($sql);
+
+        upgrade_mod_savepoint(true, 2011052300, 'glossary');
+    }
+
     return true;
 }
 
index fe1b709..e02f291 100644 (file)
@@ -104,8 +104,10 @@ if ($confirm and confirm_sesskey()) { // the operation was confirmed.
 
         //delete glossary entry ratings
         require_once($CFG->dirroot.'/rating/lib.php');
-        $delopt = new stdclass();
+        $delopt = new stdClass;
         $delopt->contextid = $context->id;
+        $delopt->component = 'mod_glossary';
+        $delopt->ratingarea = 'entry';
         $delopt->itemid = $entry->id;
         $rm = new rating_manager();
         $rm->delete_ratings($delopt);
index ec5a023..e1cb20e 100644 (file)
@@ -23,7 +23,6 @@
  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-require_once($CFG->dirroot . '/rating/lib.php');
 require_once($CFG->libdir . '/completionlib.php');
 
 define("GLOSSARY_SHOW_ALL_CATEGORIES", 0);
@@ -243,7 +242,16 @@ function glossary_user_outline($course, $user, $mod, $glossary) {
     } else if ($grade) {
         $result = new stdClass();
         $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
-        $result->time = $grade->dategraded;
+
+        //datesubmitted == time created. dategraded == time modified or time overridden
+        //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+        //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+        if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+            $result->time = $grade->dategraded;
+        } else {
+            $result->time = $grade->datesubmitted;
+        }
+
         return $result;
     }
     return NULL;
@@ -436,13 +444,14 @@ function glossary_get_user_grades($glossary, $userid=0) {
     global $CFG;
 
     require_once($CFG->dirroot.'/rating/lib.php');
-    $rm = new rating_manager();
 
-    $ratingoptions = new stdclass();
+    $ratingoptions = new stdClass;
 
     //need these to work backwards to get a context id. Is there a better way to get contextid from a module instance?
     $ratingoptions->modulename = 'glossary';
     $ratingoptions->moduleid   = $glossary->id;
+    $ratingoptions->component  = 'mod_glossary';
+    $ratingoptions->ratingarea = 'entry';
 
     $ratingoptions->userid = $userid;
     $ratingoptions->aggregationmethod = $glossary->assessed;
@@ -450,31 +459,40 @@ function glossary_get_user_grades($glossary, $userid=0) {
     $ratingoptions->itemtable = 'glossary_entries';
     $ratingoptions->itemtableusercolumn = 'userid';
 
+    $rm = new rating_manager();
     return $rm->get_user_grades($ratingoptions);
 }
 
 /**
  * Return rating related permissions
- * @param string $options the context id
+ *
+ * @param int $contextid the context id
+ * @param string $component The component we want to get permissions for
+ * @param string $ratingarea The ratingarea that we want to get permissions for
  * @return array an associative array of the user's rating permissions
  */
-function glossary_rating_permissions($options) {
-    $contextid = $options;
-    $context = get_context_instance_by_id($contextid);
-
-    if (!$context) {
-        print_error('invalidcontext');
+function glossary_rating_permissions($contextid, $component, $ratingarea) {
+    if ($component != 'mod_glossary' || $ratingarea != 'entry') {
+        // We don't know about this component/ratingarea so just return null to get the
+        // default restrictive permissions.
         return null;
-    } else {
-        return array('view'=>has_capability('mod/glossary:viewrating',$context), 'viewany'=>has_capability('mod/glossary:viewanyrating',$context), 'viewall'=>has_capability('mod/glossary:viewallratings',$context), 'rate'=>has_capability('mod/glossary:rate',$context));
     }
+    $context = get_context_instance_by_id($contextid);
+    return array(
+        'view'    => has_capability('mod/glossary:viewrating', $context),
+        'viewany' => has_capability('mod/glossary:viewanyrating', $context),
+        'viewall' => has_capability('mod/glossary:viewallratings', $context),
+        'rate'    => has_capability('mod/glossary:rate', $context)
+    );
 }
 
 /**
  * Validates a submitted rating
  * @param array $params submitted data
  *            context => object the context in which the rated items exists [required]
- *            itemid => int the ID of the object being rated
+ *            component => The component for this module - should always be mod_forum [required]
+ *            ratingarea => object the context in which the rated items exists [required]
+ *            itemid => int the ID of the object being rated [required]
  *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
  *            rating => int the submitted rating
  *            rateduserid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
@@ -484,28 +502,59 @@ function glossary_rating_permissions($options) {
 function glossary_rating_validate($params) {
     global $DB, $USER;
 
-    if (!array_key_exists('itemid', $params) || !array_key_exists('context', $params) || !array_key_exists('rateduserid', $params)) {
-        throw new rating_exception('missingparameter');
+    // Check the component is mod_forum
+    if ($params['component'] != 'mod_glossary') {
+        throw new rating_exception('invalidcomponent');
+    }
+
+    // Check the ratingarea is post (the only rating area in forum)
+    if ($params['ratingarea'] != 'entry') {
+        throw new rating_exception('invalidratingarea');
+    }
+
+    // Check the rateduserid is not the current user .. you can't rate your own posts
+    if ($params['rateduserid'] == $USER->id) {
+        throw new rating_exception('nopermissiontorate');
     }
 
-    $glossarysql = "SELECT g.id as gid, e.userid as userid, e.approved, e.timecreated, g.assesstimestart, g.assesstimefinish
+    $glossarysql = "SELECT g.id as glossaryid, g.scale, g.course, e.userid as userid, e.approved, e.timecreated, g.assesstimestart, g.assesstimefinish
                       FROM {glossary_entries} e
                       JOIN {glossary} g ON e.glossaryid = g.id
                      WHERE e.id = :itemid";
-    $glossaryparams = array('itemid'=>$params['itemid']);
-    if (!$info = $DB->get_record_sql($glossarysql, $glossaryparams)) {
+    $glossaryparams = array('itemid' => $params['itemid']);
+    $info = $DB->get_record_sql($glossarysql, $glossaryparams);
+    if (!$info) {
         //item doesn't exist
         throw new rating_exception('invaliditemid');
     }
 
-    if ($info->userid == $USER->id) {
-        //user is attempting to rate their own glossary entry
-        throw new rating_exception('nopermissiontorate');
+    if ($info->scale != $params['scaleid']) {
+        //the scale being submitted doesnt match the one in the database
+        throw new rating_exception('invalidscaleid');
+    }
+
+    //check that the submitted rating is valid for the scale
+
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
+        throw new rating_exception('invalidnum');
     }
 
-    if ($params['rateduserid'] != $info->userid) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    // upper limit
+    if ($info->scale < 0) {
+        //its a custom scale
+        $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
+        if ($scalerecord) {
+            $scalearray = explode(',', $scalerecord->scale);
+            if ($params['rating'] > count($scalearray)) {
+                throw new rating_exception('invalidnum');
+            }
+        } else {
+            throw new rating_exception('invalidscaleid');
+        }
+    } else if ($params['rating'] > $info->scale) {
+        //if its numeric and submitted rating is above maximum
+        throw new rating_exception('invalidnum');
     }
 
     if (!$info->approved) {
@@ -520,16 +569,11 @@ function glossary_rating_validate($params) {
         }
     }
 
-    $glossaryid = $info->gid;
-
-    $cm = get_coursemodule_from_instance('glossary', $glossaryid);
-    if (empty($cm)) {
-        throw new rating_exception('unknowncontext');
-    }
-    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
+    $cm = get_coursemodule_from_instance('glossary', $info->glossaryid, $info->course, false, MUST_EXIST);
+    $context = get_context_instance(CONTEXT_MODULE, $cm->id, MUST_EXIST);
 
-    //if the supplied context doesnt match the item's context
-    if (empty($context) || $context->id != $params['context']->id) {
+    // if the supplied context doesnt match the item's context
+    if ($context->id != $params['context']->id) {
         throw new rating_exception('invalidcontext');
     }
 
@@ -647,7 +691,8 @@ function glossary_grade_item_delete($glossary) {
  * Returns the users with data in one glossary
  * (users with records in glossary_entries, students)
  *
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $glossaryid
  * @return array
  */
@@ -2154,31 +2199,49 @@ function glossary_full_tag($tag,$level=0,$endline=true,$content) {
 /**
  * How many unrated entries are in the given glossary for a given user?
  *
- * @global object
+ * @global moodle_database $DB
  * @param int $glossaryid
  * @param int $userid
  * @return int
  */
 function glossary_count_unrated_entries($glossaryid, $userid) {
     global $DB;
-    if ($entries = $DB->get_record_sql("SELECT count('x') as num
-                                          FROM {glossary_entries}
-                                         WHERE glossaryid = ? AND userid <> ?", array($glossaryid, $userid))) {
-
-        if (!$cm = get_coursemodule_from_instance('glossary', $glossaryid)) {
-            return 0;
-        }
-        $context = get_context_instance(CONTEXT_MODULE, $cm->id);
-
-        if ($rated = $DB->get_record_sql("SELECT count(*) as num
-                                            FROM {glossary_entries} e, {ratings} r
-                                           WHERE e.glossaryid = :glossaryid AND e.id = r.itemid
-                                                 AND r.userid = :userid and r.contextid = :contextid",
-                array('glossaryid'=>$glossaryid, 'userid'=>$userid, 'contextid'=>$context->id))) {
 
-            $difference = $entries->num - $rated->num;
-            if ($difference > 0) {
-                return $difference;
+    $sql = "SELECT COUNT('x') as num
+              FROM {glossary_entries}
+             WHERE glossaryid = :glossaryid AND
+                   userid <> :userid";
+    $params = array('glossaryid' => $glossaryid, 'userid' => $userid);
+    $entries = $DB->count_records_sql($sql, $params);
+
+    if ($entries) {
+        // We need to get the contextid for the glossaryid we have been given.
+        $sql = "SELECT ctx.id
+                  FROM {context} ctx
+                  JOIN {course_modules} cm ON cm.id = ctx.instanceid
+                  JOIN {modules} m ON m.id = cm.module
+                  JOIN {glossary} g ON g.id = cm.instance
+                 WHERE ctx.contextlevel = :contextlevel AND
+                       m.name = 'glossary' AND
+                       g.id = :glossaryid";
+        $contextid = $DB->get_field_sql($sql, array('glossaryid' => $glossaryid, 'contextlevel' => CONTEXT_MODULE));
+
+        // Now we need to count the ratings that this user has made
+        $sql = "SELECT COUNT('x') AS num
+                  FROM {glossary_entries} e
+                  JOIN {ratings} r ON r.itemid = e.id
+                 WHERE e.glossaryid = :glossaryid AND
+                       r.userid = :userid AND
+                       r.component = 'mod_glossary' AND
+                       r.ratingarea = 'entry' AND
+                       r.contextid = :contextid";
+        $params = array('glossaryid' => $glossaryid, 'userid' => $userid, 'contextid' => $context->id);
+        $rated = $DB->count_records_sql($sql, $params);
+        if ($rated) {
+            // The number or enties minus the number or rated entries equals the number of unrated
+            // entries
+            if ($entries->num > $rated->num) {
+                return $entries->num - $rated->num;
             } else {
                 return 0;    // Just in case there was a counting error
             }
@@ -2430,7 +2493,9 @@ function glossary_reset_userdata($data) {
     $fs = get_file_storage();
 
     $rm = new rating_manager();
-    $ratingdeloptions = new stdclass();
+    $ratingdeloptions = new stdClass;
+    $ratingdeloptions->component = 'mod_glossary';
+    $ratingdeloptions->ratingarea = 'entry';
 
     // delete entries if requested
     if (!empty($data->reset_glossary_all)
index 5f7050f..6210db6 100644 (file)
@@ -5,8 +5,8 @@
 ///  This fragment is called by moodle_needs_upgrading() and /admin/index.php
 /////////////////////////////////////////////////////////////////////////////////
 
-$module->version  = 2010111501;
-$module->requires = 2010080300;  // Requires this Moodle version
+$module->version  = 2011052300;
+$module->requires = 2011052300;  // Requires this Moodle version
 $module->cron     = 0;           // Period for cron to check this module (secs)
 
 
index c77941b..f5d8687 100644 (file)
@@ -394,13 +394,13 @@ if ($allentries) {
     echo $paging;
     echo '</div>';
 
-
     //load ratings
     require_once($CFG->dirroot.'/rating/lib.php');
-    if ($glossary->assessed!=RATING_AGGREGATE_NONE) {
-        $ratingoptions = new stdclass();
+    if ($glossary->assessed != RATING_AGGREGATE_NONE) {
+        $ratingoptions = new stdClass;
         $ratingoptions->context = $context;
         $ratingoptions->component = 'mod_glossary';
+        $ratingoptions->ratingarea = 'entry';
         $ratingoptions->items = $allentries;
         $ratingoptions->aggregate = $glossary->assessed;//the aggregation method
         $ratingoptions->scaleid = $glossary->scale;
index 94dc67a..5662730 100644 (file)
@@ -249,6 +249,8 @@ function imscp_user_complete($course, $user, $mod, $imscp) {
 /**
  * Returns the users with data in one imscp
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $imscpid
  * @return bool false
  */
index 2e21e13..d11ba19 100644 (file)
@@ -116,6 +116,8 @@ function label_delete_instance($id) {
  * Returns the users with data in one resource
  * (NONE, but must exist on EVERY mod !!)
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $labelid
  */
 function label_get_participants($labelid) {
index 981a833..a0a611c 100644 (file)
@@ -46,41 +46,59 @@ if ($mode !== 'display') {
 }
 $PAGE->set_url($url);
 
+$attempt = new stdClass();
+$user = new stdClass();
+$attemptid = optional_param('attemptid', 0, PARAM_INT);
+
+if ($attemptid > 0) {
+    $attempt = $DB->get_record('lesson_attempts', array('id' => $attemptid));
+    $answer = $DB->get_record('lesson_answers', array('lessonid' => $lesson->id, 'pageid' => $attempt->pageid));
+    $user = $DB->get_record('user', array('id' => $attempt->userid));
+    $scoreoptions = array();
+    if ($lesson->custom) {
+        $i = $answer->score;
+        while ($i >= 0) {
+            $scoreoptions[$i] = (string)$i;
+            $i--;
+        }
+    } else {
+        $scoreoptions[0] = get_string('nocredit', 'lesson');
+        $scoreoptions[1] = get_string('credit', 'lesson');
+    }
+}
+
 /// Handle any preprocessing before header is printed - based on $mode
 switch ($mode) {
     case 'grade':
         // Grading form - get the necessary data
         require_sesskey();
 
-        $attemptid = required_param('attemptid', PARAM_INT);
-
-        if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $attemptid))) {
+        if (empty($attempt)) {
             print_error('cannotfindattempt', 'lesson');
         }
-        $page = $lesson->load_page($attempt->pageid);
-        if (!$user = $DB->get_record('user', array('id' => $attempt->userid))) {
+        if (empty($user)) {
             print_error('cannotfinduser', 'lesson');
         }
-        if (!$answer = $DB->get_record('lesson_answers', array('lessonid' => $lesson->id, 'pageid' => $page->id))) {
+        if (empty($answer)) {
             print_error('cannotfindanswer', 'lesson');
         }
         break;
 
     case 'update':
         require_sesskey();
-        $mform = new essay_grading_form();
-        if ($form = $mform->get_data()) {
 
-            if (optional_param('cancel', false, PARAM_RAW)) {
-                redirect("$CFG->wwwroot/mod/lesson/essay.php?id=$cm->id");
-            }
-
-            $attemptid = required_param('attemptid', PARAM_INT);
-            $score = optional_param('score', 0, PARAM_INT);
+        if (empty($attempt)) {
+            print_error('cannotfindattempt', 'lesson');
+        }
+        if (empty($user)) {
+            print_error('cannotfinduser', 'lesson');
+        }
 
-            if (!$attempt = $DB->get_record('lesson_attempts', array('id' => $attemptid))) {
-                print_error('cannotfindattempt', 'lesson');
-            }
+        $mform = new essay_grading_form(null, array('scoreoptions'=>$scoreoptions, 'user'=>$user));
+        if ($mform->is_cancelled()) {
+            redirect("$CFG->wwwroot/mod/lesson/essay.php?id=$cm->id");
+        }
+        if ($form = $mform->get_data()) {
             if (!$grades = $DB->get_records('lesson_grades', array("lessonid"=>$lesson->id, "userid"=>$attempt->userid), 'completed', '*', $attempt->retry, 1)) {
                 print_error('cannotfindgrade', 'lesson');
             }
@@ -89,7 +107,7 @@ switch ($mode) {
             $essayinfo = unserialize($attempt->useranswer);
 
             $essayinfo->graded = 1;
-            $essayinfo->score = $score;
+            $essayinfo->score = $form->score;
             $essayinfo->response = clean_param($form->response, PARAM_RAW);
             $essayinfo->sent = 0;
             if (!$lesson->custom && $essayinfo->score == 1) {
@@ -368,22 +386,9 @@ switch ($mode) {
     case 'grade':
         // Grading form
         // Expects the following to be set: $attemptid, $answer, $user, $page, $attempt
-
-
         $essayinfo = unserialize($attempt->useranswer);
-        $options = array();
-        if ($lesson->custom) {
-            $i = $answer->score;
-            while ($i >= 0) {
-                $options[$i] = (string)$i;
-                $i--;
-            }
-        } else {
-            $options[0] = get_string('nocredit', 'lesson');
-            $options[1] = get_string('credit', 'lesson');
-        }
-        $mform = new essay_grading_form(null, array('scoreoptions'=>$options, 'user'=>$user));
 
+        $mform = new essay_grading_form(null, array('scoreoptions'=>$scoreoptions, 'user'=>$user));
         $data = new stdClass;
         $data->id = $cm->id;
         $data->attemptid = $attemptid;
index 813ff4c..efaec0f 100644 (file)
@@ -166,8 +166,15 @@ function lesson_user_outline($course, $user, $mod, $lesson) {
     } else {
         $grade = reset($grades->items[0]->grades);
         $return->info = get_string("grade") . ': ' . $grade->str_long_grade;
-        $return->time = $grade->dategraded;
-        $return->info = get_string("no")." ".get_string("attempts", "lesson");
+
+        //datesubmitted == time created. dategraded == time modified or time overridden
+        //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+        //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+        if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+            $result->time = $grade->dategraded;
+        } else {
+            $result->time = $grade->datesubmitted;
+        }
     }
     return $return;
 }
@@ -521,8 +528,8 @@ function lesson_grade_item_delete($lesson) {
  * for a given instance of lesson. Must include every user involved
  * in the instance, independent of his role (student, teacher, admin...)
  *
- * @global stdClass
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $lessonid
  * @return array
  */
index ce8aef1..b94116e 100644 (file)
@@ -231,6 +231,8 @@ function page_user_complete($course, $user, $mod, $page) {
 /**
  * Returns the users with data in one page
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $pageid
  * @return bool false
  */
index f919522..ccac9d5 100644 (file)
@@ -366,7 +366,16 @@ function quiz_user_outline($course, $user, $mod, $quiz) {
 
     $result = new stdClass();
     $result->info = get_string('grade') . ': ' . $grade->str_long_grade;
-    $result->time = $grade->dategraded;
+
+    //datesubmitted == time created. dategraded == time modified or time overridden
+    //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+    //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+    if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+        $result->time = $grade->dategraded;
+    } else {
+        $result->time = $grade->datesubmitted;
+    }
+
     return $result;
 }
 
index cc4e315..79091f5 100644 (file)
@@ -210,6 +210,8 @@ function resource_user_complete($course, $user, $mod, $resource) {
 /**
  * Returns the users with data in one resource
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $resourceid
  * @return bool false
  */
index 88ecd28..80b8c7d 100644 (file)
@@ -294,7 +294,16 @@ function scorm_user_outline($course, $user, $mod, $scorm) {
         $grade = reset($grades->items[0]->grades);
         $result = new stdClass();
         $result->info = get_string('grade') . ': '. $grade->str_long_grade;
-        $result->time = $grade->dategraded;
+
+        //datesubmitted == time created. dategraded == time modified or time overridden
+        //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
+        //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
+        if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
+            $result->time = $grade->dategraded;
+        } else {
+            $result->time = $grade->datesubmitted;
+        }
+
         return $result;
     }
     return null;
index fa51e16..2537066 100644 (file)
@@ -271,7 +271,8 @@ function survey_print_recent_activity($course, $viewfullnames, $timestart) {
  * Returns the users with data in one survey
  * (users with records in survey_analysis and survey_answers, students)
  *
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $surveyid
  * @return array
  */
index f79913b..39b10d4 100644 (file)
@@ -237,6 +237,8 @@ function url_user_complete($course, $user, $mod, $url) {
 /**
  * Returns the users with data in one url
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $urlid
  * @return bool false
  */
index 024f2be..230e7bd 100644 (file)
@@ -382,6 +382,8 @@ function wiki_grades($wikiid) {
  * in the instance, independient of his role (student, teacher, admin...)
  * See other modules as example.
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $wikiid ID of an instance of this module
  * @return mixed boolean/array of students
  **/
index 83aded8..f586dec 100644 (file)
@@ -898,6 +898,8 @@ function workshop_cron () {
  * are not returned as the example submission is considered non-user
  * data for the purpose of workshop backup.
  *
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $workshopid ID of an instance of this module
  * @return array of user ids, empty if there are no participants
  */
index ff9b121..38c2f5c 100644 (file)
 require_once("../config.php");
 require_once("lib.php");
 
-$contextid   = required_param('contextid', PARAM_INT);
-$itemid   = required_param('itemid', PARAM_INT);
-$scaleid   = required_param('scaleid', PARAM_INT);
-$sort = optional_param('sort', '', PARAM_ALPHA);
-$popup = optional_param('popup', 0, PARAM_INT);//==1 if in a popup window?
+$contextid  = required_param('contextid', PARAM_INT);
+$component  = required_param('component', PARAM_ALPHAEXT);
+$ratingarea = optional_param('ratingarea', null, PARAM_ALPHANUMEXT);
+$itemid     = required_param('itemid', PARAM_INT);
+$scaleid    = required_param('scaleid', PARAM_INT);
+$sort       = optional_param('sort', '', PARAM_ALPHA);
+$popup      = optional_param('popup', 0, PARAM_INT); //==1 if in a popup window?
 
 list($context, $course, $cm) = get_context_info_array($contextid);
 require_login($course, false, $cm);
@@ -69,8 +71,10 @@ $strtime    = get_string('time');
 $PAGE->set_title(get_string('allratingsforitem','rating'));
 echo $OUTPUT->header();
 
-$ratingoptions = new stdclass();
+$ratingoptions = new stdClass;
 $ratingoptions->context = $context;
+$ratingoptions->component = $component;
+$ratingoptions->ratingarea = $ratingarea;
 $ratingoptions->itemid = $itemid;
 $ratingoptions->sort = $sqlsort;
 
@@ -80,17 +84,23 @@ if (!$ratings) {
     $msg = get_string('noratings','rating');
     echo html_writer::tag('div', $msg, array('class'=>'mdl-align'));
 } else {
-    $sortargs = "contextid=$contextid&amp;itemid=$itemid&amp;scaleid=$scaleid";
-    if($popup) {
-        $sortargs.="&amp;popup=$popup";
+    $sorturl  = new moodle_url('/index.php', array('contextid' => $contextid, 'itemid' => $itemid, 'scaleid' => $scaleid));
+    if ($popup) {
+        $sorturl->param('popup', $popup);
     }
-    echo "<table border=\"0\" cellpadding=\"3\" cellspacing=\"3\" class=\"generalbox\" style=\"width:100%\">";
-    echo "<tr>";
-    echo "<th class=\"header\" scope=\"col\">&nbsp;</th>";
-    echo "<th class=\"header\" scope=\"col\"><a href=\"index.php?$sortargs&amp;sort=firstname\">$strname</a></th>";
-    echo "<th class=\"header\" scope=\"col\" style=\"width:100%\"><a href=\"index.php?$sortargs&amp;sort=rating\">$strrating</a></th>";
-    echo "<th class=\"header\" scope=\"col\"><a href=\"index.php?$sortargs&amp;sort=time\">$strtime</a></th>";
-    echo "</tr>";
+
+    $table = new html_table;
+    $table->cellpadding = 3;
+    $table->cellspacing = 3;
+    $table->attributes['class'] = 'generalbox ratingtable';
+    $table->head = array(
+        '',
+        html_writer::link(new moodle_url($sorturl, array('sort' => 'firstname')), $strname),
+        html_writer::link(new moodle_url($sorturl, array('sort' => 'rating')), $strrating),
+        html_writer::link(new moodle_url($sorturl, array('sort' => 'time')), $strtime)
+    );
+    $table->colclasses = array('', 'firstname', 'rating', 'time');
+    $table->data = array();
 
     //if the scale was changed after ratings were submitted some ratings may have a value above the current maximum
     $maxrating = count($scalemenu) - 1;
@@ -100,28 +110,23 @@ if (!$ratings) {
         //but we don't
         $rating->id = $rating->userid;
 
-        echo '<tr class="ratingitemheader">';
-        echo "<td>";
-        if($course && $course->id) {
-            echo $OUTPUT->user_picture($rating, array('courseid'=>$course->id));
+        $row = new html_table_row();
+        $row->attributes['class'] = 'ratingitemheader';
+        if ($course && $course->id) {
+            $row->cells[] = $OUTPUT->user_picture($rating, array('courseid' => $course->id));
         } else {
-            echo $OUTPUT->user_picture($rating);
+            $row->cells[] = $OUTPUT->user_picture($rating);
         }
-        echo '</td><td>'.fullname($rating).'</td>';
-        
-        //if they've switched to rating out of 5 but there were ratings submitted out of 10 for example
-        //Not doing this within $rm->get_all_ratings_for_item to allow access to the raw data
+        $row->cells[] = fullname($rating);
         if ($rating->rating > $maxrating) {
             $rating->rating = $maxrating;
         }
-        echo '<td style="white-space:nowrap" align="center" class="rating">'.$scalemenu[$rating->rating]."</td>";
-        echo '<td style="white-space:nowrap" align="center" class="time">'.userdate($rating->timemodified)."</td>";
-        echo "</tr>\n";
+        $row->cells[] = $scalemenu[$rating->rating];
+        $row->cells[] = userdate($rating->timemodified);
+        $table->data[] = $row;
     }
-    echo "</table>";
-    echo "<br />";
+    echo html_writer::table($table);
 }
-
 if ($popup) {
     echo $OUTPUT->close_window_button();
 }
index 881c10d..f1a511e 100644 (file)
@@ -46,16 +46,23 @@ class rating implements renderable {
 
     /**
      * The context in which this rating exists
-     * @var context
+     * @var stdClass
      */
     public $context;
 
     /**
      * The component using ratings. For example "mod_forum"
-     * @var component
+     * @var string
      */
     public $component;
 
+    /**
+     * The rating area to associate this rating with.
+     * This allows a plugin to rate more than one thing by specifying different rating areas.
+     * @var string
+     */
+    public $ratingarea = null;
+
     /**
      * The id of the item (forum post, glossary item etc) being rated
      * @var int
@@ -81,78 +88,288 @@ class rating implements renderable {
     public $settings;
 
     /**
-    * Constructor.
-    * @param object $options {
-    *            context => context context to use for the rating [required]
-    *            component => component using ratings ie mod_forum [required]
-    *            itemid  => int the id of the associated item (forum post, glossary item etc) [required]
-    *            scaleid => int The scale in use when the rating was submitted [required]
-    *            userid  => int The id of the user who submitted the rating [required]
-    * }
-    */
+     * The Id of this rating within the rating table.
+     * This is only set if the rating already exists
+     * @var int
+     */
+    public $id = null;
+
+    /**
+     * The aggregate of the combined ratings for the associated item.
+     * This is only set if the rating already exists
+     *
+     * @var int
+     */
+    public $aggregate = null;
+
+    /**
+     * The total number of ratings for the associated item.
+     * This is only set if the rating already exists
+     *
+     * @var int
+     */
+    public $count = 0;
+
+    /**
+     * The rating the associated user gave the associated item
+     * This is only set if the rating already exists
+     *
+     * @var int
+     */
+    public $rating = null;
+
+    /**
+     * The time the associated item was created
+     *
+     * @var int
+     */
+    public $itemtimecreated = null;
+
+    /**
+     * The id of the user who submitted the rating
+     *
+     * @var int
+     */
+    public $itemuserid = null;
+
+    /**
+     * Constructor.
+     * @param object $options {
+     *            context => context context to use for the rating [required]
+     *            component => component using ratings ie mod_forum [required]
+     *            ratingarea => ratingarea to associate this rating with [required]
+     *            itemid  => int the id of the associated item (forum post, glossary item etc) [required]
+     *            scaleid => int The scale in use when the rating was submitted [required]
+     *            userid  => int The id of the user who submitted the rating [required]
+     *            settings => Settings for the rating object [optional]
+     *            id => The id of this rating (if the rating is from the db) [optional]
+     *            aggregate => The aggregate for the rating [optional]
+     *            count => The number of ratings [optional]
+     *            rating => The rating given by the user [optional]
+     * }
+     */
     public function __construct($options) {
-        $this->context = $options->context;
-        $this->component = $options->component;
-        $this->itemid = $options->itemid;
-        $this->scaleid = $options->scaleid;
-        $this->userid = $options->userid;
+        $this->context =    $options->context;
+        $this->component =  $options->component;
+        $this->ratingarea = $options->ratingarea;
+        $this->itemid =     $options->itemid;
+        $this->scaleid =    $options->scaleid;
+        $this->userid =     $options->userid;
+
+        if (isset($options->settings)) {
+            $this->settings = $options->settings;
+        }
+        if (isset($options->id)) {
+            $this->id = $options->id;
+        }
+        if (isset($options->aggregate)) {
+            $this->aggregate = $options->aggregate;
+        }
+        if (isset($options->count)) {
+            $this->count = $options->count;
+        }
+        if (isset($options->rating)) {
+            $this->rating = $options->rating;
+        }
     }
 
     /**
-    * Update this rating in the database
-    * @param int $rating the integer value of this rating
-    * @return void
-    */
+     * Update this rating in the database
+     * @param int $rating the integer value of this rating
+     * @return void
+     */
     public function update_rating($rating) {
         global $DB;
 
-        $data = new stdclass();
-        $table = 'rating';
+        $time = time();
+
+        $data = new stdClass;
+        $data->rating       = $rating;
+        $data->timemodified = $time;
 
         $item = new stdclass();
         $item->id = $this->itemid;
         $items = array($item);
 
-        $ratingoptions = new stdclass();
+        $ratingoptions = new stdClass;
         $ratingoptions->context = $this->context;
         $ratingoptions->component = $this->component;
+        $ratingoptions->ratingarea = $this->ratingarea;
         $ratingoptions->items = $items;
         $ratingoptions->aggregate = RATING_AGGREGATE_AVERAGE;//we dont actually care what aggregation method is applied
         $ratingoptions->scaleid = $this->scaleid;
         $ratingoptions->userid = $this->userid;
 
-        $rm = new rating_manager();
+        $rm = new rating_manager();;
         $items = $rm->get_ratings($ratingoptions);
-        if( empty($items) || empty($items[0]->rating) || empty($items[0]->rating->id) ) {
+        $firstitem = $items[0]->rating;
+
+        if (empty($firstitem->id)) {
+            // Insert a new rating
             $data->contextid    = $this->context->id;
+            $data->component    = $this->component;
+            $data->ratingarea   = $this->ratingarea;
             $data->rating       = $rating;
             $data->scaleid      = $this->scaleid;
             $data->userid       = $this->userid;
             $data->itemid       = $this->itemid;
-
-            $time = time();
-            $data->timecreated = $time;
+            $data->timecreated  = $time;
             $data->timemodified = $time;
+            $DB->insert_record('rating', $data);
+        } else {
+            // Update the rating
+            $data->id           = $firstitem->id;
+            $DB->update_record('rating', $data);
+        }
+    }
 
-            $DB->insert_record($table, $data);
+    /**
+     * Retreive the integer value of this rating
+     * @return int the integer value of this rating object
+     */
+    public function get_rating() {
+        return $this->rating;
+    }
+
+    /**
+     * Returns this ratings aggregate value as a string.
+     *
+     * @return string
+     */
+    public function get_aggregate_string() {
+
+        $aggregate = $this->aggregate;
+        $method = $this->settings->aggregationmethod;
+
+        // only display aggregate if aggregation method isn't COUNT
+        $aggregatestr = '';
+        if ($aggregate && $method != RATING_AGGREGATE_COUNT) {
+            if ($method != RATING_AGGREGATE_SUM && !$this->settings->scale->isnumeric) {
+                $aggregatestr .= $this->settings->scale->scaleitems[round($aggregate)]; //round aggregate as we're using it as an index
+            } else { // aggregation is SUM or the scale is numeric
+                $aggregatestr .= round($aggregate, 1);
+            }
         }
-        else {
-            $data->id       = $items[0]->rating->id;
-            $data->rating       = $rating;
 
-            $time = time();
-            $data->timemodified = $time;
+        return $aggregatestr;
+    }
 
-            $DB->update_record($table, $data);
+    /**
+     * Returns true if the user is able to rate this rating object
+     *
+     * @param int $userid Current user assumed if left empty
+     * @return bool
+     */
+    public function user_can_rate($userid = null) {
+        if (empty($userid)) {
+            global $USER;
+            $userid = $USER->id;
         }
+        // You can't rate your item
+        if ($this->itemuserid == $userid) {
+            return false;
+        }
+        // You can't rate if you don't have the system cap
+        if (!$this->settings->permissions->rate) {
+            return false;
+        }
+        // You can't rate if you don't have the plugin cap
+        if (!$this->settings->pluginpermissions->rate) {
+            return false;
+        }
+
+        // You can't rate if the item was outside of the assessment times
+        $timestart = $this->settings->assesstimestart;
+        $timefinish = $this->settings->assesstimefinish;
+        $timecreated = $this->itemtimecreated;
+        if (!empty($timestart) && !empty($timefinish) && ($timecreated < $timestart || $timecreated > $timefinish)) {
+            return false;
+        }
+        return true;
     }
 
     /**
-    * Retreive the integer value of this rating
-    * @return int the integer value of this rating object
-    */
-    public function get_rating() {
-        return $this->rating;
+     * Returns true if the user is able to view the aggregate for this rating object.
+     *
+     * @param int|null $userid If left empty the current user is assumed.
+     * @return bool
+     */
+    public function user_can_view_aggregate($userid = null) {
+        if (empty($userid)) {
+            global $USER;
+            $userid = $USER->id;
+        }
+
+        // if the item doesnt belong to anyone or its another user's items and they can see the aggregate on items they don't own
+        // Note that viewany doesnt mean you can see the aggregate or ratings of your own items
+        if ((empty($this->itemuserid) or $this->itemuserid != $userid) && $this->settings->permissions->viewany && $this->settings->pluginpermissions->viewany ) {
+            return true;
+        }
+
+        // if its the current user's item and they have permission to view the aggregate on their own items
+        if ($this->itemuserid == $userid && $this->settings->permissions->view && $this->settings->pluginpermissions->view) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns a URL to view all of the ratings for the item this rating is for.
+     *
+     * If this is a rating of a post then this URL will take the user to a page that shows all
+     * of the ratings for the post (this one included).
+     *
+     * @param bool $popup
+     * @return moodle_url
+     */
+    public function get_view_ratings_url($popup = false) {
+        $attributes = array(
+            'contextid'  => $this->context->id,
+            'component'  => $this->component,
+            'ratingarea' => $this->ratingarea,
+            'itemid'     => $this->itemid,
+            'scaleid'    => $this->settings->scale->id
+        );
+        if ($popup) {
+            $attributes['popup'] = 1;
+        }
+        return new moodle_url('/rating/index.php', $attributes);
+    }
+
+    /**
+     * Returns a URL that can be used to rate the associated item.
+     *
+     * @param int|null $rating The rating to give the item, if null then no rating
+     *                         param is added.
+     * @param moodle_url|string $returnurl The URL to return to.
+     * @return moodle_url
+     */
+    public function get_rate_url($rating = null, $returnurl = null) {
+        if (empty($returnurl)) {
+            if (!empty($this->settings->returnurl)) {
+                $returnurl = $this->settings->returnurl;
+            } else {
+                global $PAGE;
+                $returnurl = $PAGE->url;
+            }
+        }
+        $args = array(
+            'contextid'   => $this->context->id,
+            'component'   => $this->component,
+            'ratingarea'  => $this->ratingarea,
+            'itemid'      => $this->itemid,
+            'scaleid'     => $this->settings->scale->id,
+            'returnurl'   => $returnurl,
+            'rateduserid' => $this->itemuserid,
+            'aggregation' => $this->settings->aggregationmethod,
+            'sesskey'     => sesskey()
+        );
+        if (!empty($rating)) {
+            $args['rating'] = $rating;
+        }
+        $url = new moodle_url('/rating/rate.php', $args);
+        return $url;
     }
 
     /**
@@ -174,273 +391,365 @@ class rating implements renderable {
 class rating_manager {
 
     /**
-    * Delete one or more ratings. Specify either a rating id, an item id or just the context id.
-    * @param object $options {
-    *            contextid => int the context in which the ratings exist [required]
-    *            ratingid => int the id of an individual rating to delete [optional]
-    *            userid => int delete the ratings submitted by this user. May be used in conjuction with itemid [optional]
-    *            itemid => int delete all ratings attached to this item [optional]
-    * }
-    * @return void
-    */
+     * An array of calculated scale options to save us generating them for each request.
+     * @var array
+     */
+    protected $scales = array();
+
+    /**
+     * Gets set to true when the JavaScript that controls AJAX rating has been
+     * initialised (so that it only gets initialised once.
+     * @var int
+     */
+    protected $javascriptinitialised = false;
+
+    /**
+     * Delete one or more ratings. Specify either a rating id, an item id or just the context id.
+     *
+     * @global moodle_database $DB
+     * @param stdClass $options {
+     *            contextid => int the context in which the ratings exist [required]
+     *            ratingid => int the id of an individual rating to delete [optional]
+     *            userid => int delete the ratings submitted by this user. May be used in conjuction with itemid [optional]
+     *            itemid => int delete all ratings attached to this item [optional]
+     *            component => string The component to delete ratings from [optional]
+     *            ratingarea => string The ratingarea to delete ratings from [optional]
+     * }
+     * @return void
+     */
     public function delete_ratings($options) {
         global $DB;
 
-        if( !empty($options->ratingid) ) {
-            //delete a single rating
-            $DB->delete_records('rating', array('contextid'=>$options->contextid, 'id'=>$options->ratingid) );
-        }
-        else if( !empty($options->itemid) && !empty($options->userid) ) {
-            //delete the rating for an item submitted by a particular user
-            $DB->delete_records('rating', array('contextid'=>$options->contextid, 'itemid'=>$options->itemid, 'userid'=>$options->userid) );
+        if (empty($options->contextid)) {
+            throw new coding_exception('The context option is a required option when deleting ratings.');
         }
-        else if( !empty($options->itemid) ) {
-            //delete all ratings for an item
-            $DB->delete_records('rating', array('contextid'=>$options->contextid, 'itemid'=>$options->itemid) );
-        }
-        else if( !empty($options->userid) ) {
-            //delete all ratings submitted by a user
-            $DB->delete_records('rating', array('contextid'=>$options->contextid, 'userid'=>$options->userid) );
-        }
-        else {
-            //delete all ratings for this context
-            $DB->delete_records('rating', array('contextid'=>$options->contextid) );
+
+        $conditions = array('contextid' => $options->contextid);
+        $possibleconditions = array(
+            'ratingid'   => 'id',
+            'userid'     => 'userid',
+            'itemid'     => 'itemid',
+            'component'  => 'component',
+            'ratingarea' => 'ratingarea'
+        );
+        foreach ($possibleconditions as $option => $field) {
+            if (isset($options->{$option})) {
+                $conditions[$field] = $options->{$option};
+            }
         }
+        $DB->delete_records('rating', $conditions);
     }
 
     /**
-    * Returns an array of ratings for a given item (forum post, glossary entry etc)
-    * This returns all users ratings for a single item
-    * @param object $options {
-    *            context => context the context in which the ratings exists [required]
-    *            itemid  =>  int the id of the associated item (forum post, glossary item etc) [required]
-    *            sort    => string SQL sort by clause [optional]
-    * }
-    * @return array an array of ratings
-    */
+     * Returns an array of ratings for a given item (forum post, glossary entry etc)
+     * This returns all users ratings for a single item
+     * @param stdClass $options {
+     *            context => context the context in which the ratings exists [required]
+     *            component => component using ratings ie mod_forum [required]
+     *            ratingarea => ratingarea to associate this rating with [required]
+     *            itemid  =>  int the id of the associated item (forum post, glossary item etc) [required]
+     *            sort    => string SQL sort by clause [optional]
+     * }
+     * @return array an array of ratings
+     */
     public function get_all_ratings_for_item($options) {
         global $DB;
 
+        if (!isset($options->context)) {
+            throw new coding_exception('The context option is a required option when getting ratings for an item.');
+        }
+        if (!isset($options->itemid)) {
+            throw new coding_exception('The itemid option is a required option when getting ratings for an item.');
+        }
+        if (!isset($options->component)) {
+            throw new coding_exception('The component option is now a required option when getting ratings for an item.');
+        }
+        if (!isset($options->ratingarea)) {
+            throw new coding_exception('The ratingarea option is now a required option when getting ratings for an item.');
+        }
+
         $sortclause = '';
         if( !empty($options->sort) ) {
             $sortclause = "ORDER BY $options->sort";
         }
 
+        $params = array(
+            'contextid'  => $options->context->id,
+            'itemid'     => $options->itemid,
+            'component'  => $options->component,
+            'ratingarea' => $options->ratingarea,
+        );
         $userfields = user_picture::fields('u', null, 'userid');
-        $sql = "SELECT r.id, r.rating, r.itemid, r.userid, r.timemodified, $userfields
-                FROM {rating} r
-                LEFT JOIN {user} u ON r.userid = u.id
-                WHERE r.contextid = :contextid AND
-                      r.itemid  = :itemid
-                {$sortclause}";
-
-        $params['contextid'] = $options->context->id;
-        $params['itemid'] = $options->itemid;
+        $sql = "SELECT r.id, r.rating, r.itemid, r.userid, r.timemodified, r.component, r.ratingarea, $userfields
+                  FROM {rating} r
+             LEFT JOIN {user} u ON r.userid = u.id
+                 WHERE r.contextid = :contextid AND
+                       r.itemid  = :itemid AND
+                       r.component = :component AND
+                       r.ratingarea = :ratingarea
+                       {$sortclause}";
 
         return $DB->get_records_sql($sql, $params);
     }
 
     /**
-    * Adds rating objects to an array of items (forum posts, glossary entries etc)
-    * Rating objects are available at $item->rating
-    * @param object $options {
-    *            context => context the context in which the ratings exists [required]
-    *            component => the component name ie mod_forum [required]
-    *            items  => array an array of items such as forum posts or glossary items. They must have an 'id' member ie $items[0]->id[required]
-    *            aggregate    => int what aggregation method should be applied. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
-    *            scaleid => int the scale from which the user can select a rating [required]
-    *            userid => int the id of the current user [optional]
-    *            returnurl => string the url to return the user to after submitting a rating. Can be left null for ajax requests [optional]
-    *            assesstimestart => int only allow rating of items created after this timestamp [optional]
-    *            assesstimefinish => int only allow rating of items created before this timestamp [optional]
-    * @return array the array of items with their ratings attached at $items[0]->rating
-    */
+     * Adds rating objects to an array of items (forum posts, glossary entries etc)
+     * Rating objects are available at $item->rating
+     * @param stdClass $options {
+     *            context          => context the context in which the ratings exists [required]
+     *            component        => the component name ie mod_forum [required]
+     *            ratingarea       => the ratingarea we are interested in [required]
+     *            items            => array an array of items such as forum posts or glossary items. They must have an 'id' member ie $items[0]->id[required]
+     *            aggregate        => int what aggregation method should be applied. RATING_AGGREGATE_AVERAGE, RATING_AGGREGATE_MAXIMUM etc [required]
+     *            scaleid          => int the scale from which the user can select a rating [required]
+     *            userid           => int the id of the current user [optional]
+     *            returnurl        => string the url to return the user to after submitting a rating. Can be left null for ajax requests [optional]
+     *            assesstimestart  => int only allow rating of items created after this timestamp [optional]
+     *            assesstimefinish => int only allow rating of items created before this timestamp [optional]
+     * @return array the array of items with their ratings attached at $items[0]->rating
+     */
     public function get_ratings($options) {
-        global $DB, $USER, $PAGE, $CFG;
+        global $DB, $USER;
 
-        //are ratings enabled?
-        if ($options->aggregate==RATING_AGGREGATE_NONE) {
-            return $options->items;
+        if (!isset($options->context)) {
+            throw new coding_exception('The context option is a required option when getting ratings.');
+        }
+
+        if (!isset($options->component)) {
+            throw new coding_exception('The component option is a required option when getting ratings.');
+        }
+
+        if (!isset($options->ratingarea)) {
+            throw new coding_exception('The ratingarea option is a required option when getting ratings.');
         }
-        $aggregatestr = $this->get_aggregation_method($options->aggregate);
 
-        if(empty($options->items)) {
+        if (!isset($options->scaleid)) {
+            throw new coding_exception('The scaleid option is a required option when getting ratings.');
+        }
+
+        if (!isset($options->items)) {
+            throw new coding_exception('The items option is a required option when getting ratings.');
+        } else if (empty($options->items)) {
+            return array();
+        }
+
+        if (!isset($options->aggregate)) {
+            throw new coding_exception('The aggregate option is a required option when getting ratings.');
+        } else if ($options->aggregate == RATING_AGGREGATE_NONE) {
+            // Ratings arn't enabled.
             return $options->items;
         }
+        $aggregatestr = $this->get_aggregation_method($options->aggregate);
 
-        $userid = null;
+        // Default the userid to the current user if it is not set
         if (empty($options->userid)) {
             $userid = $USER->id;
         } else {
             $userid = $options->userid;
         }
 
-        //create an array of item ids
+        // Get the item table name, the item id field, and the item user field for the given rating item
+        // from the related component.
+        list($type, $name) = normalize_component($options->component);
+        $default = array(null, 'id', 'userid');
+        list($itemtablename, $itemidcol, $itemuseridcol) = plugin_callback($type, $name, 'rating', 'get_item_fields', array($options), $default);
+
+        // Create an array of item ids
         $itemids = array();
-        foreach($options->items as $item) {
-            $itemids[] = $item->id;
-        }
-
-        //get the items from the database
-        list($itemidtest, $params) = $DB->get_in_or_equal(
-                $itemids, SQL_PARAMS_NAMED, 'itemid0000');
-
-       //note: all the group bys arent really necessary but PostgreSQL complains
-       //about selecting a mixture of grouped and non-grouped columns
-        $sql = "SELECT r.itemid, ur.id, ur.userid, ur.scaleid,
-        $aggregatestr(r.rating) AS aggrrating,
-        COUNT(r.rating) AS numratings,
-        ur.rating AS usersrating
-    FROM {rating} r
-    LEFT JOIN {rating} ur ON ur.contextid = r.contextid AND
-            ur.itemid = r.itemid AND
-            ur.userid = :userid
-    WHERE
-        r.contextid = :contextid AND
-        r.itemid $itemidtest
-    GROUP BY r.itemid, ur.rating, ur.id, ur.userid, ur.scaleid
-    ORDER BY r.itemid";
-
-        $params['userid'] = $userid;
-        $params['contextid'] = $options->context->id;
+        foreach ($options->items as $item) {
+            $itemids[] = $item->{$itemidcol};
+        }
 
+        // get the items from the database
+        list($itemidtest, $params) = $DB->get_in_or_equal($itemids, SQL_PARAMS_NAMED);
+        $params['contextid'] = $options->context->id;
+        $params['userid']    = $userid;
+        $params['component']    = $options->component;
+        $params['ratingarea'] = $options->ratingarea;
+
+        $sql = "SELECT r.itemid, r.component, r.ratingarea, r.contextid,
+                       $aggregatestr(r.rating) AS aggrrating, COUNT(r.rating) AS numratings,
+                       ur.id, ur.userid, ur.scaleid, ur.rating AS usersrating
+                  FROM {rating} r
+             LEFT JOIN {rating} ur ON ur.contextid = r.contextid AND
+                                      ur.itemid = r.itemid AND
+                                      ur.component = r.component AND
+                                      ur.ratingarea = r.ratingarea AND
+                                      ur.userid = :userid
+                 WHERE r.contextid = :contextid AND
+                       r.itemid {$itemidtest} AND
+                       r.component = :component AND
+                       r.ratingarea = :ratingarea
+              GROUP BY r.itemid, r.component, r.ratingarea, r.contextid, ur.id, ur.userid, ur.scaleid
+              ORDER BY r.itemid";
         $ratingsrecords = $DB->get_records_sql($sql, $params);
 
-        //now create the rating sub objects
-        $scaleobj = new stdClass();
-        $scalemax = null;
-
-        //we could look for a scale id on each item to allow each item to use a different scale
-        if($options->scaleid < 0 ) { //if its a scale (not numeric)
-            $scalerecord = $DB->get_record('scale', array('id' => -$options->scaleid));
-            if ($scalerecord) {
-                $scalearray = explode(',', $scalerecord->scale);
-
-                //is there a more efficient way to get the indexes to start at 1 instead of 0?
-                //this will go away when scales are refactored
-                $c = count($scalearray);
-                $n = null;
-                for($i=0; $i<$c; $i++) {
-                    $n = $i+1;
-                    $scaleobj->scaleitems["$n"] = $scalearray[$i];//treat index as a string to allow sorting without changing the value
-                }
-                krsort($scaleobj->scaleitems);//have the highest grade scale item appear first
-
-                $scaleobj->id = $options->scaleid;//dont use the one from the record or we "forget" that its negative
-                $scaleobj->name = $scalerecord->name;
-                $scaleobj->courseid = $scalerecord->courseid;
+        $ratingoptions = new stdClass;
+        $ratingoptions->context = $options->context;
+        $ratingoptions->component = $options->component;
+        $ratingoptions->ratingarea = $options->ratingarea;
+        $ratingoptions->settings = $this->generate_rating_settings_object($options);
+        foreach ($options->items as $item) {
+            if (array_key_exists($item->{$itemidcol}, $ratingsrecords)) {
+                // Note: rec->scaleid = the id of scale at the time the rating was submitted
+                // may be different from the current scale id
+                $rec = $ratingsrecords[$item->{$itemidcol}];
+                $ratingoptions->itemid = $item->{$itemidcol};
+                $ratingoptions->scaleid = $rec->scaleid;
+                $ratingoptions->userid = $rec->userid;
+                $ratingoptions->id = $rec->id;
+                $ratingoptions->aggregate = min($rec->aggrrating, $ratingoptions->settings->scale->max);
+                $ratingoptions->count = $rec->numratings;
+                $ratingoptions->rating = min($rec->usersrating, $ratingoptions->settings->scale->max);
+            } else {
+                $ratingoptions->itemid = $item->{$itemidcol};
+                $ratingoptions->scaleid = null;
+                $ratingoptions->userid = null;
+                $ratingoptions->id = null;
+                $ratingoptions->aggregate = null;
+                $ratingoptions->count = 0;
+                $ratingoptions->rating =  null;
+            }
 
-                $scalemax = count($scaleobj->scaleitems);
+            $rating = new rating($ratingoptions);
+            $rating->itemtimecreated = $this->get_item_time_created($item);
+            if (!empty($item->{$itemuseridcol})) {
+                $rating->itemuserid = $item->{$itemuseridcol};
             }
+            $item->rating = $rating;
         }
-        else { //its numeric
-            $scaleobj->scaleitems = $options->scaleid;
-            $scaleobj->id = $options->scaleid;
-            $scaleobj->name = null;
 
-            $scalemax = $options->scaleid;
+        return $options->items;
+    }
+
+    /**
+     * Generates a rating settings object based upon the options it is provided.
+     *
+     * @param stdClass $options {
+     *      context           => context the context in which the ratings exists [required]
+     *      component         => string The component the items belong to [required]
+     *      ratingarea        => string The