Merge pull request #2 from rwijaya/MDL-27450
authorDavid Mudrák <david.mudrak@gmail.com>
Fri, 27 May 2011 13:44:30 +0000 (06:44 -0700)
committerDavid Mudrák <david.mudrak@gmail.com>
Fri, 27 May 2011 13:44:30 +0000 (06:44 -0700)
MDL-27450 mod_survey: added conversion code to restore 1.9 backup to 2.1

270 files changed:
backup/converter/moodle1/handlerlib.php
backup/converter/moodle1/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_stepslib.php
backup/util/xml/parser/processors/simplified_parser_processor.class.php
backup/util/xml/parser/simpletest/testparser.php
blocks/dock.js
blocks/navigation/renderer.php
blocks/navigation/yui/navigation/navigation.js
lang/en/error.php
lib/db/install.xml
lib/db/upgrade.php
lib/outputrenderers.php
lib/rsslib.php
message/lib.php
mod/assignment/lib.php
mod/chat/lib.php
mod/choice/backup/moodle1/lib.php
mod/choice/lib.php
mod/choice/renderer.php
mod/data/backup/moodle1/lib.php [new file with mode: 0644]
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/lib.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/lib.php
mod/page/lib.php
mod/quiz/lib.php
mod/resource/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
version.php

index af4d371..e206429 100644 (file)
@@ -283,6 +283,15 @@ class moodle1_root_handler extends moodle1_xml_handler {
         return array(new convert_path('root_element', '/MOODLE_BACKUP'));
     }
 
+    /**
+     * Converts course_files and site_files
+     */
+    public function on_root_element_start() {
+        // convert course files
+        $fileshandler = new moodle1_files_handler($this->converter);
+        $fileshandler->process();
+    }
+
     /**
      * This is executed at the end of the moodle.xml parsing
      */
@@ -446,6 +455,15 @@ class moodle1_root_handler extends moodle1_xml_handler {
 
         $this->close_xml_writer();
 
+        // write files.xml
+        $this->open_xml_writer('files.xml');
+        $this->xmlwriter->begin_tag('files');
+        foreach ($this->converter->get_stash_itemids('files') as $fileid) {
+            $this->write_xml('file', $this->converter->get_stash('files', $fileid), array('/file/id'));
+        }
+        $this->xmlwriter->end_tag('files');
+        $this->close_xml_writer('files.xml');
+
         // make sure that the files required by the restore process have been generated.
         // missing file may happen if the watched tag is not present in moodle.xml (for example
         // QUESTION_CATEGORIES is optional in moodle.xml but questions.xml must exist in
@@ -465,27 +483,18 @@ class moodle1_root_handler extends moodle1_xml_handler {
 
 
 /**
- * The class responsible for course files migration
+ * The class responsible for course and site files migration
  *
- * The files in Moodle 1.9 backup are stored in moddata, user_files, group_files,
- * course_files and site_files folders.
+ * @todo migrate site_files
  */
 class moodle1_files_handler extends moodle1_xml_handler {
 
-    /** @var textlib */
-    protected $textlib;
-
     /**
      * Migrates course_files and site_files in the converter workdir
      */
     public function process() {
-        $this->textlib = textlib_get_instance();
-        $this->open_xml_writer('files.xml');
-        $this->xmlwriter->begin_tag('files');
         $this->migrate_course_files();
-        $this->migrate_site_files();
-        $this->xmlwriter->end_tag('files');
-        $this->close_xml_writer('files.xml');
+        // todo $this->migrate_site_files();
     }
 
     /**
@@ -493,167 +502,13 @@ class moodle1_files_handler extends moodle1_xml_handler {
      */
     protected function migrate_course_files() {
         $path = $this->converter->get_tempdir_path().'/course_files';
+        $ids  = array();
+        $fileman = $this->converter->get_file_manager($this->converter->get_contextid(CONTEXT_COURSE), 'course', 'legacy');
         if (file_exists($path)) {
-            $this->migrate_files($path);
-        }
-    }
-
-    /**
-     * Migrates site_files in the converter workdir
-     */
-    protected function migrate_site_files() {
-        $path = $this->converter->get_tempdir_path().'/site_files';
-        if (file_exists($path)) {
-            $this->migrate_files($path);
+            $ids = $fileman->migrate_directory($path);
+            $this->converter->set_stash('course_files_ids', $ids);
         }
     }
-
-    /**
-     * Migrates files in the given directory
-     *
-     * @param string $rootpath full path to the root directory containing the files (like course_files)
-     * @param string $relpath relative path used during the recursion
-     */
-    protected function migrate_files($rootpath, $relpath='/') {
-
-        $coursecontextid = $this->converter->get_contextid(CONTEXT_COURSE);
-
-        // make the fake file record for the directory itself
-        $filerecord = array(
-            'id'            => $this->converter->get_nextid(),
-            'contenthash'   => 'da39a3ee5e6b4b0d3255bfef95601890afd80709',  // sha1 of an empty file
-            'contextid'     => $coursecontextid,
-            'component'     => 'course',
-            'filearea'      => 'legacy',
-            'itemid'        => 0,
-            'filepath'      => $relpath,
-            'filename'      => '.',
-            'filesize'      => 0,
-            'userid'        => null,
-            'mimetype'      => null,
-            'status'        => 0,
-            'timecreated'   => $now = time(),
-            'timemodified'  => $now,
-            'source'        => null,
-            'author'        => null,
-            'license'       => null,
-            'sortorder'     => 0,
-        );
-        $this->write_xml('file', $filerecord, array('file/id'));
-        $this->converter->set_stash('course_files', $filerecord, $filerecord['id']);
-
-        $fullpath = $rootpath.$relpath;
-        $items    = new DirectoryIterator($fullpath);
-
-        foreach ($items as $item) {
-
-            if ($item->isDot()) {
-                continue;
-            }
-
-            if ($item->isLink()) {
-                throw new moodle1_convert_exception('unexpected_symlink');
-            }
-
-            if ($item->isFile()) {
-                if (!$item->isReadable()) {
-                    throw new moodle1_convert_exception('file_not_readable');
-                }
-
-                $filepath = clean_param($relpath, PARAM_PATH);
-                $filename = clean_param($item->getFilename(), PARAM_FILE);
-
-                if ($filename === '') {
-                    throw new moodle1_convert_exception('unsupported_chars_in_filename');
-                }
-
-                if ($this->textlib->strlen($filepath) > 255) {
-                    throw new moodle1_convert_exception('file_path_longer_than_255_chars');
-                }
-
-                // make the fake file record
-                $filerecord = array(
-                    'id'            => $this->converter->get_nextid(),
-                    'contenthash'   => null, // will be set below
-                    'contextid'     => $coursecontextid,
-                    'component'     => 'course',
-                    'filearea'      => 'legacy',
-                    'itemid'        => 0,
-                    'filepath'      => $filepath,
-                    'filename'      => $filename,
-                    'filesize'      => null, // will be set below
-                    'userid'        => null,
-                    'mimetype'      => mimeinfo('type', $item->getPathname()),
-                    'status'        => 0,
-                    'timecreated'   => $item->getCTime(),
-                    'timemodified'  => $item->getMTime(),
-                    'source'        => null,
-                    'author'        => null,
-                    'license'       => null,
-                    'sortorder'     => 0,
-                );
-
-                list($filerecord['contenthash'], $filerecord['filesize'], $newfile) = $this->move_file_to_pool($item->getPathname());
-                $this->write_xml('file', $filerecord, array('file/id'));
-                $this->converter->set_stash('course_files', $filerecord, $filerecord['id']);
-
-                if (!$newfile) {
-                    unlink($item->getPathname());
-                }
-
-            } else {
-                $dirname = clean_param($item->getFilename(), PARAM_PATH);
-
-                if ($dirname === '') {
-                    throw new moodle1_convert_exception('unsupported_chars_in_filename');
-                }
-
-                // migrate subdirectories recursively
-                $this->migrate_files($rootpath, $relpath.$item->getFilename().'/');
-            }
-        }
-    }
-
-    /**
-     * Moves the given path to the file pool directory
-     *
-     * @param string $pathname
-     * @return array => (string)contenthash, (int)filesize, (bool)newfile
-     */
-    protected function move_file_to_pool($pathname) {
-
-        if (!is_readable($pathname)) {
-            throw new moodle1_convert_exception('file_not_readable');
-        }
-
-        $contenthash = sha1_file($pathname);
-        $filesize    = filesize($pathname);
-        $hashpath    = $this->converter->get_workdir_path().'/files/'.substr($contenthash, 0, 2);
-        $hashfile    = "$hashpath/$contenthash";
-
-        if (file_exists($hashfile)) {
-            if (filesize($hashfile) !== $filesize) {
-                // congratulations! you have found two files with different size and the same
-                // content hash. or, something were wrong (which is more likely)
-                throw new moodle1_convert_exception('same_has_different_size');
-            }
-            $newfile = false;
-
-        } else {
-            check_dir_exists($hashpath);
-            $newfile = true;
-
-            if (!rename($pathname, $hashfile)) {
-                throw new moodle1_convert_exception('unable_to_move_file');
-            }
-
-            if (filesize($hashfile) !== $filesize) {
-                throw new moodle1_convert_exception('filesize_different_after_move');
-            }
-        }
-
-        return array($contenthash, $filesize, $newfile);
-    }
 }
 
 
@@ -827,19 +682,15 @@ class moodle1_course_header_handler extends moodle1_xml_handler {
         $this->write_xml('course', $this->course, array('/course/id', '/course/contextid'));
         $this->close_xml_writer();
 
-        // convert file - @todo move this to on_root_start()
-        $fileshandler = new moodle1_files_handler($this->converter);
-        $fileshandler->process();
-        unset($fileshandler);
-
         // generate course/inforef.xml
         $this->open_xml_writer('course/inforef.xml');
         $this->xmlwriter->begin_tag('inforef');
 
         $this->xmlwriter->begin_tag('fileref');
-        foreach ($this->converter->get_stash_itemids('course_files') as $fileid) {
+        foreach ($this->converter->get_stash('course_files_ids') as $fileid) {
             $this->write_xml('file', array('id' => $fileid));
         }
+        // todo site files
         $this->xmlwriter->end_tag('fileref');
 
         $this->xmlwriter->end_tag('inforef');
@@ -1084,8 +935,8 @@ abstract class moodle1_plugin_handler extends moodle1_xml_handler {
 
     /**
      * @param moodle1_converter $converter the converter that requires us
-     * @param string plugintype
-     * @param string pluginname
+     * @param string $plugintype
+     * @param string $pluginname
      */
     public function __construct(moodle1_converter $converter, $plugintype, $pluginname) {
 
@@ -1093,6 +944,15 @@ abstract class moodle1_plugin_handler extends moodle1_xml_handler {
         $this->plugintype = $plugintype;
         $this->pluginname = $pluginname;
     }
+
+    /**
+     * Returns the normalized name of the plugin, eg mod_workshop
+     *
+     * @return string
+     */
+    public function get_component_name() {
+        return $this->plugintype.'_'.$this->pluginname;
+    }
 }
 
 
@@ -1136,3 +996,35 @@ abstract class moodle1_mod_handler extends moodle1_plugin_handler {
 abstract class moodle1_block_handler extends moodle1_plugin_handler {
 
 }
+
+
+/**
+ * Base class for the activity modules' subplugins
+ */
+abstract class moodle1_submod_handler extends moodle1_plugin_handler {
+
+    /** @var moodle1_mod_handler */
+    protected $parenthandler;
+
+    /**
+     * @param moodle1_mod_handler $parenthandler the handler of a module we are subplugin of
+     * @param string $subplugintype the type of the subplugin
+     * @param string $subpluginname the name of the subplugin
+     */
+    public function __construct(moodle1_mod_handler $parenthandler, $subplugintype, $subpluginname) {
+        $this->parenthandler = $parenthandler;
+        parent::__construct($parenthandler->converter, $subplugintype, $subpluginname);
+    }
+
+    /**
+     * Activity module subplugins can't declare any paths to handle
+     *
+     * The paths must be registered by the parent module and then re-dispatched to the
+     * relevant subplugins for eventual processing.
+     *
+     * @return array empty array
+     */
+    final public function get_paths() {
+        return array();
+    }
+}
index 5f63d67..39a1555 100644 (file)
@@ -49,11 +49,11 @@ class moodle1_converter extends base_converter {
     /** @var array of {@link convert_path} to process */
     protected $pathelements = array();
 
-    /** @var string the current module being processed - used to expand the MOD paths */
-    protected $currentmod = '';
+    /** @var null|string the current module being processed - used to expand the MOD paths */
+    protected $currentmod = null;
 
-    /** @var string the current block being processed - used to expand the BLOCK paths */
-    protected $currentblock = '';
+    /** @var null|string the current block being processed - used to expand the BLOCK paths */
+    protected $currentblock = null;
 
     /** @var string path currently locking processing of children */
     protected $pathlock;
@@ -296,14 +296,41 @@ class moodle1_converter extends base_converter {
     /**
      * Executes operations required at the start of a watched path
      *
-     * Note that this is called before the MOD and BLOCK paths are expanded
-     * so the current plugin is not known yet. Also note that this is
-     * triggered before the previous path is actually dispatched.
+     * For MOD and BLOCK paths, this is supported only for the sub-paths, not the root
+     * module/block element. For the illustration:
+     *
+     * You CAN'T attach on_xxx_start() listener to a path like
+     * /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP because the <MOD> must
+     * be processed first in {@link self::process_chunk()} where $this->currentmod
+     * is set.
+     *
+     * You CAN attach some on_xxx_start() listener to a path like
+     * /MOODLE_BACKUP/COURSE/MODULES/MOD/WORKSHOP/SUBMISSIONS because it is
+     * a sub-path under <MOD> and we have $this->currentmod already set when the
+     * <SUBMISSIONS> is reached.
      *
      * @param string $path in the original file
      */
     public function path_start_reached($path) {
 
+        if ($path === '/MOODLE_BACKUP/COURSE/MODULES/MOD') {
+            $this->currentmod = null;
+            $forbidden = true;
+
+        } else if (strpos($path, '/MOODLE_BACKUP/COURSE/MODULES/MOD') === 0) {
+            // expand the MOD paths so that they contain the module name
+            $path = str_replace('/MOODLE_BACKUP/COURSE/MODULES/MOD', '/MOODLE_BACKUP/COURSE/MODULES/MOD/' . $this->currentmod, $path);
+        }
+
+        if ($path === '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') {
+            $this->currentmod = null;
+            $forbidden = true;
+
+        } else if (strpos($path, '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK') === 0) {
+            // expand the BLOCK paths so that they contain the module name
+            $path = str_replace('/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK', '/MOODLE_BACKUP/COURSE/BLOCKS/BLOCK/' . $this->currentmod, $path);
+        }
+
         if (empty($this->pathelements[$path])) {
             return;
         }
@@ -313,7 +340,13 @@ class moodle1_converter extends base_converter {
         $method  = $element->get_start_method();
 
         if (method_exists($pobject, $method)) {
-            $pobject->$method();
+            if (empty($forbidden)) {
+                $pobject->$method();
+
+            } else {
+                // this path is not supported because we do not know the module/block yet
+                throw new coding_exception('Attaching the on-start event listener to the root MOD or BLOCK element is forbidden.');
+            }
         }
     }
 
@@ -472,6 +505,20 @@ class moodle1_converter extends base_converter {
         return ++$autoincrement;
     }
 
+    /**
+     * Creates and returns new instance of the file manager
+     *
+     * @param int $contextid the default context id of the files being migrated
+     * @param string $component the default component name of the files being migrated
+     * @param string $filearea the default file area of the files being migrated
+     * @param int $itemid the default item id of the files being migrated
+     * @param int $userid initial user id of the files being migrated
+     * @return moodle1_file_manager
+     */
+    public function get_file_manager($contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
+        return new moodle1_file_manager($this, $contextid, $component, $filearea, $itemid, $userid);
+    }
+
     /**
      * @see parent::description()
      */
@@ -952,3 +999,261 @@ class convert_path_exception extends moodle_exception {
         parent::__construct($errorcode, '', '', $a, $debuginfo);
     }
 }
+
+
+/**
+ * The class responsible for files migration
+ *
+ * The files in Moodle 1.9 backup are stored in moddata, user_files, group_files,
+ * course_files and site_files folders.
+ */
+class moodle1_file_manager {
+
+    /** @var moodle1_converter instance we serve to */
+    public $converter;
+
+    /** @var int context id of the files being migrated */
+    public $contextid;
+
+    /** @var string component name of the files being migrated */
+    public $component;
+
+    /** @var string file area of the files being migrated */
+    public $filearea;
+
+    /** @var int item id of the files being migrated */
+    public $itemid = 0;
+
+    /** @var int user id */
+    public $userid;
+
+    /** @var textlib instance used during the migration */
+    protected $textlib;
+
+    /** @var array of file ids that were migrated by this instance */
+    protected $fileids = array();
+
+    /**
+     * Constructor optionally accepting some default values for the migrated files
+     *
+     * @param moodle1_converter $converter the converter instance we serve to
+     * @param int $contextid initial context id of the files being migrated
+     * @param string $component initial component name of the files being migrated
+     * @param string $filearea initial file area of the files being migrated
+     * @param int $itemid initial item id of the files being migrated
+     * @param int $userid initial user id of the files being migrated
+     */
+    public function __construct(moodle1_converter $converter, $contextid = null, $component = null, $filearea = null, $itemid = 0, $userid = null) {
+
+        $this->converter = $converter;
+        $this->contextid = $contextid;
+        $this->component = $component;
+        $this->filearea  = $filearea;
+        $this->itemid    = $itemid;
+        $this->userid    = $userid;
+        $this->textlib   = textlib_get_instance();
+    }
+
+    /**
+     * Migrates one given file stored on disk
+     *
+     * @param string $sourcefullpath the full path to the source local file
+     * @param string $filepath the file path of the migrated file, defaults to the root directory '/'
+     * @param string $filename the name of the migrated file, defaults to the same as the source file has
+     * @param int $timecreated override the timestamp of when the migrated file should appear as created
+     * @param int $timemodified override the timestamp of when the migrated file should appear as modified
+     * @return int id of the migrated file
+     */
+    public function migrate_file($sourcefullpath, $filepath = '/', $filename = null, $timecreated = null, $timemodified = null) {
+
+        if (!is_readable($sourcefullpath)) {
+            throw new moodle1_convert_exception('file_not_readable');
+        }
+
+        $filepath = clean_param($filepath, PARAM_PATH);
+
+        if ($this->textlib->strlen($filepath) > 255) {
+            throw new moodle1_convert_exception('file_path_longer_than_255_chars');
+        }
+
+        if (is_null($filename)) {
+            $filename = basename($sourcefullpath);
+        }
+
+        $filename = clean_param($filename, PARAM_FILE);
+
+        if ($filename === '') {
+            throw new moodle1_convert_exception('unsupported_chars_in_filename');
+        }
+
+        if (is_null($timecreated)) {
+            $timecreated = filectime($sourcefullpath);
+        }
+
+        if (is_null($timemodified)) {
+            $timemodified = filemtime($sourcefullpath);
+        }
+
+        $filerecord = $this->make_file_record(array(
+            'filepath'      => $filepath,
+            'filename'      => $filename,
+            'mimetype'      => mimeinfo('type', $sourcefullpath),
+            'timecreated'   => $timecreated,
+            'timemodified'  => $timemodified,
+        ));
+
+        list($filerecord['contenthash'], $filerecord['filesize'], $newfile) = $this->add_file_to_pool($sourcefullpath);
+        $this->stash_file($filerecord);
+
+        return $filerecord['id'];
+    }
+
+    /**
+     * Migrates all files in the given directory
+     *
+     * @param string $rootpath full path to the root directory containing the files (like course_files)
+     * @param string $relpath relative path used during the recursion - do not provide when calling this!
+     * @return array ids of the migrated files
+     */
+    public function migrate_directory($rootpath, $relpath='/') {
+
+        $fileids = array();
+
+        // make the fake file record for the directory itself
+        $filerecord = $this->make_file_record(array('filepath' => $relpath, 'filename' => '.'));
+        $this->stash_file($filerecord);
+        $fileids[] = $filerecord['id'];
+
+        $fullpath = $rootpath.$relpath;
+        $items    = new DirectoryIterator($fullpath);
+
+        foreach ($items as $item) {
+
+            if ($item->isDot()) {
+                continue;
+            }
+
+            if ($item->isLink()) {
+                throw new moodle1_convert_exception('unexpected_symlink');
+            }
+
+            if ($item->isFile()) {
+                $fileids[] = $this->migrate_file($item->getPathname(), $relpath, $item->getFilename(), $item->getCTime(), $item->getMTime());
+
+            } else {
+                $dirname = clean_param($item->getFilename(), PARAM_PATH);
+
+                if ($dirname === '') {
+                    throw new moodle1_convert_exception('unsupported_chars_in_filename');
+                }
+
+                // migrate subdirectories recursively
+                $fileids = array_merge($fileids, $this->migrate_directory($rootpath, $relpath.$item->getFilename().'/'));
+            }
+        }
+
+        return $fileids;
+    }
+
+    /**
+     * Returns the list of all file ids migrated by this instance so far
+     *
+     * @return array of int
+     */
+    public function get_fileids() {
+        return $this->fileids;
+    }
+
+    /// internal implementation details ////////////////////////////////////////
+
+    /**
+     * Prepares a fake record from the files table
+     *
+     * @param array $fileinfo explicit file data
+     * @return array
+     */
+    protected function make_file_record(array $fileinfo) {
+
+        $defaultrecord = array(
+            'contenthash'   => 'da39a3ee5e6b4b0d3255bfef95601890afd80709',  // sha1 of an empty file
+            'contextid'     => $this->contextid,
+            'component'     => $this->component,
+            'filearea'      => $this->filearea,
+            'itemid'        => $this->itemid,
+            'filepath'      => null,
+            'filename'      => null,
+            'filesize'      => 0,
+            'userid'        => $this->userid,
+            'mimetype'      => null,
+            'status'        => 0,
+            'timecreated'   => $now = time(),
+            'timemodified'  => $now,
+            'source'        => null,
+            'author'        => null,
+            'license'       => null,
+            'sortorder'     => 0,
+        );
+
+        if (!array_key_exists('id', $fileinfo)) {
+            $defaultrecord['id'] = $this->converter->get_nextid();
+        }
+
+        // override the default values with the explicit data provided and return
+        return array_merge($defaultrecord, $fileinfo);
+    }
+
+    /**
+     * Copies the given file to the pool directory
+     *
+     * Returns an array containing SHA1 hash of the file contents, the file size
+     * and a flag indicating whether the file was actually added to the pool or whether
+     * it was already there.
+     *
+     * @param string $pathname the full path to the file
+     * @return array with keys (string)contenthash, (int)filesize, (bool)newfile
+     */
+    protected function add_file_to_pool($pathname) {
+
+        if (!is_readable($pathname)) {
+            throw new moodle1_convert_exception('file_not_readable');
+        }
+
+        $contenthash = sha1_file($pathname);
+        $filesize    = filesize($pathname);
+        $hashpath    = $this->converter->get_workdir_path().'/files/'.substr($contenthash, 0, 2);
+        $hashfile    = "$hashpath/$contenthash";
+
+        if (file_exists($hashfile)) {
+            if (filesize($hashfile) !== $filesize) {
+                // congratulations! you have found two files with different size and the same
+                // content hash. or, something were wrong (which is more likely)
+                throw new moodle1_convert_exception('same_hash_different_size');
+            }
+            $newfile = false;
+
+        } else {
+            check_dir_exists($hashpath);
+            $newfile = true;
+
+            if (!copy($pathname, $hashfile)) {
+                throw new moodle1_convert_exception('unable_to_copy_file');
+            }
+
+            if (filesize($hashfile) !== $filesize) {
+                throw new moodle1_convert_exception('filesize_different_after_copy');
+            }
+        }
+
+        return array($contenthash, $filesize, $newfile);
+    }
+
+    /**
+     * Stashes the file record into 'files' stash and adds the record id to list of migrated files
+     *
+     * @param array $filerecord
+     */
+    protected function stash_file(array $filerecord) {
+        $this->converter->set_stash('files', $filerecord, $filerecord['id']);
+        $this->fileids[] = $filerecord['id'];
+    }
+}
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 0c3ddb6..4ba9120 100644 (file)
@@ -419,6 +419,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 53fd788..7c66910 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 c65a4be..c3c5cdd 100644 (file)
@@ -174,12 +174,12 @@ abstract class simplified_parser_processor extends progressive_parser_processor
      */
     public function after_path($path) {
         $toprocess = false;
-        // If the path being closed matches (same or parent) the last path in the stack
+        // 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 ($eleaction = 'end' && strpos($elepath, $path) === 0) {
+            if (strpos($elepath, $path) === 0) {
                 $toprocess = true;
             }
 
index 589f8c9..cae713f 100644 (file)
@@ -652,43 +652,45 @@ class progressive_parser_test extends UnitTestCase {
     function helper_check_notifications_order_integrity($notifications) {
         $numerrors = 0;
         $notifpile = array('pilebase' => 'start');
-        $lastpile = 'start:pilebase';
+        $lastnotif = 'start:pilebase';
         foreach ($notifications as $notif) {
-            $lastpilelevel = strlen(preg_replace('/[^\/]/', '', $lastpile));
-            $lastpiletype  = preg_replace('/:.*/', '', $lastpile);
-            $lastpilepath  = preg_replace('/.*:/', '', $lastpile);
 
-            $notiflevel = strlen(preg_replace('/[^\/]/', '', $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 ($lastpilepath != $notifpath or $lastpiletype != 'start') {
-                        $numerrors++; // Only start for same path is allowed before 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 for same path is allowed before end
+                        $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++; // If same path exists or the level is < than the last one
+                        $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 lastpile
-            end($notifpile);
-            $path = key($notifpile);
-            $type = $notifpile[$path];
-            $lastpile = $type. ':' . $path;
+            // Update lastnotif
+            $lastnotif = $notif;
         }
         return $numerrors;
     }
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 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 27b3c1e..5c1397d 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">
       </KEYS>
     </TABLE>
   </TABLES>
-</XMLDB>
+</XMLDB>
\ No newline at end of file
index b47810c..2918eb1 100644 (file)
@@ -6062,6 +6062,56 @@ WHERE gradeitemid IS NOT NULL AND grademax IS NOT NULL");
         upgrade_main_savepoint(true, 2011022100.01);
     }
 
+    if ($oldversion < 2011052300.00) {
+        $table = new xmldb_table('rating');
+
+        // Add the component field to the ratings table
+        upgrade_set_timeout(60 * 20);
+        $field = new xmldb_field('component', XMLDB_TYPE_CHAR, '100', null, XMLDB_NOTNULL, null, 'unknown', 'contextid');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        // Add the ratingarea field to the ratings table
+        upgrade_set_timeout(60 * 20);
+        $field = new xmldb_field('ratingarea', XMLDB_TYPE_CHAR, '50', null, XMLDB_NOTNULL, null, 'unknown', 'component');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+
+        upgrade_main_savepoint(true, 2011052300.00);
+    }
+
+    if ($oldversion < 2011052300.01) {
+
+        // Define index uniqueuserrating (unique) to be added to rating
+        $table = new xmldb_table('rating');
+        $index = new xmldb_index('uniqueuserrating', XMLDB_INDEX_NOTUNIQUE, array('component', 'ratingarea', 'contextid', 'itemid'));
+
+        // Conditionally launch add index uniqueuserrating
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011052300.01);
+    }
+
+    if ($oldversion < 2011052300.02) {
+
+        // Define index itemid (not unique) to be dropped form rating
+        $table = new xmldb_table('rating');
+        $index = new xmldb_index('itemid', XMLDB_INDEX_NOTUNIQUE, array('itemid'));
+
+        // Conditionally launch drop index itemid
+        if ($dbman->index_exists($table, $index)) {
+            $dbman->drop_index($table, $index);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2011052300.02);
+    }
+
 
     return true;
 }
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 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 611abc8..acf231b 100644 (file)
@@ -1966,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;
index c681a3c..eed4da0 100644 (file)
@@ -2772,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 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 635681f..2d8c595 100644 (file)
@@ -61,6 +61,7 @@ class moodle1_mod_choice_handler extends moodle1_mod_handler {
                     ),
                 )
             ),
+            new convert_path('choice_options', '/MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE/OPTIONS'),
             new convert_path('choice_option', '/MOODLE_BACKUP/COURSE/MODULES/MOD/CHOICE/OPTIONS/OPTION'),
         );
     }
@@ -87,7 +88,12 @@ class moodle1_mod_choice_handler extends moodle1_mod_handler {
         foreach ($data as $field => $value) {
             $this->xmlwriter->full_tag($field, $value);
         }
+    }
 
+    /**
+     * This is executed when the parser reaches the <OPTIONS> opening element
+     */
+    public function on_choice_options_start() {
         $this->xmlwriter->begin_tag('options');
     }
 
@@ -99,12 +105,18 @@ class moodle1_mod_choice_handler extends moodle1_mod_handler {
         $this->write_xml('option', $data, array('/option/id'));
     }
 
+    /**
+     * This is executed when the parser reaches the closing </OPTIONS> element
+     */
+    public function on_choice_options_end() {
+        $this->xmlwriter->end_tag('options');
+    }
+
     /**
      * This is executed when we reach the closing </MOD> tag of our 'choice' path
      */
     public function on_choice_end() {
-
-        $this->xmlwriter->end_tag('options');
+        // finalize choice.xml
         $this->xmlwriter->end_tag('choice');
         $this->xmlwriter->end_tag('activity');
         $this->close_xml_writer();
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' ));
diff --git a/mod/data/backup/moodle1/lib.php b/mod/data/backup/moodle1/lib.php
new file mode 100644 (file)
index 0000000..8059161
--- /dev/null
@@ -0,0 +1,147 @@
+<?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/>.
+
+/**
+ * Provides support for the conversion of moodle1 backup to the moodle2 format
+ * Based off of a template @ http://docs.moodle.org/en/Development:Backup_1.9_conversion_for_developers
+ *
+ * @package    mod
+ * @subpackage data
+ * @copyright  2011 Aparup Banerjee <aparup@moodle.com>
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Database conversion handler
+ */
+class moodle1_mod_data_handler extends moodle1_mod_handler {
+
+    /**
+     * Declare the paths in moodle.xml we are able to convert
+     *
+     * The method returns list of {@link convert_path} instances.
+     * For each path returned, the corresponding conversion method must be
+     * defined.
+     *
+     * Note that the path /MOODLE_BACKUP/COURSE/MODULES/MOD/DATA does not
+     * actually exist in the file. The last element with the module name was
+     * appended by the moodle1_converter class.
+     *
+     * @return array of {@link convert_path} instances
+     */
+    public function get_paths() {
+        return array(
+            new convert_path('data', '/MOODLE_BACKUP/COURSE/MODULES/MOD/DATA',
+                        array(
+                            'newfields' => array(
+                                'introformat' => 0,
+                                'assesstimestart' => 0,
+                                'assesstimefinish' => 0,
+                            )
+                        )
+                    ),
+            new convert_path('data_field', '/MOODLE_BACKUP/COURSE/MODULES/MOD/DATA/FIELDS/FIELD')
+        );
+    }
+
+    /**
+     * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/DATA
+     * data available
+     */
+    public function process_data($data) {
+        global $CFG;
+        // get the course module id and context id
+        $instanceid = $data['id'];
+        $cminfo     = $this->get_cminfo($instanceid);
+        $moduleid   = $cminfo['id'];
+        $contextid  = $this->converter->get_contextid(CONTEXT_MODULE, $moduleid);
+
+        //upgrade data
+
+        //Upgrade all the data->notification currently being NULL to 0
+        if (is_null($data['notification'])) {
+            $data['notification'] = 0;
+        }
+
+        //@todo: user data - upgrade content to new file storage
+
+        // add 'export' tag to list and single template.
+        $pattern = '/\#\#delete\#\#(\s+)\#\#approve\#\#/';
+        $replacement = '##delete##$1##approve##$1##export##';
+        $data['listtemplate'] = preg_replace($pattern, $replacement, $data['listtemplate']);
+        $data['singletemplate'] = preg_replace($pattern, $replacement, $data['singletemplate']);
+
+        // conditionally migrate to html format in intro
+        if ($CFG->texteditors !== 'textarea' && $data['introformat'] == FORMAT_MOODLE ) {
+            $data['intro'] = text_to_html($data['intro'], false, false, true);
+            $data['introformat'] = FORMAT_HTML;
+        }
+
+        //@todo: user data - move data comments to comments table
+        //@todo: user data - move data ratings to ratings table
+
+        // we now have all information needed to start writing into the file
+        $this->open_xml_writer("activities/data_{$moduleid}/data.xml");
+        $this->xmlwriter->begin_tag('activity', array('id' => $instanceid, 'moduleid' => $moduleid,
+            'modulename' => 'data', 'contextid' => $contextid));
+        $this->xmlwriter->begin_tag('data', array('id' => $instanceid));
+
+        unset($data['id']); // we already write it as attribute, do not repeat it as child element
+        $addfield = true;
+        foreach ($data as $field => $value) {
+            if ($field == 'asearchtemplate') {
+                //add field asearchtemplate (if doesn't exist already)
+                $addfield = false;
+            }
+            $this->xmlwriter->full_tag($field, $value);
+        }
+        if ($addfield) {
+            $this->xmlwriter->full_tag('asearchtemplate', null);
+        }
+        $this->xmlwriter->begin_tag('fields');
+    }
+
+    /**
+     * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/DATA/FIELDS/FIELD
+     * data available
+     */
+    public function process_data_field($data) {
+        // process database fields
+        $this->write_xml('field', $data, array('/field/id'));
+    }
+    /**
+     * This is executed every time we have one /MOODLE_BACKUP/COURSE/MODULES/MOD/DATA/RECORDS/RECORD
+     * data available
+     */
+    public function process_data_record($data) {
+        //@todo process user data, and define the convert path in get_paths() above.
+        //$this->write_xml('record', $data, array('/record/id'));
+    }
+
+    /**
+     * This is executed when we reach the closing </MOD> tag of our 'data' path
+     */
+    public function on_data_end() {
+
+        $this->xmlwriter->end_tag('fields');
+        $this->xmlwriter->end_tag('data');
+        $this->xmlwriter->end_tag('activity');
+        $this->close_xml_writer();
+    }
+}
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 4ab2ee5..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;
         }
@@ -1016,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;
 
@@ -1028,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);
 }
 
@@ -1144,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();
 
@@ -1357,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)
+    );
 }
 
 /**
@@ -1387,14 +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)
-            || !array_key_exists('scaleid', $params)) {
-        throw new rating_exception('missingparameter');
+    // Check the component is mod_data
+    if ($params['component'] != 'mod_data') {
+        throw new rating_exception('invalidcomponent');
     }
 
-    $datasql = "SELECT d.id as did, d.scale, d.course, r.userid as userid, d.approval, r.approved, r.timecreated, d.assesstimestart, d.assesstimefinish, r.groupid
+    // Check the ratingarea is entry (the only rating area in data module)
+    if ($params['ratingarea'] != 'entry') {
+        throw new rating_exception('invalidratingarea');
+    }
+
+    // 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";
@@ -1409,22 +1443,17 @@ function data_rating_validate($params) {
         throw new rating_exception('invalidscaleid');
     }
 
-    if ($info->userid == $USER->id) {
-        //user is attempting to rate their own glossary entry
-        throw new rating_exception('nopermissiontorate');
-    }
+    //check that the submitted rating is valid for the scale
 
-    if ($info->userid != $params['rateduserid']) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
+        throw new rating_exception('invalidnum');
     }
 
-    //check that the submitted rating is valid for the scale
-    if ($params['rating'] < 0) {
-        throw new rating_exception('invalidnum');
-    } else if ($info->scale < 0) {
+    // upper limit
+    if ($info->scale < 0) {
         //its a custom scale
-        $scalerecord = $DB->get_record('scale', array('id' => -$params['scaleid']));
+        $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
         if ($scalerecord) {
             $scalearray = explode(',', $scalerecord->scale);
             if ($params['rating'] > count($scalearray)) {
@@ -1443,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;
+    $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);
 
-    $cm = get_coursemodule_from_instance('data', $dataid);
-    if (empty($cm)) {
-        throw new rating_exception('unknowncontext');
-    }
-    $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) {
+    // 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
@@ -1676,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);
     }
 }
@@ -2523,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 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 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 d850e57..b43997b 100644 (file)
@@ -1445,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);
 }
 
@@ -1586,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
  */
@@ -1595,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) {
@@ -2050,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
@@ -2456,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
             }
@@ -3451,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]
@@ -3479,45 +3496,57 @@ 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)
-            || !array_key_exists('scaleid', $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, f.scale, 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->scale != $params['scaleid']) {
-        //the scale being submitted doesnt match the one in the database
-        throw new rating_exception('invalidscaleid');
+    // 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 ($info->userid == $USER->id) {
-        //user is attempting to rate their own post
-        throw new rating_exception('nopermissiontorate');
+    // 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');
     }
 
-    if ($info->userid != $params['rateduserid']) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    // 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');
+        }
     }
 
     //check that the submitted rating is valid for the scale
-    if ($params['rating'] < 0) {
+
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
         throw new rating_exception('invalidnum');
-    } else if ($info->scale < 0) {
+    }
+
+    // upper limit
+    if ($forum->scale < 0) {
         //its a custom scale
-        $scalerecord = $DB->get_record('scale', array('id' => -$params['scaleid']));
+        $scalerecord = $DB->get_record('scale', array('id' => -$forum->scale));
         if ($scalerecord) {
             $scalearray = explode(',', $scalerecord->scale);
             if ($params['rating'] > count($scalearray)) {
@@ -3526,61 +3555,25 @@ function forum_rating_validate($params) {
         } else {
             throw new rating_exception('invalidscaleid');
         }
-    } else if ($params['rating'] > $info->scale) {
+    } else if ($params['rating'] > $forum->scale) {
         //if its numeric and submitted rating is above maximum
         throw new rating_exception('invalidnum');
     }
 
-    //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');
-        }
-    }
-
-    $forumid = $info->fid;
-    $discussionid = $info->did;
-    $groupid = $info->groupid;
-    $courseid = $info->course;
-
-    $cm = get_coursemodule_from_instance('forum', $forumid);
-    if (empty($cm)) {
-        throw new rating_exception('unknowncontext');
-    }
-    $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');
-    }
-
     // 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');
     }
 
@@ -4350,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);
@@ -5386,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);
@@ -5420,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();
 
@@ -5453,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;
@@ -7130,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) {
@@ -7142,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)) {
@@ -7190,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 edd9e42..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);
@@ -445,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;
@@ -459,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]
@@ -493,19 +502,28 @@ 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)
-            || !array_key_exists('scaleid', $params)) {
-        throw new rating_exception('missingparameter');
+    // Check the component is mod_forum
+    if ($params['component'] != 'mod_glossary') {
+        throw new rating_exception('invalidcomponent');
     }
 
-    $glossarysql = "SELECT g.id as gid, g.scale, e.userid as userid, e.approved, e.timecreated, g.assesstimestart, g.assesstimefinish
+    // 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 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');
     }
@@ -515,22 +533,17 @@ function glossary_rating_validate($params) {
         throw new rating_exception('invalidscaleid');
     }
 
-    if ($info->userid == $USER->id) {
-        //user is attempting to rate their own glossary entry
-        throw new rating_exception('nopermissiontorate');
-    }
+    //check that the submitted rating is valid for the scale
 
-    if ($info->userid != $params['rateduserid']) {
-        //supplied user ID doesnt match the user ID from the database
-        throw new rating_exception('invaliduserid');
+    // lower limit
+    if ($params['rating'] < 0  && $params['rating'] != RATING_UNSET_RATING) {
+        throw new rating_exception('invalidnum');
     }
 
-    //check that the submitted rating is valid for the scale
-    if ($params['rating'] < 0) {
-        throw new rating_exception('invalidnum');
-    } else if ($info->scale < 0) {
+    // upper limit
+    if ($info->scale < 0) {
         //its a custom scale
-        $scalerecord = $DB->get_record('scale', array('id' => -$params['scaleid']));
+        $scalerecord = $DB->get_record('scale', array('id' => -$info->scale));
         if ($scalerecord) {
             $scalearray = explode(',', $scalerecord->scale);
             if ($params['rating'] > count($scalearray)) {
@@ -556,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');
     }
 
@@ -683,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
  */
@@ -2190,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
             }
@@ -2466,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 4c866b7..efaec0f 100644 (file)
@@ -528,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 be6b2b6..5524773 100644 (file)
@@ -745,8 +745,8 @@ function quiz_get_grading_options() {
 /**
  * Returns an array of users who have data in a given quiz
  *
- * @global stdClass
- * @global object
+ * @todo: deprecated - to be deleted in 2.2
+ *
  * @param int $quizid
  * @return array
  */
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 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 ratingarea the items belong to [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]
+     *      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]
+     *      plugintype        => string plugin type ie 'mod' Used to find the permissions callback [optional]
+     *      pluginname        => string plugin name ie 'forum' Used to find the permissions callback [optional]
+     * }
+     * @return stdClass
+     */
+    protected function generate_rating_settings_object($options) {
+
+        if (!isset($options->context)) {
+            throw new coding_exception('The context option is a required option when generating a rating settings object.');
+        }
+        if (!isset($options->component)) {
+            throw new coding_exception('The component option is now a required option when generating a rating settings object.');
+        }
+        if (!isset($options->ratingarea)) {
+            throw new coding_exception('The ratingarea option is now a required option when generating a rating settings object.');
+        }
+        if (!isset($options->aggregate)) {
+            throw new coding_exception('The aggregate option is now a required option when generating a rating settings object.');
+        }
+        if (!isset($options->scaleid)) {
+            throw new coding_exception('The scaleid option is now a required option when generating a rating settings object.');
         }
 
-        //should $settings and $settings->permissions be declared as proper classes?
-        $settings = new stdclass(); //settings that are common to all ratings objects in this context
-        $settings->component =$options->component;
-        $settings->scale = $scaleobj; //the scale to use now
+        // settings that are common to all ratings objects in this context
+        $settings = new stdClass;
+        $settings->scale             = $this->generate_rating_scale_object($options->scaleid); // the scale to use now
         $settings->aggregationmethod = $options->aggregate;
-        if( !empty($options->returnurl) ) {
-            $settings->returnurl = $options->returnurl;
-        }
+        $settings->assesstimestart   = null;
+        $settings->assesstimefinish  = null;
 
-        $settings->assesstimestart = $settings->assesstimefinish = null;
-        if( !empty($options->assesstimestart) ) {
+        // Collect options into the settings object
+        if (!empty($options->assesstimestart)) {
             $settings->assesstimestart = $options->assesstimestart;
         }
-        if( !empty($options->assesstimefinish) ) {
+        if (!empty($options->assesstimefinish)) {
             $settings->assesstimefinish = $options->assesstimefinish;
         }
+        if (!empty($options->returnurl)) {
+            $settings->returnurl = $options->returnurl;
+        }
 
-        //check site capabilities
-        $settings->permissions = new stdclass();
-        $settings->permissions->view = has_capability('moodle/rating:view',$options->context);//can view the aggregate of ratings of their own items
-        $settings->permissions->viewany = has_capability('moodle/rating:viewany',$options->context);//can view the aggregate of ratings of other people's items
-        $settings->permissions->viewall = has_capability('moodle/rating:viewall',$options->context);//can view individual ratings
-        $settings->permissions->rate = has_capability('moodle/rating:rate',$options->context);//can submit ratings
-
-        //check module capabilities (mostly for backwards compatability with old modules that previously implemented their own ratings)
-        $pluginpermissionsarray = $this->get_plugin_permissions_array($options->context->id, $options->component);
-
-        $settings->pluginpermissions = new stdclass();
-        $settings->pluginpermissions->view = $pluginpermissionsarray['view'];
+        // check site capabilities
+        $settings->permissions = new stdClass;
+        $settings->permissions->view    = has_capability('moodle/rating:view', $options->context); // can view the aggregate of ratings of their own items
+        $settings->permissions->viewany = has_capability('moodle/rating:viewany', $options->context); // can view the aggregate of ratings of other people's items
+        $settings->permissions->viewall = has_capability('moodle/rating:viewall', $options->context); // can view individual ratings
+        $settings->permissions->rate    = has_capability('moodle/rating:rate', $options->context); // can submit ratings
+
+        // check module capabilities (mostly for backwards compatability with old modules that previously implemented their own ratings)
+        $pluginpermissionsarray = $this->get_plugin_permissions_array($options->context->id, $options->component, $options->ratingarea);
+        $settings->pluginpermissions = new stdClass;
+        $settings->pluginpermissions->view    = $pluginpermissionsarray['view'];
         $settings->pluginpermissions->viewany = $pluginpermissionsarray['viewany'];
         $settings->pluginpermissions->viewall = $pluginpermissionsarray['viewall'];
-        $settings->pluginpermissions->rate = $pluginpermissionsarray['rate'];
+        $settings->pluginpermissions->rate    = $pluginpermissionsarray['rate'];
 
-        $rating = null;
-        $ratingoptions = new stdclass();
-        $ratingoptions->context = $options->context;//context is common to all ratings in the set
-        $ratingoptions->component = $options->component;
-        foreach($options->items as $item) {
-            $rating = null;
-            //match the item with its corresponding rating
-            foreach($ratingsrecords as $rec) {
-                if( $item->id==$rec->itemid ) {
-                    //Note: rec->scaleid = the id of scale at the time the rating was submitted
-                    //may be different from the current scale id
-                    $ratingoptions->itemid = $item->id;
-                    $ratingoptions->scaleid = $rec->scaleid;
-                    $ratingoptions->userid = $rec->userid;
-
-                    $rating = new rating($ratingoptions);
-                    $rating->id         = $rec->id;    //unset($rec->id);
-                    $rating->aggregate  = $rec->aggrrating; //unset($rec->aggrrating);
-                    $rating->count      = $rec->numratings; //unset($rec->numratings);
-                    $rating->rating     = $rec->usersrating; //unset($rec->usersrating);
-                    $rating->itemtimecreated = $this->get_item_time_created($item);
-
-                    break;
-                }
-            }
-            //if there are no ratings for this item
-            if( !$rating ) {
-                $ratingoptions->itemid = $item->id;
-                $ratingoptions->scaleid = null;
-                $ratingoptions->userid = null;
-
-                $rating = new rating($ratingoptions);
-                $rating->id         = null;
-                $rating->aggregate  = null;
-                $rating->count      = 0;
-                $rating->rating     = null;
-
-                $rating->itemid     = $item->id;
-                $rating->userid     = null;
-                $rating->scaleid     = null;
-                $rating->itemtimecreated = $this->get_item_time_created($item);
-            }
+        return $settings;
+    }
 
-            if( !empty($item->userid) ) {
-                $rating->itemuserid = $item->userid;
+    /**
+     * Generates a scale object that can be returned
+     *
+     * @global moodle_database $DB
+     * @param type $scaleid
+     * @return stdClass
+     */
+    protected function generate_rating_scale_object($scaleid) {
+        global $DB;
+        if (!array_key_exists('s'.$scaleid, $this->scales)) {
+            $scale = new stdClass;
+            $scale->id = $scaleid;
+            $scale->name = null;
+            $scale->courseid = null;
+            $scale->scaleitems = array();
+            $scale->isnumeric = true;
+            $scale->max = $scaleid;
+
+            if ($scaleid < 0) {
+                // It is a proper scale (not numeric)
+                $scalerecord = $DB->get_record('scale', array('id' => abs($scaleid)));
+                if ($scalerecord) {
+                    // We need to generate an array with string keys starting at 1
+                    $scalearray = explode(',', $scalerecord->scale);
+                    $c = count($scalearray);
+                    for ($i = 0; $i < $c; $i++) {
+                        // treat index as a string to allow sorting without changing the value
+                        $scale->scaleitems[(string)($i + 1)] = $scalearray[$i];
+                    }
+                    krsort($scale->scaleitems); // have the highest grade scale item appear first
+                    $scale->isnumeric = false;
+                    $scale->name = $scalerecord->name;
+                    $scale->courseid = $scalerecord->courseid;
+                    $scale->max = count($scale->scaleitems);
+                }
             } else {
-                $rating->itemuserid = null;
-            }
-            $rating->settings = $settings;
-            $item->rating = $rating;
-
-            //Below is a nasty hack presumably here to handle scales being changed (out of 10 to out of 5 for example)
-            //
-            // it could throw off the grading if count and sum returned a grade higher than scale
-            // so to prevent it we review the results and ensure that grade does not exceed the scale, if it does we set grade = scale (i.e. full credit)
-            if ($rating->rating > $scalemax) {
-                $rating->rating = $scalemax;
-            }
-            if ($rating->aggregate > $scalemax) {
-                $rating->aggregate = $scalemax;
+                //generate an array of values for numeric scales
+                for($i = 0; $i <= (int)$scaleid; $i++) {
+                    $scale->scaleitems[(string)$i] = $i;
+                }
             }
+            $this->scales['s'.$scaleid] = $scale;
         }
-
-        return $options->items;
+        return $this->scales['s'.$scaleid];
     }
 
-    private function get_item_time_created($item) {
+    /**
+     * Gets the time the given item was created
+     *
+     * TODO: Find a better solution for this, its not ideal to test for fields really we should be
+     * asking the component the item belongs to what field to look for or even the value we
+     * are looking for.
+     *
+     * @param stdClass $item
+     * @return mixed
+     */
+    protected function get_item_time_created($item) {
         if( !empty($item->created) ) {
             return $item->created;//the forum_posts table has created instead of timecreated
         }
@@ -453,26 +762,35 @@ class rating_manager {
     }
 
     /**
-    * Returns an array of grades calculated by aggregating item ratings.
-    * @param object $options {
-    *            userid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
-    *            aggregationmethod => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
-    *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
-    *            itemtable => int the table containing the items [required]
-    *            itemtableusercolum => int the column of the user table containing the item owner's user id [required]
-    *
-    *            contextid => int the context in which the rated items exist [optional]
-    *
-    *            modulename => string the name of the module [optional]
-    *            moduleid => int the id of the module instance [optional]
-    *
-    * @return array the array of the user's grades
-    */
+     * Returns an array of grades calculated by aggregating item ratings.
+     * @param object $options {
+     *            userid => int the id of the user whose items have been rated. NOT the user who submitted the ratings. 0 to update all. [required]
+     *            aggregationmethod => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [required]
+     *            scaleid => int the scale from which the user can select a rating. Used for bounds checking. [required]
+     *            itemtable => int the table containing the items [required]
+     *            itemtableusercolum => int the column of the user table containing the item owner's user id [required]
+     *            component => The component for the ratings [required]
+     *            ratingarea => The ratingarea for the ratings [required]
+     *
+     *            contextid => int the context in which the rated items exist [optional]
+     *
+     *            modulename => string the name of the module [optional]
+     *            moduleid => int the id of the module instance [optional]
+     *
+     * @return array the array of the user's grades
+     */
     public function get_user_grades($options) {
         global $DB;
 
         $contextid = null;
 
+        if (!isset($options->component)) {
+            throw new coding_exception('The component option is now a required option when getting user grades from ratings.');
+        }
+        if (!isset($options->ratingarea)) {
+            throw new coding_exception('The ratingarea option is now a required option when getting user grades from ratings.');
+        }
+
         //if the calling code doesn't supply a context id we'll have to figure it out
         if( !empty($options->contextid) ) {
             $contextid = $options->contextid;
@@ -489,24 +807,27 @@ class rating_manager {
             //going direct to the db for the context id seems wrong
             list($ctxselect, $ctxjoin) = context_instance_preload_sql('cm.id', CONTEXT_MODULE, 'ctx');
             $sql = "SELECT cm.* $ctxselect
-            FROM {course_modules} cm
-            LEFT JOIN {modules} mo ON mo.id = cm.module
-            LEFT JOIN {{$modulename}} m ON m.id = cm.instance $ctxjoin
-            WHERE mo.name=:modulename AND m.id=:moduleid";
+                      FROM {course_modules} cm
+                 LEFT JOIN {modules} mo ON mo.id = cm.module
+                 LEFT JOIN {{$modulename}} m ON m.id = cm.instance $ctxjoin
+                     WHERE mo.name=:modulename AND
+                           m.id=:moduleid";
             $contextrecord = $DB->get_record_sql($sql, array('modulename'=>$modulename, 'moduleid'=>$moduleid), '*', MUST_EXIST);
             $contextid = $contextrecord->ctxid;
         }
 
         $params = array();
-        $params['contextid']= $contextid;
-        $itemtable          = $options->itemtable;
-        $itemtableusercolumn= $options->itemtableusercolumn;
-        $scaleid            = $options->scaleid;
-        $aggregationstring = $this->get_aggregation_method($options->aggregationmethod);
+        $params['contextid']  = $contextid;
+        $params['component']  = $options->component;
+        $params['ratingarea'] = $options->ratingarea;
+        $itemtable            = $options->itemtable;
+        $itemtableusercolumn  = $options->itemtableusercolumn;
+        $scaleid              = $options->scaleid;
+        $aggregationstring    = $this->get_aggregation_method($options->aggregationmethod);
 
         //if userid is not 0 we only want the grade for a single user
         $singleuserwhere = '';
-        if ($options->userid!=0) {
+        if ($options->userid != 0) {
             $params['userid1'] = intval($options->userid);
             $singleuserwhere = "AND i.{$itemtableusercolumn} = :userid1";
         }
@@ -515,13 +836,14 @@ class rating_manager {
         //r.contextid will be null for users who haven't been rated yet
         //no longer including users who haven't been rated to reduce memory requirements
         $sql = "SELECT u.id as id, u.id AS userid, $aggregationstring(r.rating) AS rawgrade
-                FROM {user} u
-                LEFT JOIN {{$itemtable}} i ON u.id=i.{$itemtableusercolumn}
-                LEFT JOIN {rating} r ON r.itemid=i.id
-                WHERE r.contextid=:contextid
-                $singleuserwhere
-                GROUP BY u.id";
-
+                  FROM {user} u
+             LEFT JOIN {{$itemtable}} i ON u.id=i.{$itemtableusercolumn}
+             LEFT JOIN {rating} r ON r.itemid=i.id
+                 WHERE r.contextid = :contextid AND
+                       r.component = :component AND
+                       r.ratingarea = :ratingarea
+                       $singleuserwhere
+              GROUP BY u.id";
         $results = $DB->get_records_sql($sql, $params);
 
         if ($results) {
@@ -568,19 +890,19 @@ class rating_manager {
      * @return array
      */
     public function get_aggregate_types() {
-        return array (RATING_AGGREGATE_NONE  => get_string('aggregatenone', 'rating'),
-                      RATING_AGGREGATE_AVERAGE   => get_string('aggregateavg', 'rating'),
-                      RATING_AGGREGATE_COUNT => get_string('aggregatecount', 'rating'),
-                      RATING_AGGREGATE_MAXIMUM   => get_string('aggregatemax', 'rating'),
-                      RATING_AGGREGATE_MINIMUM   => get_string('aggregatemin', 'rating'),
-                      RATING_AGGREGATE_SUM   => get_string('aggregatesum', 'rating'));
+        return array (RATING_AGGREGATE_NONE     => get_string('aggregatenone', 'rating'),
+                      RATING_AGGREGATE_AVERAGE  => get_string('aggregateavg', 'rating'),
+                      RATING_AGGREGATE_COUNT    => get_string('aggregatecount', 'rating'),
+                      RATING_AGGREGATE_MAXIMUM  => get_string('aggregatemax', 'rating'),
+                      RATING_AGGREGATE_MINIMUM  => get_string('aggregatemin', 'rating'),
+                      RATING_AGGREGATE_SUM      => get_string('aggregatesum', 'rating'));
     }
 
     /**
-    * Converts an aggregation method constant into something that can be included in SQL
-    * @param int $aggregate An aggregation constant. For example, RATING_AGGREGATE_AVERAGE.
-    * @return string an SQL aggregation method
-    */
+     * Converts an aggregation method constant into something that can be included in SQL
+     * @param int $aggregate An aggregation constant. For example, RATING_AGGREGATE_AVERAGE.
+     * @return string an SQL aggregation method
+     */
     public function get_aggregation_method($aggregate) {
         $aggregatestr = null;
         switch($aggregate){
@@ -607,17 +929,18 @@ class rating_manager {
     }
 
     /**
-    * Looks for a callback like forum_rating_permissions() to retrieve permissions from the plugin whose items are being rated
-    * @param int $contextid The current context id
-    * @param string component the name of the component that is using ratings ie 'mod_forum'
-    * @return array rating related permissions
-    */
-    public function get_plugin_permissions_array($contextid, $component=null) {
+     * Looks for a callback like forum_rating_permissions() to retrieve permissions from the plugin whose items are being rated
+     * @param int $contextid The current context id
+     * @param string component the name of the component that is using ratings ie 'mod_forum'
+     * @param string ratingarea The area the rating is associated with
+     * @return array rating related permissions
+     */
+    public function get_plugin_permissions_array($contextid, $component, $ratingarea) {
         $pluginpermissionsarray = null;
         $defaultpluginpermissions = array('rate'=>false,'view'=>false,'viewany'=>false,'viewall'=>false);//deny by default
         if (!empty($component)) {
             list($type, $name) = normalize_component($component);
-            $pluginpermissionsarray = plugin_callback($type, $name, 'rating', 'permissions', array($contextid), $defaultpluginpermissions);
+            $pluginpermissionsarray = plugin_callback($type, $name, 'rating', 'permissions', array($contextid, $component, $ratingarea), $defaultpluginpermissions);
         } else {
             $pluginpermissionsarray = $defaultpluginpermissions;
         }
@@ -628,28 +951,100 @@ class rating_manager {
      * 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 the rating belongs to [required]
+     *            ratingarea => The ratingarea the rating is associated with [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]
      *            aggregation => int the aggregation method to apply when calculating grades ie RATING_AGGREGATE_AVERAGE [optional]
      * @return boolean true if the rating is valid. False if callback wasnt found and will throw rating_exception if rating is invalid
      */
-    public function check_rating_is_valid($component, $params) {
-        list($plugintype, $pluginname) = normalize_component($component);
+    public function check_rating_is_valid($params) {
+
+        if (!isset($params['context'])) {
+            throw new coding_exception('The context option is a required option when checking rating validity.');
+        }
+        if (!isset($params['component'])) {
+            throw new coding_exception('The component option is now a required option when checking rating validity');
+        }
+        if (!isset($params['ratingarea'])) {
+            throw new coding_exception('The ratingarea option is now a required option when checking rating validity');
+        }
+        if (!isset($params['itemid'])) {
+            throw new coding_exception('The itemid option is now a required option when checking rating validity');
+        }
+        if (!isset($params['scaleid'])) {
+            throw new coding_exception('The scaleid option is now a required option when checking rating validity');
+        }
+        if (!isset($params['rateduserid'])) {
+            throw new coding_exception('The rateduserid option is now a required option when checking rating validity');
+        }
 
-        //this looks for a function like forum_rating_is_valid() in mod_forum lib.php
+        list($plugintype, $pluginname) = normalize_component($params['component']);
+
+        //this looks for a function like forum_rating_validate() in mod_forum lib.php
         //wrapping the params array in another array as call_user_func_array() expands arrays into multiple arguments
         $isvalid = plugin_callback($plugintype, $pluginname, 'rating', 'validate', array($params), null);
 
         //if null then the callback doesn't exist
         if ($isvalid === null) {
             $isvalid = false;
-            debugging('plugin rating_add() function not found');
+            debugging('rating validation callback not found for component '.  clean_param($component, PARAM_ALPHANUMEXT));
         }
-
         return $isvalid;
     }
+
+    /**
+     * Initialises JavaScript to enable AJAX ratings on the provided page
+     *
+     * @param moodle_page $page
+     * @return true
+     */
+    public function initialise_rating_javascript(moodle_page $page) {
+        global $CFG;
+
+        if ($this->javascriptinitialised) {
+            return true;
+        }
+
+        if (!empty($CFG->enableajax)) {
+            $page->requires->js_init_call('M.core_rating.init');
+        }
+
+        $this->javascriptinitialised = true;
+        return true;
+    }
+
+    /**
+     * Returns a string that describes the aggregation method that was provided.
+     *
+     * @param string $aggregationmethod
+     * @return string
+     */
+    public function get_aggregate_label($aggregationmethod) {
+        $aggregatelabel = '';
+        switch ($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');
+        return $aggregatelabel;
+    }
+
 }//end rating_manager class definition
 
 class rating_exception extends moodle_exception {
index f9b881a..ad6f66c 100644 (file)
  */
 
 require_once('../config.php');
-require_once('lib.php');
-
-$contextid = required_param('contextid', PARAM_INT);
-$component = required_param('component', PARAM_ALPHAEXT);
-$itemid = required_param('itemid', PARAM_INT);
-$scaleid = required_param('scaleid', PARAM_INT);
-$userrating = required_param('rating', PARAM_INT);
+require_once($CFG->dirroot.'/rating/lib.php');
+
+$contextid   = required_param('contextid', PARAM_INT);
+$component   = required_param('component', PARAM_ALPHAEXT);
+$ratingarea  = required_param('ratingarea', PARAM_ALPHANUMEXT);
+$itemid      = required_param('itemid', PARAM_INT);
+$scaleid     = required_param('scaleid', PARAM_INT);
+$userrating  = required_param('rating', PARAM_INT);
 $rateduserid = required_param('rateduserid', PARAM_INT);//which user is being rated. Required to update their grade
-$returnurl = required_param('returnurl', PARAM_LOCALURL);//required for non-ajax requests
+$returnurl   = required_param('returnurl', PARAM_LOCALURL);//required for non-ajax requests
 
 $result = new stdClass;
 
@@ -44,7 +45,7 @@ require_login($course, false, $cm);
 
 $contextid = null;//now we have a context object throw away the id from the user
 $PAGE->set_context($context);
-$PAGE->set_url('/rating/rate.php', array('contextid'=>$context->id));
+$PAGE->set_url('/rating/rate.php', array('contextid' => $context->id));
 
 if (!confirm_sesskey() || !has_capability('moodle/rating:rate',$context)) {
     echo $OUTPUT->header();
@@ -57,7 +58,7 @@ $rm = new rating_manager();
 
 //check the module rating permissions
 //doing this check here rather than within rating_manager::get_ratings() so we can return a json error response
-$pluginpermissionsarray = $rm->get_plugin_permissions_array($context->id, $component);
+$pluginpermissionsarray = $rm->get_plugin_permissions_array($context->id, $component, $ratingarea);
 
 if (!$pluginpermissionsarray['rate']) {
     $result->error = get_string('ratepermissiondenied', 'rating');
@@ -65,13 +66,15 @@ if (!$pluginpermissionsarray['rate']) {
     die();
 } else {
     $params = array(
-        'context' => $context,
-        'itemid' => $itemid,
-        'scaleid' => $scaleid,
-        'rating' => $userrating,
-        'rateduserid' => $rateduserid);
-
-    if (!$rm->check_rating_is_valid($component, $params)) {
+        'context'     => $context,
+        'component'   => $component,
+        'ratingarea'  => $ratingarea,
+        'itemid'      => $itemid,
+        'scaleid'     => $scaleid,
+        'rating'      => $userrating,
+        'rateduserid' => $rateduserid
+    );
+    if (!$rm->check_rating_is_valid($params)) {
         echo $OUTPUT->header();
         echo get_string('ratinginvalid', 'rating');
         echo $OUTPUT->footer();
@@ -80,9 +83,10 @@ if (!$pluginpermissionsarray['rate']) {
 }
 
 if ($userrating != RATING_UNSET_RATING) {
-    $ratingoptions = new stdClass();
+    $ratingoptions = new stdClass;
     $ratingoptions->context = $context;
     $ratingoptions->component = $component;
+    $ratingoptions->ratingarea = $ratingarea;
     $ratingoptions->itemid  = $itemid;
     $ratingoptions->scaleid = $scaleid;
     $ratingoptions->userid  = $USER->id;
@@ -90,9 +94,10 @@ if ($userrating != RATING_UNSET_RATING) {
     $rating = new rating($ratingoptions);
     $rating->update_rating($userrating);
 } else { //delete the rating if the user set to Rate...
-    $options = new stdClass();
+    $options = new stdClass;
     $options->contextid = $context->id;
     $options->component = $component;
+    $options->ratingarea = $ratingarea;
     $options->userid = $USER->id;
     $options->itemid = $itemid;
 
@@ -101,15 +106,13 @@ if ($userrating != RATING_UNSET_RATING) {
 
 //todo add a setting to turn grade updating off for those who don't want them in gradebook
 //note that this needs to be done in both rate.php and rate_ajax.php
-if(true){
+if (!empty($cm) && $context->contextlevel == CONTEXT_MODULE) {
     //tell the module that its grades have changed
-    if ( !$modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance)) ) {
-        print_error('invalidid');
-    }
+    $modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance), '*', MUST_EXIST);
     $modinstance->cmidnumber = $cm->id; //MDL-12961
     $functionname = $cm->modname.'_update_grades';
     require_once($CFG->dirroot."/mod/{$cm->modname}/lib.php");
-    if(function_exists($functionname)) {
+    if (function_exists($functionname)) {
         $functionname($modinstance, $rateduserid);
     }
 }
index 3a26223..c3e5bf8 100644 (file)
 define('AJAX_SCRIPT', true);
 
 require_once('../config.php');
-require_once('lib.php');
-
-$contextid = required_param('contextid', PARAM_INT);
-$component = required_param('component', PARAM_ALPHAEXT);
-$itemid = required_param('itemid', PARAM_INT);
-$scaleid = required_param('scaleid', PARAM_INT);
-$userrating = required_param('rating', PARAM_INT);
-$rateduserid = required_param('rateduserid', PARAM_INT);//which user is being rated. Required to update their grade
+require_once($CFG->dirroot.'/rating/lib.php');
+
+$contextid         = required_param('contextid', PARAM_INT);
+$component         = required_param('component', PARAM_ALPHAEXT);
+$ratingarea        = required_param('ratingarea', PARAM_ALPHANUMEXT);
+$itemid            = required_param('itemid', PARAM_INT);
+$scaleid           = required_param('scaleid', PARAM_INT);
+$userrating        = required_param('rating', PARAM_INT);
+$rateduserid       = required_param('rateduserid', PARAM_INT);//which user is being rated. Required to update their grade
 $aggregationmethod = optional_param('aggregation', RATING_AGGREGATE_NONE, PARAM_INT);//we're going to calculate the aggregate and return it to the client
 
 $result = new stdClass;
@@ -66,7 +67,7 @@ $rm = new rating_manager();
 
 //check the module rating permissions
 //doing this check here rather than within rating_manager::get_ratings() so we can return a json error response
-$pluginpermissionsarray = $rm->get_plugin_permissions_array($context->id, $component);
+$pluginpermissionsarray = $rm->get_plugin_permissions_array($context->id, $component, $ratingarea);
 
 if (!$pluginpermissionsarray['rate']) {
     $result->error = get_string('ratepermissiondenied', 'rating');
@@ -74,14 +75,16 @@ if (!$pluginpermissionsarray['rate']) {
     die();
 } else {
     $params = array(
-        'context' => $context,
-        'itemid' => $itemid,
-        'scaleid' => $scaleid,
-        'rating' => $userrating,
+        'context'     => $context,
+        'component'   => $component,
+        'ratingarea'  => $ratingarea,
+        'itemid'      => $itemid,
+        'scaleid'     => $scaleid,
+        'rating'      => $userrating,
         'rateduserid' => $rateduserid,
-        'aggregation' => $aggregationmethod);
-
-    if (!$rm->check_rating_is_valid($component, $params)) {
+        'aggregation' => $aggregationmethod
+    );
+    if (!$rm->check_rating_is_valid($params)) {
         $result->error = get_string('ratinginvalid', 'rating');
         echo json_encode($result);
         die();
@@ -89,8 +92,9 @@ if (!$pluginpermissionsarray['rate']) {
 }
 
 //rating options used to update the rating then retrieve the aggregate
-$ratingoptions = new stdClass();
+$ratingoptions = new stdClass;
 $ratingoptions->context = $context;
+$ratingoptions->ratingarea = $ratingarea;
 $ratingoptions->component = $component;
 $ratingoptions->itemid  = $itemid;
 $ratingoptions->scaleid = $scaleid;
@@ -100,56 +104,57 @@ if ($userrating != RATING_UNSET_RATING) {
     $rating = new rating($ratingoptions);
     $rating->update_rating($userrating);
 } else { //delete the rating if the user set to Rate...
-    $options = new stdClass();
+    $options = new stdClass;
     $options->contextid = $context->id;
     $options->component = $component;
+    $options->ratingarea = $ratingarea;
     $options->userid = $USER->id;
     $options->itemid = $itemid;
 
     $rm->delete_ratings($options);
 }
 
-//Future possible enhancement: add a setting to turn grade updating off for those who don't want them in gradebook
-//note that this would need to be done in both rate.php and rate_ajax.php
-    if ($context->contextlevel==CONTEXT_MODULE) {
-        //tell the module that its grades have changed
-        if ( $modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance)) ) {
-            $modinstance->cmidnumber = $cm->id; //MDL-12961
-            $functionname = $cm->modname.'_update_grades';
-            require_once("../mod/{$cm->modname}/lib.php");
-            if(function_exists($functionname)) {
-                $functionname($modinstance, $rateduserid);
-            }
+// Future possible enhancement: add a setting to turn grade updating off for those who don't want them in gradebook
+// note that this would need to be done in both rate.php and rate_ajax.php
+if ($context->contextlevel == CONTEXT_MODULE) {
+    //tell the module that its grades have changed
+    $modinstance = $DB->get_record($cm->modname, array('id' => $cm->instance));
+    if ($modinstance) {
+        $modinstance->cmidnumber = $cm->id; //MDL-12961
+        $functionname = $cm->modname.'_update_grades';
+        require_once($CFG->dirroot."/mod/{$cm->modname}/lib.php");
+        if (function_exists($functionname)) {
+            $functionname($modinstance, $rateduserid);
         }
     }
+}
 
 //object to return to client as json
-$result = new stdClass;
 $result->success = true;
 
 //need to retrieve the updated item to get its new aggregate value
-$item = new stdclass();
+$item = new stdClass;
 $item->id = $itemid;
-$items = array($item);
 
 //most of $ratingoptions variables were previously set
-$ratingoptions->items = $items;
+$ratingoptions->items = array($item);
 $ratingoptions->aggregate = $aggregationmethod;
 
 $items = $rm->get_ratings($ratingoptions);
+$firstrating = $items[0]->rating;
 
 //for custom scales return text not the value
 //this scales weirdness will go away when scales are refactored
 $scalearray = null;
-$aggregatetoreturn = round($items[0]->rating->aggregate,1);
+$aggregatetoreturn = round($firstrating->aggregate, 1);
 
 // Output a dash if aggregation method == COUNT as the count is output next to the aggregate anyway
-if ($items[0]->rating->settings->aggregationmethod==RATING_AGGREGATE_COUNT or $items[0]->rating->count == 0) {
+if ($firstrating->settings->aggregationmethod == RATING_AGGREGATE_COUNT or $firstrating->count == 0) {
     $aggregatetoreturn = ' - ';
-} else if($items[0]->rating->settings->scale->id < 0) { //if its non-numeric scale
+} else if ($firstrating->settings->scale->id < 0) { //if its non-numeric scale
     //dont use the scale item if the aggregation method is sum as adding items from a custom scale makes no sense
-    if ($items[0]->rating->settings->aggregationmethod!= RATING_AGGREGATE_SUM) {
-        $scalerecord = $DB->get_record('scale', array('id' => -$items[0]->rating->settings->scale->id));
+    if ($firstrating->settings->aggregationmethod != RATING_AGGREGATE_SUM) {
+        $scalerecord = $DB->get_record('scale', array('id' => -$firstrating->settings->scale->id));
         if ($scalerecord) {
             $scalearray = explode(',', $scalerecord->scale);
             $aggregatetoreturn = $scalearray[$aggregatetoreturn-1];
@@ -158,17 +163,9 @@ if ($items[0]->rating->settings->aggregationmethod==RATING_AGGREGATE_COUNT or $i
 }
 
 //See if the user has permission to see the rating aggregate
-//we could do this check as "if $userid==$rateduserid" but going to the database to determine item owner id seems more secure
-//if we accept the item owner user id from the http request a user could alter the URL and erroneously get access to the rating aggregate
-
-//if its their own item and they have view permission
-if (($USER->id==$items[0]->rating->itemuserid && has_capability('moodle/rating:view',$context)
-        && (empty($pluginpermissionsarray) or $pluginpermissionsarray['view']))
-    //or if its not their item or if no user created the item (the hub did) and they have viewany permission
-    || (($USER->id!=$items[0]->rating->itemuserid or empty($items[0]->rating->itemuserid)) && has_capability('moodle/rating:viewany',$context)
-        && (empty($pluginpermissionsarray) or $pluginpermissionsarray['viewany']))) {
+if ($firstrating->user_can_view_aggregate()) {
     $result->aggregate = $aggregatetoreturn;
-    $result->count = $items[0]->rating->count;
+    $result->count = $firstrating->count;
     $result->itemid = $itemid;
 }
 
index a7284c5..4436b10 100644 (file)
@@ -61,15 +61,15 @@ class repository_webdav extends repository {
     public function check_login() {
         return true;
     }
-    public function get_file($path, $title) {
+    public function get_file($url, $title) {
         global $CFG;
-        $path = urldecode($path);
+        $url = urldecode($url);
         $path = $this->prepare_file($title);
         $buffer = '';
         if (!$this->dav->open()) {
             return false;
         }
-        $this->dav->get($path, $buffer);
+        $this->dav->get($url, $buffer);
         $fp = fopen($path, 'wb');
         fwrite($fp, $buffer);
         return array('path'=>$path);
diff --git a/theme/afterburner/config.php b/theme/afterburner/config.php
new file mode 100644 (file)
index 0000000..47ef877
--- /dev/null
@@ -0,0 +1,133 @@
+<?php
+
+$THEME->name = 'afterburner';
+
+$THEME->parents = array('base');
+
+$THEME->sheets = array(
+    'afterburner_layout',   /** Must come first: Page layout **/
+    'afterburner_styles',   /** Must come second: default styles **/
+    'afterburner_menu',
+    'afterburner_blocks',
+    'afterburner_mod',
+    'afterburner_calendar',
+    'afterburner_dock',
+    'rtl'
+);
+
+$THEME->parents_exclude_sheets = array(
+    'base'=>array(
+        'pagelayout',
+        'dock'
+    ),
+);
+$THEME->editor_sheets = array('editor');
+
+$THEME->layouts = array(
+    // Most backwards compatible layout without the blocks - this is the layout used by default
+    'base' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+    ),
+    // Standard layout with blocks, this is recommended for most pages with default information
+    'standard' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre', 'side-post'),
+        'defaultregion' => 'side-post',
+    ),
+    // Main course page
+    'course' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre', 'side-post'),
+        'defaultregion' => 'side-post',
+        'options' => array('langmenu'=>true),
+    ),
+    'coursecategory' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre', 'side-post'),
+        'defaultregion' => 'side-post',
+    ),
+    // part of course, typical for modules - default page layout if $cm specified in require_login()
+    'incourse' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre', 'side-post'),
+        'defaultregion' => 'side-post',
+    ),
+    // The site home page.
+    'frontpage' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre', 'side-post'),
+        'defaultregion' => 'side-post',
+    ),
+    // Server administration scripts.
+    'admin' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre'),
+        'defaultregion' => 'side-pre',
+    ),
+    // My dashboard page
+    'mydashboard' => array(
+        'file' => 'default.php',
+        'regions' => array('side-post'),
+        'defaultregion' => 'side-post',
+        'options' => array('langmenu'=>true),
+    ),
+    // My public page
+    'mypublic' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre'),
+        'defaultregion' => 'side-pre',
+    ),
+    'login' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+        'options' => array('langmenu'=>true),
+    ),
+
+    // Pages that appear in pop-up windows - no navigation, no blocks, no header.
+    'popup' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+        'options' => array('nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true, 'nologininfo'=>true),
+    ),
+    // No blocks and minimal footer - used for legacy frame layouts only!
+    'frametop' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+        'options' => array('nofooter'=>true),
+    ),
+    // Embedded pages, like iframe/object embeded in moodleform - it needs as much space as possible
+    'embedded' => array(
+        'file' => 'embedded.php',
+        'regions' => array()
+    ),
+    // Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
+    // This must not have any blocks, and it is good idea if it does not have links to
+    // other places - for example there should not be a home link in the footer...
+    'maintenance' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+        'options' => array('noblocks'=>true, 'nofooter'=>true, 'nonavbar'=>true, 'nocustommenu'=>true),
+    ),
+    // Should display the content and basic headers only.
+    'print' => array(
+        'file' => 'default.php',
+        'regions' => array(),
+        'options' => array('noblocks'=>true, 'nofooter'=>true, 'nonavbar'=>false, 'nocustommenu'=>true),
+    ),
+    // The pagelayout used when a redirection is occuring.
+    'redirect' => array(
+        'file' => 'embedded.php',
+        'regions' => array()
+    ),
+    // The pagelayout used for reports
+    'report' => array(
+        'file' => 'default.php',
+        'regions' => array('side-pre'),
+        'defaultregion' => 'side-pre',
+    ),
+);
+
+$THEME->enable_dock = true;
+
+$THEME->rendererfactory = 'theme_overridden_renderer_factory';
\ No newline at end of file
diff --git a/theme/afterburner/lang/en/theme_afterburner.php b/theme/afterburner/lang/en/theme_afterburner.php
new file mode 100644 (file)
index 0000000..48d12c4
--- /dev/null
@@ -0,0 +1,55 @@
+<?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/>.
+
+/**
+ * Strings for component 'theme_afterburner', language 'en'
+ *
+ * @package   theme_afterburner
+ * @copyright 2011
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['pluginname'] = 'Afterburner';
+$string['region-side-post'] = 'Right';
+$string['region-side-pre'] = 'Left';
+$string['choosereadme'] = '
+<div class="clearfix">
+ <div class="theme_screenshot">
+
+  <h2>Afterburner</h2>
+  <img src="afterburner/pix/screenshot.jpg" />
+
+  <h3>Theme Discussion Forum:</h3>
+  <p><a href="http://moodle.org/mod/forum/view.php?id=46">http://moodle.org/mod/forum/view.php?id=46</a></p>
+
+  <h3>Theme Credits</h3>
+  <p><a href="http://docs.moodle.org/en/Theme_credits">http://docs.moodle.org/en/Theme_credits</a></p>
+
+  <h3>Theme Documentation:</h3>
+  <p><a href="http://docs.moodle.org/en/Themes">http://docs.moodle.org/en/Themes</a></p><h3>Report a bug:</h3><p><a href="http://tracker.moodle.org">http://tracker.moodle.org</a></p>
+ </div>
+ <div class="theme_description">
+  <h2>About</h2>
+  <p>Afterburner is a three-column, fluid-width theme coded for Moodle 2.0. It makes use of custom menus that appear below the site title on every page. An added function, which allows users to login and logout, has now been integrated into the custom menu for this theme.</p>
+  <h2>Parents</h2>
+  <p>This theme is built on Base, a parent theme included in the Moodle core. If you wish to modify aspects of this theme, beyond the settings offered, we advise creating a new theme using this theme and Base theme as parent themes, so any updates to these parent themes, in the core, will find their way into your new theme.</p>
+  <h2>Credits</h2>
+  <p>This design was originally created for Moodle 1.9 by Patrick Malley of NewSchool Learning (www.newschoollearning.com)from an original design by Rocket Themes (www.rockettheme.com) before being ported to Moodle 2.0 by Mary Evans of NewSchool Learning (contact@newschoollearning.com).</p>
+  <h2>License</h2>
+  <p>This, and all other themes included in the Moodle core, are licensed under the <a href="http://www.gnu.org/licenses/gpl.html">GNU General Public License</a>.</p>
+ </div>
+</div>';
diff --git a/theme/afterburner/layout/default.php b/theme/afterburner/layout/default.php
new file mode 100644 (file)
index 0000000..72ce60b
--- /dev/null
@@ -0,0 +1,128 @@
+<?php
+
+$hasheading = ($PAGE->heading);
+$hasnavbar = (empty($PAGE->layout_options['nonavbar']) && $PAGE->has_navbar());
+$hasfooter = (empty($PAGE->layout_options['nofooter']));
+$hassidepre = (empty($PAGE->layout_options['noblocks']) && $PAGE->blocks->region_has_content('side-pre', $OUTPUT));
+$hassidepost = (empty($PAGE->layout_options['noblocks']) && $PAGE->blocks->region_has_content('side-post', $OUTPUT));
+$haslogininfo = (empty($PAGE->layout_options['nologininfo']));
+
+$showsidepre = ($hassidepre && !$PAGE->blocks->region_completely_docked('side-pre', $OUTPUT));
+$showsidepost = ($hassidepost && !$PAGE->blocks->region_completely_docked('side-post', $OUTPUT));
+
+$custommenu = $OUTPUT->custom_menu();
+$hascustommenu = (empty($PAGE->layout_options['nocustommenu']) && !empty($custommenu));
+
+$bodyclasses = array();
+if ($showsidepre && !$showsidepost) {
+    $bodyclasses[] = 'side-pre-only';
+} else if ($showsidepost && !$showsidepre) {
+    $bodyclasses[] = 'side-post-only';
+} else if (!$showsidepost && !$showsidepre) {
+    $bodyclasses[] = 'content-only';
+}
+if ($hascustommenu) {
+    $bodyclasses[] = 'has_custom_menu';
+}
+
+echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes() ?>>
+<head>
+    <title><?php echo $PAGE->title ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->pix_url('favicon', 'theme')?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+</head>
+
+<body id="<?php p($PAGE->bodyid) ?>" class="<?php p($PAGE->bodyclasses.' '.join(' ', $bodyclasses)) ?>">
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+<div id="page-wrapper">
+  <div id="page">
+   <?php if ($hasheading || $hasnavbar) { ?>
+    <div id="page-header">
+        <?php if ($hasheading) { ?>
+         <div id="logo">
+         </div>
+         <div class="headermenu"><?php
+            if ($haslogininfo) {
+                echo $OUTPUT->login_info();
+            }
+            if (!empty($PAGE->layout_options['langmenu'])) {
+                echo $OUTPUT->lang_menu();
+            }
+            echo $PAGE->headingmenu
+            ?></div>
+        <?php } ?>
+    </div>
+<?php } ?>
+<!-- END OF HEADER -->
+<!-- START CUSTOMMENU AND NAVBAR -->
+    <div id="navcontainer">
+        <?php if ($hascustommenu) { ?>
+                <div id="custommenu" class="javascript-disabled"><?php echo $custommenu; ?></div>
+        <?php } ?>
+
+    </div>
+
+        <?php if ($hasnavbar) { ?>
+            <div class="navbar clearfix">
+                <div class="breadcrumb"><?php echo $OUTPUT->navbar(); ?></div>
+                <div class="navbutton"> <?php echo $PAGE->button; ?></div>
+            </div>
+        <?php } ?>
+
+<!-- END OF CUSTOMMENU AND NAVBAR -->
+    <div id="page-content">
+       <div id="region-main-box">
+           <div id="region-post-box">
+              <div id="region-main-wrap">
+                 <div id="region-main-pad">
+                   <div id="region-main">
+                     <div class="region-content">
+                            <?php echo core_renderer::MAIN_CONTENT_TOKEN ?>
+                     </div>
+                   </div>
+                 </div>
+               </div>
+
+                <?php if ($hassidepre) { ?>
+                <div id="region-pre" class="block-region">
+                   <div class="region-content">
+                        <?php echo $OUTPUT->blocks_for_region('side-pre') ?>
+                   </div>
+                </div>
+                <?php } ?>
+
+                <?php if ($hassidepost) { ?>
+                <div id="region-post" class="block-region">
+                   <div class="region-content">
+                        <?php echo $OUTPUT->blocks_for_region('side-post') ?>
+                   </div>
+                </div>
+                <?php } ?>
+            </div>
+        </div>
+    </div>
+
+    <!-- START OF FOOTER -->
+    <?php if ($hasfooter) { ?>
+    <div id="page-footer" class="clearfix">
+
+        <div class="footer-left">
+            <a href="http://moodle.org" title="Moodle">
+                <img src="<?php echo $OUTPUT->pix_url('footer/moodle-logo','theme')?>" alt="Moodle logo" />
+            </a>
+        </div>
+
+        <div class="footer-right">
+            <?php echo $OUTPUT->login_info();?>
+        </div>
+
+        <?php echo $OUTPUT->standard_footer_html(); ?>
+    </div>
+    <?php } ?>
+    <div class="clearfix"></div>
+</div>
+</div>
+<?php echo $OUTPUT->standard_end_of_body_html() ?>
+</body>
+</html>
\ No newline at end of file
diff --git a/theme/afterburner/layout/embedded.php b/theme/afterburner/layout/embedded.php
new file mode 100644 (file)
index 0000000..a5dd741
--- /dev/null
@@ -0,0 +1,23 @@
+<?php echo $OUTPUT->doctype() ?>
+<html <?php echo $OUTPUT->htmlattributes() ?>>
+<head>
+    <title><?php echo $PAGE->title ?></title>
+    <link rel="shortcut icon" href="<?php echo $OUTPUT->pix_url('favicon', 'theme')?>" />
+    <?php echo $OUTPUT->standard_head_html() ?>
+</head>
+<body id="<?php p($PAGE->bodyid) ?>" class="<?php p($PAGE->bodyclasses) ?>">
+<?php echo $OUTPUT->standard_top_of_body_html() ?>
+
+<div id="page">
+
+<!-- END OF HEADER -->
+
+    <div id="content" class="clearfix">
+        <?php echo core_renderer::MAIN_CONTENT_TOKEN ?>
+    </div>
+
+<!-- START OF FOOTER -->
+</div>
+<?php echo $OUTPUT->standard_end_of_body_html() ?>
+</body>
+</html>
\ No newline at end of file
diff --git a/theme/afterburner/pix/core/bg.png b/theme/afterburner/pix/core/bg.png
new file mode 100644 (file)
index 0000000..1a45ec4
Binary files /dev/null and b/theme/afterburner/pix/core/bg.png differ
diff --git a/theme/afterburner/pix/core/bground.jpg b/theme/afterburner/pix/core/bground.jpg
new file mode 100644 (file)
index 0000000..ae9be35
Binary files /dev/null and b/theme/afterburner/pix/core/bground.jpg differ
diff --git a/theme/afterburner/pix/core/h2grad.jpg b/theme/afterburner/pix/core/h2grad.jpg
new file mode 100644 (file)
index 0000000..939f19b
Binary files /dev/null and b/theme/afterburner/pix/core/h2grad.jpg differ
diff --git a/theme/afterburner/pix/favicon.ico b/theme/afterburner/pix/favicon.ico
new file mode 100644 (file)
index 0000000..5a7a36a
Binary files /dev/null and b/theme/afterburner/pix/favicon.ico differ
diff --git a/theme/afterburner/pix/footer/moodle-logo.png b/theme/afterburner/pix/footer/moodle-logo.png
new file mode 100644 (file)
index 0000000..874d63b
Binary files /dev/null and b/theme/afterburner/pix/footer/moodle-logo.png differ
diff --git a/theme/afterburner/pix/forum/gradient.png b/theme/afterburner/pix/forum/gradient.png
new file mode 100644 (file)
index 0000000..d9d25c3
Binary files /dev/null and b/theme/afterburner/pix/forum/gradient.png differ
diff --git a/theme/afterburner/pix/images/light3.png b/theme/afterburner/pix/images/light3.png
new file mode 100644 (file)
index 0000000..0162513
Binary files /dev/null and b/theme/afterburner/pix/images/light3.png differ
diff --git a/theme/afterburner/pix/menu/ab-arrowover.png b/theme/afterburner/pix/menu/ab-arrowover.png
new file mode 100644 (file)
index 0000000..6e452f6
Binary files /dev/null and b/theme/afterburner/pix/menu/ab-arrowover.png differ
diff --git a/theme/afterburner/pix/menu/nav-arrow-right.png b/theme/afterburner/pix/menu/nav-arrow-right.png
new file mode 100644 (file)
index 0000000..b07b784
Binary files /dev/null and b/theme/afterburner/pix/menu/nav-arrow-right.png differ
diff --git a/theme/afterburner/pix/screenshot.jpg b/theme/afterburner/pix/screenshot.jpg
new file mode 100644 (file)
index 0000000..2d5d2ad
Binary files /dev/null and b/theme/afterburner/pix/screenshot.jpg differ
diff --git a/theme/afterburner/pix/sideblocks/sidegrad.jpg b/theme/afterburner/pix/sideblocks/sidegrad.jpg
new file mode 100644 (file)
index 0000000..939f19b
Binary files /dev/null and b/theme/afterburner/pix/sideblocks/sidegrad.jpg differ
diff --git a/theme/afterburner/pix/tab/left.gif b/theme/afterburner/pix/tab/left.gif
new file mode 100644 (file)
index 0000000..48b6628
Binary files /dev/null and b/theme/afterburner/pix/tab/left.gif differ
diff --git a/theme/afterburner/pix/tab/left_active.gif b/theme/afterburner/pix/tab/left_active.gif
new file mode 100644 (file)
index 0000000..bd72d63
Binary files /dev/null and b/theme/afterburner/pix/tab/left_active.gif differ
diff --git a/theme/afterburner/pix/tab/left_active_hover.gif b/theme/afterburner/pix/tab/left_active_hover.gif
new file mode 100644 (file)
index 0000000..f1af440
Binary files /dev/null and b/theme/afterburner/pix/tab/left_active_hover.gif differ
diff --git a/theme/afterburner/pix/tab/left_hover.gif b/theme/afterburner/pix/tab/left_hover