Merge branch 'MDL-26594' of git://github.com/nebgor/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Sun, 13 Mar 2011 23:54:36 +0000 (00:54 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Sun, 13 Mar 2011 23:54:36 +0000 (00:54 +0100)
70 files changed:
admin/cli/install.php
admin/cli/upgrade.php
admin/index.php
admin/user.php
backup/moodle2/backup_plan_builder.class.php
backup/moodle2/backup_stepslib.php
backup/moodle2/backup_theme_plugin.class.php [new file with mode: 0644]
backup/moodle2/restore_plan_builder.class.php
backup/moodle2/restore_plugin.class.php
backup/moodle2/restore_stepslib.php
backup/moodle2/restore_subplugin.class.php
backup/moodle2/restore_theme_plugin.class.php [new file with mode: 0644]
backup/util/dbops/backup_plan_dbops.class.php
backup/util/plan/restore_structure_step.class.php
backup/util/ui/renderer.php
backup/util/ui/restore_ui_components.php
blocks/completionstatus/block_completionstatus.php
blocks/selfcompletion/block_selfcompletion.php
blocks/selfcompletion/lang/en/block_selfcompletion.php
course/modedit.php
course/report/log/lang/en/coursereport_log.php
course/report/log/lib.php
install/lang/de_kids/langconfig.php [new file with mode: 0644]
install/lang/pl/admin.php
install/lang/pl/install.php
lang/en/admin.php
lang/en/moodle.php
lib/dml/simpletest/testdml.php
lib/dml/sqlsrv_native_moodle_database.php
lib/enrollib.php
lib/excellib.class.php
lib/moodlelib.php
lib/setuplib.php
lib/simpletest/testweblib.php
lib/weblib.php
message/discussion.php
message/index.php
message/lib.php
message/search.html
message/search_advanced.html
mod/quiz/attemptlib.php
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/mod_form.php
mod/quiz/review.php
mod/quiz/startattempt.php
mod/scorm/aicc.php
mod/workshop/db/access.php
mod/workshop/eval/best/lib.php
mod/workshop/form/accumulative/db/upgradelib.php
mod/workshop/form/comments/db/upgradelib.php
mod/workshop/form/numerrors/db/upgradelib.php
mod/workshop/form/numerrors/lib.php
mod/workshop/form/rubric/db/upgradelib.php
mod/workshop/lang/en/workshop.php
mod/workshop/locallib.php
mod/workshop/styles.css
mod/workshop/submission.php
mod/workshop/version.php
mod/workshop/view.php
question/type/essay/questiontype.php
question/type/multianswer/questiontype.php
question/type/numerical/display.html
question/type/numerical/questiontype.php
question/type/questiontype.php
question/type/random/questiontype.php
question/type/shortanswer/questiontype.php
theme/standard/style/admin.css
user/profile.php
version.php

index 3378948..0acea3d 100644 (file)
@@ -70,6 +70,8 @@ Options:
                       problem encountered.
 --agree-license       Indicates agreement with software license,
                       required in non-interactive mode.
+--allow-unstable      Install even if the version is not marked as stable yet,
+                      required in non-interactive mode.
 -h, --help            Print out this help
 
 Example:
@@ -136,9 +138,6 @@ $CFG->early_install_lang   = true;
 $parts = explode('/', str_replace('\\', '/', dirname(dirname(__FILE__))));
 $CFG->admin                = array_pop($parts);
 
-require($CFG->dirroot.'/version.php');
-$CFG->target_release = $release;
-
 //point pear include path to moodles lib/pear so that includes and requires will search there for files before anywhere else
 //the problem is that we need specific version of quickforms and hacked excel files :-(
 ini_set('include_path', $CFG->libdir.'/pear' . PATH_SEPARATOR . ini_get('include_path'));
@@ -154,6 +153,9 @@ require_once($CFG->libdir.'/deprecatedlib.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/componentlib.class.php');
 
+require($CFG->dirroot.'/version.php');
+$CFG->target_release = $release;
+
 //Database types
 $databases = array('mysqli' => moodle_database::get_driver_instance('mysqli', 'native'),
                    'pgsql'  => moodle_database::get_driver_instance('pgsql',  'native'),
@@ -193,6 +195,7 @@ list($options, $unrecognized) = cli_get_params(
         'adminpass'         => '',
         'non-interactive'   => false,
         'agree-license'     => false,
+        'allow-unstable'    => false,
         'help'              => false
     ),
     array(
@@ -402,6 +405,28 @@ $CFG->langotherroot      = $CFG->dataroot.'/lang';
 $CFG->langlocalroot      = $CFG->dataroot.'/lang';
 get_string_manager(true);
 
+// make sure we are installing stable release or require a confirmation
+if (isset($maturity)) {
+    if (($maturity < MATURITY_STABLE) and !$options['allow-unstable']) {
+        $maturitylevel = get_string('maturity'.$maturity, 'admin');
+
+        if ($interactive) {
+            cli_separator();
+            cli_heading(get_string('notice'));
+            echo get_string('maturitycorewarning', 'admin', $maturitylevel) . PHP_EOL;
+            echo get_string('morehelp') . ': ' . get_docs_url('admin/versions') . PHP_EOL;
+            echo get_string('continue') . PHP_LOL;
+            $prompt = get_string('cliyesnoprompt', 'admin');
+            $input = cli_input($prompt, '', array(get_string('clianswerno', 'admin'), get_string('cliansweryes', 'admin')));
+            if ($input == get_string('clianswerno', 'admin')) {
+                exit(1);
+            }
+        } else {
+            cli_error(get_string('maturitycorewarning', 'admin'));
+        }
+    }
+}
+
 // ask for db type - show only drivers available
 if ($interactive) {
     $options['dbtype'] = strtolower($options['dbtype']);
index 142f2d3..7e145f2 100644 (file)
@@ -40,8 +40,16 @@ require_once($CFG->libdir.'/environmentlib.php');
 
 
 // now get cli options
-list($options, $unrecognized) = cli_get_params(array('non-interactive'=>false, 'help'=>false),
-                                               array('h'=>'help'));
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'non-interactive'   => false,
+        'allow-unstable'    => false,
+        'help'              => false
+    ),
+    array(
+        'h' => 'help'
+    )
+);
 
 $interactive = empty($options['non-interactive']);
 
@@ -59,6 +67,8 @@ Site defaults may be changed via local/defaults.php.
 
 Options:
 --non-interactive     No interactive questions or confirmations
+--allow-unstable      Upgrade even if the version is not marked as stable yet,
+                      required in non-interactive mode.
 -h, --help            Print out this help
 
 Example:
@@ -73,13 +83,14 @@ if (empty($CFG->version)) {
     cli_error(get_string('missingconfigversion', 'debug'));
 }
 
-require("$CFG->dirroot/version.php");       // defines $version and $release
+require("$CFG->dirroot/version.php");       // defines $version, $release and $maturity
 $CFG->target_release = $release;            // used during installation and upgrades
 
 if ($version < $CFG->version) {
     cli_error(get_string('downgradedcore', 'error'));
 }
 
+$oldversion = "$CFG->release ($CFG->version)";
 $newversion = "$release ($version)";
 
 // test environment first
@@ -93,6 +104,30 @@ if (!check_moodle_environment($version, $environment_results, false, ENV_SELECT_
     exit(1);
 }
 
+if ($interactive) {
+    $a = new stdClass();
+    $a->oldversion = $oldversion;
+    $a->newversion = $newversion;
+    echo cli_heading(get_string('databasechecking', '', $a)) . PHP_EOL;
+}
+
+// make sure we are upgrading to a stable release or display a warning
+if (isset($maturity)) {
+    if (($maturity < MATURITY_STABLE) and !$options['allow-unstable']) {
+        $maturitylevel = get_string('maturity'.$maturity, 'admin');
+
+        if ($interactive) {
+            cli_separator();
+            cli_heading(get_string('notice'));
+            echo get_string('maturitycorewarning', 'admin', $maturitylevel) . PHP_EOL;
+            echo get_string('morehelp') . ': ' . get_docs_url('admin/versions') . PHP_EOL;
+            cli_separator();
+        } else {
+            cli_error(get_string('maturitycorewarning', 'admin', $maturitylevel));
+        }
+    }
+}
+
 if ($interactive) {
     echo html_to_text(get_string('upgradesure', 'admin', $newversion))."\n";
     $prompt = get_string('cliyesnoprompt', 'admin');
index 14b6f24..f40ad17 100644 (file)
@@ -86,7 +86,7 @@ if (!isset($CFG->version)) {
 
 $version = null;
 $release = null;
-require("$CFG->dirroot/version.php");       // defines $version and $release
+require("$CFG->dirroot/version.php");       // defines $version, $release and $maturity
 $CFG->target_release = $release;            // used during installation and upgrades
 
 if (!$version or !$release) {
@@ -130,14 +130,26 @@ if (!core_tables_exist()) {
     if (empty($confirmrelease)) {
         $strcurrentrelease = get_string('currentrelease');
         $PAGE->navbar->add($strcurrentrelease);
-        $PAGE->set_title($strinstallation.' - Moodle '.$CFG->target_release);
-        $PAGE->set_heading($strinstallation);
+        $PAGE->set_title($strinstallation);
+        $PAGE->set_heading($strinstallation . ' - Moodle ' . $CFG->target_release);
         $PAGE->set_cacheable(false);
         echo $OUTPUT->header();
         echo $OUTPUT->heading("Moodle $release");
+
+        if (isset($maturity)) {
+            // main version.php declares moodle code maturity
+            if ($maturity < MATURITY_STABLE) {
+                $maturitylevel = get_string('maturity'.$maturity, 'admin');
+                echo $OUTPUT->box(
+                    $OUTPUT->container(get_string('maturitycorewarning', 'admin', $maturitylevel)) .
+                    $OUTPUT->container($OUTPUT->doc_link('admin/versions', get_string('morehelp'))),
+                    'generalbox maturitywarning');
+            }
+        }
+
         $releasenoteslink = get_string('releasenoteslink', 'admin', 'http://docs.moodle.org/en/Release_Notes');
         $releasenoteslink = str_replace('target="_blank"', 'onclick="this.target=\'_blank\'"', $releasenoteslink); // extremely ugly validation hack
-        echo $OUTPUT->box($releasenoteslink, 'generalbox boxaligncenter boxwidthwide');
+        echo $OUTPUT->box($releasenoteslink, 'generalbox releasenoteslink');
 
         require_once($CFG->libdir.'/environmentlib.php');
         if (!check_moodle_environment($release, $environment_results, true, ENV_SELECT_RELEASE)) {
@@ -189,11 +201,20 @@ if ($version > $CFG->version) {  // upgrade
     $strdatabasechecking = get_string('databasechecking', '', $a);
 
     if (empty($confirmupgrade)) {
-        $PAGE->navbar->add($strdatabasechecking);
-        $PAGE->set_title($strdatabasechecking);
-        $PAGE->set_heading($stradministration);
+        $PAGE->set_title($stradministration);
+        $PAGE->set_heading($strdatabasechecking);
         $PAGE->set_cacheable(false);
         echo $OUTPUT->header();
+        if (isset($maturity)) {
+            // main version.php declares moodle code maturity
+            if ($maturity < MATURITY_STABLE) {
+                $maturitylevel = get_string('maturity'.$maturity, 'admin');
+                echo $OUTPUT->box(
+                    $OUTPUT->container(get_string('maturitycorewarning', 'admin', $maturitylevel)) .
+                    $OUTPUT->container($OUTPUT->doc_link('admin/versions', get_string('morehelp'))),
+                    'generalbox maturitywarning');
+}
+        }
         $continueurl = new moodle_url('index.php', array('confirmupgrade' => 1));
         $cancelurl = new moodle_url('index.php');
         echo $OUTPUT->confirm(get_string('upgradesure', 'admin', $a->newversion), $continueurl, $cancelurl);
index d2ad915..2827ccf 100644 (file)
         }
 
         foreach ($users as $key => $user) {
-            if (!empty($user->country)) {
+            if (isset($countries[$user->country])) {
                 $users[$key]->country = $countries[$user->country];
             }
         }
index 89c8654..4c9e876 100644 (file)
@@ -35,6 +35,7 @@ require_once($CFG->dirroot . '/backup/moodle2/backup_xml_transformer.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_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_settingslib.php');
 require_once($CFG->dirroot . '/backup/moodle2/backup_stepslib.php');
index 6b4dbf3..051a409 100644 (file)
@@ -412,6 +412,10 @@ class backup_course_structure_step extends backup_structure_step {
         // attach format plugin structure to $course element, only one allowed
         $this->add_plugin_structure('format', $course, false);
 
+        // attach theme plugin structure to $course element; multiple themes can
+        // save course data (in case of user theme, legacy theme, etc)
+        $this->add_plugin_structure('theme', $course, true);
+
         // Build the tree
 
         $course->add_child($category);
diff --git a/backup/moodle2/backup_theme_plugin.class.php b/backup/moodle2/backup_theme_plugin.class.php
new file mode 100644 (file)
index 0000000..d4a3a09
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package    moodlecore
+ * @subpackage backup-moodle2
+ * @copyright  2011 onwards The Open University
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+/**
+ * Base class for theme backup plugins.
+ *
+ * NOTE: When you back up a course, it runs backup for ALL themes - not just
+ * the currently selected one.
+ *
+ * That means that if, for example, a course was once in theme A, and theme A
+ * had some data settings, but it is then changed to theme B, the data settings
+ * will still be included in the backup and restore. With the restored course,
+ * if you ever change it back to theme A, the settings will be ready.
+ *
+ * It also means that other themes which are not the one set up for the course,
+ * but might be seen by some users (eg user themes, session themes, mnet themes)
+ * can store data.
+ *
+ * If this behaviour is not desired for a particular theme's data, the subclass
+ * can call is_current_theme('myname') to check.
+ */
+abstract class backup_theme_plugin extends backup_plugin {
+
+    /**
+     * @var string Current theme for course (may not be the same as plugin).
+     */
+    protected $coursetheme;
+
+    /**
+     * @param string $plugintype Plugin type (always 'theme')
+     * @param string $pluginname Plugin name (name of theme)
+     * @param backup_optigroup $optigroup Group that will contain this data
+     * @param backup_course_structure_step $step Backup step that this is part of
+     */
+    public function __construct($plugintype, $pluginname, $optigroup, $step) {
+
+        parent::__construct($plugintype, $pluginname, $optigroup, $step);
+
+        $this->coursetheme = backup_plan_dbops::get_theme_from_courseid(
+                    $this->task->get_courseid());
+
+    }
+
+    /**
+     * Return condition for whether this theme should be backed up (= if it
+     * is the same theme as the one used in this course). This condition has
+     * the theme used in the course. It will be compared against the name
+     * of the theme, by use of third parameter in get_plugin_element; in
+     * subclass, you should do:
+     * $plugin = $this->get_plugin_element(null, $this->get_theme_condition(), 'mytheme');
+     */
+    protected function get_theme_condition() {
+        return array('sqlparam' => $this->coursetheme);
+    }
+}
index f685d38..122b05e 100644 (file)
@@ -34,9 +34,11 @@ require_once($CFG->dirroot . '/backup/moodle2/restore_default_block_task.class.p
 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/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/restore_subplugin.class.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_settingslib.php');
 require_once($CFG->dirroot . '/backup/moodle2/restore_stepslib.php');
index 8103ada..0203987 100644 (file)
@@ -174,9 +174,12 @@ abstract class restore_plugin {
     /**
      * Return the new id of a mapping for the given itemname
      *
+     * @param string $itemname the type of item
+     * @param int $oldid the item ID from the backup
+     * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
      */
-    protected function get_mappingid($itemname, $oldid) {
-        return $this->step->get_mappingid($itemname, $oldid);
+    protected function get_mappingid($itemname, $oldid, $ifnotfound = false) {
+        return $this->step->get_mappingid($itemname, $oldid, $ifnotfound);
     }
 
     /**
index eace014..b7b28a3 100644 (file)
@@ -148,14 +148,14 @@ class restore_gradebook_structure_step extends restore_structure_step {
 
         //manual grade items store category id in categoryid
         if ($data->itemtype=='manual') {
-            $data->categoryid = $this->get_mappingid('grade_category', $data->categoryid);
+            $data->categoryid = $this->get_mappingid('grade_category', $data->categoryid, NULL);
         } //course and category grade items store their category id in iteminstance
         else if ($data->itemtype=='course' || $data->itemtype=='category') {
-            $data->iteminstance = $this->get_mappingid('grade_category', $data->iteminstance);
+            $data->iteminstance = $this->get_mappingid('grade_category', $data->iteminstance, NULL);
         }
 
-        $data->scaleid   = $this->get_mappingid('scale', $data->scaleid);
-        $data->outcomeid = $this->get_mappingid('outcome', $data->outcomeid);
+        $data->scaleid   = $this->get_mappingid('scale', $data->scaleid, NULL);
+        $data->outcomeid = $this->get_mappingid('outcome', $data->outcomeid, NULL);
 
         $data->locktime     = $this->apply_date_offset($data->locktime);
         $data->timecreated  = $this->apply_date_offset($data->timecreated);
@@ -164,23 +164,19 @@ class restore_gradebook_structure_step extends restore_structure_step {
         $coursecategory = $newitemid = null;
         //course grade item should already exist so updating instead of inserting
         if($data->itemtype=='course') {
-
             //get the ID of the already created grade item
             $gi = new stdclass();
             $gi->courseid  = $this->get_courseid();
-
             $gi->itemtype  = $data->itemtype;
-            if ($data->itemtype=='course') {
-                //need to get the id of the grade_category that was automatically created for the course
-                $category = new stdclass();
-                $category->courseid  = $this->get_courseid();
-                $category->parent  = null;
-                //course category fullname starts out as ? but may be edited
-                //$category->fullname  = '?';
-
-                $coursecategory = $DB->get_record('grade_categories', (array)$category);
-                $gi->iteminstance = $coursecategory->id;
-            }
+
+            //need to get the id of the grade_category that was automatically created for the course
+            $category = new stdclass();
+            $category->courseid  = $this->get_courseid();
+            $category->parent  = null;
+            //course category fullname starts out as ? but may be edited
+            //$category->fullname  = '?';
+            $coursecategory = $DB->get_record('grade_categories', (array)$category);
+            $gi->iteminstance = $coursecategory->id;
 
             $existinggradeitem = $DB->get_record('grade_items', (array)$gi);
             if (!empty($existinggradeitem)) {
@@ -208,8 +204,8 @@ class restore_gradebook_structure_step extends restore_structure_step {
 
         $data->itemid = $this->get_new_parentid('grade_item');
 
-        $data->userid = $this->get_mappingid('user', $data->userid);
-        $data->usermodified = $this->get_mappingid('user', $data->usermodified);
+        $data->userid = $this->get_mappingid('user', $data->userid, NULL);
+        $data->usermodified = $this->get_mappingid('user', $data->usermodified, NULL);
         $data->locktime     = $this->apply_date_offset($data->locktime);
         // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
         $data->overridden = $this->apply_date_offset($data->overridden);
@@ -337,6 +333,9 @@ class restore_gradebook_structure_step extends restore_structure_step {
             }
         }
         $rs->close();
+
+        //Restore marks items as needing update. Update everything now.
+        grade_regrade_final_grades($this->get_courseid());
     }
 }
 
@@ -1008,6 +1007,9 @@ class restore_course_structure_step extends restore_structure_step {
         // Apply for 'format' plugins optional paths at course level
         $this->add_plugin_structure('format', $course);
 
+        // Apply for 'theme' plugins optional paths at course level
+        $this->add_plugin_structure('theme', $course);
+
         return array($course, $category, $tag, $allowed_module);
     }
 
@@ -1052,8 +1054,9 @@ class restore_course_structure_step extends restore_structure_step {
         if (!array_key_exists($data->lang, $languages)) {
             $data->lang = '';
         }
+
         $themes = get_list_of_themes(); // Get themes for quick search later
-        if (!in_array($data->theme, $themes) || empty($CFG->allowcoursethemes)) {
+        if (!array_key_exists($data->theme, $themes) || empty($CFG->allowcoursethemes)) {
             $data->theme = '';
         }
 
index d22a0d9..8972e35 100644 (file)
@@ -123,9 +123,12 @@ abstract class restore_subplugin {
     /**
      * Return the new id of a mapping for the given itemname
      *
+     * @param string $itemname the type of item
+     * @param int $oldid the item ID from the backup
+     * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
      */
-    protected function get_mappingid($itemname, $oldid) {
-        return $this->step->get_mappingid($itemname, $oldid);
+    protected function get_mappingid($itemname, $oldid, $ifnotfound = false) {
+        return $this->step->get_mappingid($itemname, $oldid, $ifnotfound);
     }
 
     /**
diff --git a/backup/moodle2/restore_theme_plugin.class.php b/backup/moodle2/restore_theme_plugin.class.php
new file mode 100644 (file)
index 0000000..8493194
--- /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: theme.
+ *
+ * @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_theme_plugin extends restore_plugin {
+    // Use default parent behaviour
+}
index fe5d941..3d620f5 100644 (file)
@@ -128,6 +128,45 @@ abstract class backup_plan_dbops extends backup_dbops {
         return $DB->get_field('course', 'format', array('id' => $courseid));
     }
 
+    /**
+     * Given a course id, returns its theme. This can either be the course
+     * theme or (if not specified in course) the category, site theme.
+     *
+     * User, session, and inherited-from-mnet themes cannot have backed-up
+     * per course data. This is course-related data so it must be in a course
+     * theme specified as part of the course structure
+     * @param int $courseid
+     * @return string Name of course theme
+     * @see moodle_page#resolve_theme()
+     */
+    public static function get_theme_from_courseid($courseid) {
+        global $DB, $CFG;
+
+        // Course theme first
+        if (!empty($CFG->allowcoursethemes)) {
+            $theme = $DB->get_field('course', 'theme', array('id' => $courseid));
+            if ($theme) {
+                return $theme;
+            }
+        }
+
+        // Category themes in reverse order
+        if (!empty($CFG->allowcategorythemes)) {
+            $catid = $DB->get_field('course', 'category', array('id' => $courseid));
+            while($catid) {
+                $category = $DB->get_record('course_categories', array('id'=>$catid),
+                        'theme,parent', MUST_EXIST);
+                if ($category->theme) {
+                    return $category->theme;
+                }
+                $catid = $category->parent;
+            }
+        }
+
+        // Finally use site theme
+        return $CFG->theme;
+    }
+
     /**
      * Return the wwwroot of the $CFG->mnet_localhost_id host
      * caching it along the request
index 970605e..ae648bc 100644 (file)
@@ -197,10 +197,13 @@ abstract class restore_structure_step extends restore_step {
     /**
      * Return the new id of a mapping for the given itemname
      *
+     * @param string $itemname the type of item
+     * @param int $oldid the item ID from the backup
+     * @param mixed $ifnotfound what to return if $oldid wasnt found. Defaults to false
      */
-    public function get_mappingid($itemname, $oldid) {
+    public function get_mappingid($itemname, $oldid, $ifnotfound = false) {
         $mapping = $this->get_mapping($itemname, $oldid);
-        return $mapping ? $mapping->newitemid : false;
+        return $mapping ? $mapping->newitemid : $ifnotfound;
     }
 
     /**
index f8ab57b..e07bc0b 100644 (file)
@@ -181,7 +181,7 @@ class core_backup_renderer extends plugin_renderer_base {
         $hasrestoreoption = false;
 
         $html  = html_writer::start_tag('div', array('class'=>'backup-course-selector backup-restore'));
-        if (!empty($categories) && ($categories->get_count() > 0 || $categories->get_search())) {
+        if ($details->type == backup::TYPE_1COURSE && !empty($categories) && ($categories->get_count() > 0 || $categories->get_search())) {
             // New course
             $hasrestoreoption = true;
             $html .= $form;
@@ -194,7 +194,7 @@ class core_backup_renderer extends plugin_renderer_base {
             $html .= html_writer::end_tag('form');
         }
 
-        if (!empty($currentcourse)) {
+        if ($details->type == backup::TYPE_1COURSE && !empty($currentcourse)) {
             // Current course
             $hasrestoreoption = true;
             $html .= $form;
@@ -214,9 +214,17 @@ class core_backup_renderer extends plugin_renderer_base {
             $html .= $form;
             $html .= html_writer::start_tag('div', array('class'=>'bcs-existing-course backup-section'));
             $html .= $this->output->heading(get_string('restoretoexistingcourse', 'backup'), 2, array('class'=>'header'));
-            $html .= $this->backup_detail_input(get_string('restoretoexistingcourseadding', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_ADDING, array('checked'=>'checked'));
-            $html .= $this->backup_detail_input(get_string('restoretoexistingcoursedeleting', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_DELETING);
-            $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
+            if ($details->type == backup::TYPE_1COURSE) {
+                $html .= $this->backup_detail_input(get_string('restoretoexistingcourseadding', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_ADDING, array('checked'=>'checked'));
+                $html .= $this->backup_detail_input(get_string('restoretoexistingcoursedeleting', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_DELETING);
+                $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
+            } else {
+                // We only allow restore adding to existing for now. Enforce it here.
+                $html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'target', 'value'=>backup::TARGET_EXISTING_ADDING));
+                $courses->invalidate_results(); // Clean list of courses
+                $courses->set_include_currentcourse(); // Show current course in the list
+                $html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
+            }
             $html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('continue'))));
             $html .= html_writer::end_tag('div');
             $html .= html_writer::end_tag('form');
index 5243c1d..056f7a1 100644 (file)
@@ -132,6 +132,7 @@ abstract class restore_search_base implements renderable {
      */
     final public function invalidate_results() {
         $this->results = null;
+        $this->totalcount = null;
     }
     /**
      * Adds a required capability which all results will be checked against
@@ -217,6 +218,7 @@ class restore_course_search extends restore_search_base {
     static $VAR_SEARCH = 'search';
 
     protected $currentcourseid = null;
+    protected $includecurrentcourse;
 
     /**
      * @param array $config
@@ -226,6 +228,7 @@ class restore_course_search extends restore_search_base {
         parent::__construct($config);
         $this->require_capability('moodle/restore:restorecourse');
         $this->currentcourseid = $currentcouseid;
+        $this->includecurrentcourse = false;
     }
     /**
      *
@@ -246,7 +249,7 @@ class restore_course_search extends restore_search_base {
         $where      = " WHERE (".$DB->sql_like('c.fullname', ':fullnamesearch', false)." OR ".$DB->sql_like('c.shortname', ':shortnamesearch', false).") AND c.id <> :siteid";
         $orderby    = " ORDER BY c.sortorder";
 
-        if ($this->currentcourseid !== null) {
+        if ($this->currentcourseid !== null && !$this->includecurrentcourse) {
             $where .= " AND c.id <> :currentcourseid";
             $params['currentcourseid'] = $this->currentcourseid;
         }
@@ -260,6 +263,9 @@ class restore_course_search extends restore_search_base {
     public function get_varsearch() {
         return self::$VAR_SEARCH;
     }
+    public function set_include_currentcourse() {
+        $this->includecurrentcourse = true;
+    }
 }
 
 /**
index 7a8e17b..0d7d104 100644 (file)
@@ -49,6 +49,7 @@ class block_completionstatus extends block_base {
 
         // Don't display if completion isn't enabled!
         if (!$this->page->course->enablecompletion) {
+            $this->content->text = get_string('completionnotenabled', 'block_completionstatus');
             return $this->content;
         }
 
@@ -58,6 +59,7 @@ class block_completionstatus extends block_base {
 
         // Check if this course has any criteria
         if (empty($completions)) {
+            $this->content->text = get_string('nocriteria', 'block_completionstatus');
             return $this->content;
         }
 
@@ -157,7 +159,7 @@ class block_completionstatus extends block_base {
 
         // Is course complete?
         $coursecomplete = $info->is_course_complete($USER->id);
-               
+
         // Load course completion
         $params = array(
             'userid' => $USER->id,
index f08eef5..a5a7da9 100644 (file)
@@ -54,6 +54,7 @@ class block_selfcompletion extends block_base {
 
         // Don't display if completion isn't enabled!
         if (!$this->page->course->enablecompletion) {
+            $this->content->text = get_string('completionnotenabled', 'block_selfcompletion');
             return $this->content;
         }
 
@@ -68,6 +69,7 @@ class block_selfcompletion extends block_base {
 
         // Check if self completion is one of this course's criteria
         if (empty($completion)) {
+            $this->content->text = get_string('selfcompletionnotenabled', 'block_selfcompletion');
             return $this->content;
         }
 
index 7b066a1..45775d8 100644 (file)
@@ -26,3 +26,5 @@
 $string['selfcompletion'] = 'Self completion';
 $string['pluginname'] = 'Self completion';
 $string['completecourse'] = 'Complete course';
+$string['completionnotenabled'] = 'Course completion is not enabled';
+$string['selfcompletionnotenabled'] = 'The self completion criteria has not been enabled for this course';
index 4b5c615..4d9cb68 100644 (file)
@@ -325,7 +325,7 @@ if ($mform->is_cancelled()) {
         }
 
         if (!$updateinstancefunction($fromform, $mform)) {
-            print_error('cannotupdatemod', '', 'view.php?id=$course->id', $fromform->modulename);
+            print_error('cannotupdatemod', '', "view.php?id={$course->id}#section-{$cw->section}", $fromform->modulename);
         }
 
         // make sure visibility is set correctly (in particular in calendar)
@@ -416,9 +416,9 @@ if ($mform->is_cancelled()) {
             $DB->delete_records('course_modules', array('id'=>$fromform->coursemodule));
 
             if (!is_number($returnfromfunc)) {
-                print_error('invalidfunction', '', 'view.php?id=$course->id');
+                print_error('invalidfunction', '', "view.php?id={$course->id}#section-{$cw->section}");
             } else {
-                print_error('cannotaddnewmodule', '', "view.php?id=$course->id", $fromform->modulename);
+                print_error('cannotaddnewmodule', '', "view.php?id={$course->id}#section-{$cw->section}", $fromform->modulename);
             }
         }
 
@@ -568,7 +568,7 @@ if ($mform->is_cancelled()) {
     if (isset($fromform->submitbutton)) {
         redirect("$CFG->wwwroot/mod/$module->name/view.php?id=$fromform->coursemodule");
     } else {
-        redirect("$CFG->wwwroot/course/view.php?id=$course->id");
+        redirect("$CFG->wwwroot/course/view.php?id={$course->id}#section-{$cw->section}");
     }
     exit;
 
index 3113eea..dc37408 100644 (file)
@@ -27,4 +27,4 @@ $string['loglive'] = 'Live logs';
 $string['log:view'] = 'View course logs';
 $string['log:viewlive'] = 'View live logs';
 $string['log:viewtoday'] = 'View today\'s logs';
-$string['pluginname'] = 'Live logs';
+$string['pluginname'] = 'Logs';
index e12de53..96cbdc7 100644 (file)
@@ -534,6 +534,6 @@ function log_report_extend_navigation($navigation, $course, $context) {
     global $CFG, $OUTPUT;
     if (has_capability('coursereport/log:view', $context)) {
         $url = new moodle_url('/course/report/log/index.php', array('id'=>$course->id));
-        $navigation->add(get_string('log:view', 'coursereport_log'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
+        $navigation->add(get_string('pluginname', 'coursereport_log'), $url, navigation_node::TYPE_SETTING, null, null, new pix_icon('i/report', ''));
     }
 }
diff --git a/install/lang/de_kids/langconfig.php b/install/lang/de_kids/langconfig.php
new file mode 100644 (file)
index 0000000..1ebd9f7
--- /dev/null
@@ -0,0 +1,33 @@
+<?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/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/en/Development:Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['parentlanguage'] = 'de_du';
+$string['thisdirection'] = '';
+$string['thislanguage'] = 'Deutsch - Kids';
index 1f5ef1c..f3d7407 100644 (file)
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 't';
 $string['cliincorrectvalueerror'] = 'Błąd, niepoprawna wartość "{$a->value}" dla "{$a->option}"';
 $string['cliincorrectvalueretry'] = 'Nieprawidłowa wartość, spróbuj ponownie';
 $string['clitypevalue'] = 'typ wartości';
 $string['clitypevaluedefault'] = 'typ wartości, naciśnij Enter, aby użyć wartości domyślnej ({$a})';
+$string['cliunknowoption'] = 'Nieznana opcja:
+  {$a}
+Proszę użyć pomocy.';
 $string['cliyesnoprompt'] = 'wpisz y (czyli tak) lub n (czyli nie)';
 $string['environmentrequireinstall'] = 'jest niezbędnę, żeby było zainstalowane/włączone';
 $string['environmentrequireversion'] = 'wersja {$a->needed} jest niezbędna a ty używasz wersji {$a->current}';
index 5e2ea90..c41f37d 100644 (file)
@@ -32,12 +32,14 @@ $string['admindirname'] = 'Katalog admin';
 $string['availablelangs'] = 'Dostępne paczki językowe';
 $string['chooselanguagehead'] = 'Wybierz język';
 $string['chooselanguagesub'] = 'Proszę wybrać język TYLKO do instalacji. Stronę i języki dla użytkowników będzie można wybrać na następnym ekranie.';
+$string['clialreadyinstalled'] = 'Plik config.php już istnieje, użyj admin/cli/upgrade.php jeśli chcesz uaktualnić witrynę.';
 $string['databasehost'] = 'Host bazy danych';
 $string['databasename'] = 'Nazwa bazy danych';
 $string['dataroot'] = 'Katalog z danymi';
 $string['dbprefix'] = 'Prefiks tabel';
 $string['dirroot'] = 'Katalog Moodle';
 $string['environmenthead'] = 'Sprawdzam środowisko (ustawienia) ...';
+$string['errorsinenvironment'] = 'Kontrola środowiska zakończona niepowodzeniem!';
 $string['installation'] = 'Instalacja';
 $string['langdownloaderror'] = 'Niestety język "{$a}" nie może zostać pobrany. Proces instalacji będzie kontynuowany w języku angielskim.';
 $string['memorylimithelp'] = '<p>Limit pamięci PHP dla Twojego serwera jest ustawiony obecnie na {$a}.</p>
@@ -54,7 +56,13 @@ Pozwoli to Moodle ustawić samoczynnie limit pamięci.</li>
 <blockquote><div>php_value memory_limit 40M</div></blockquote>
 <p>Jednakże na niektórych serwerach będzie uniemożliwiało to poprawne działanie <b>wszystkich</b> stron PHP (ujrzysz błędy na wyświetlanych stronach), wtedy będziesz musiał usunąć plik .htaccess.</p></li>
 </ol>';
+$string['paths'] = 'Ścieżki';
+$string['pathserrcreatedataroot'] = 'Katalog danych ({$a->dataroot}) nie może zostać utworzony przez instalator.';
+$string['pathshead'] = 'Potwierdź ścieżki';
+$string['pathsrodataroot'] = 'Główny katalog danych nie ma uprawnień do zapisu. ';
 $string['pathsroparentdataroot'] = 'Nadrzędny katalog ({$a->parent}) jest tylko do odczytu. Katalog danych ({$a->dataroot}) nie może zostać utworzony przez instalator. ';
+$string['pathssubdirroot'] = 'Pełna ścieżka do katalogu z instalacją Moodle.';
+$string['pathsunsecuredataroot'] = 'Lokalizacja głównego katalogu danych nie jest bezpieczna';
 $string['pathswrongadmindir'] = 'Katalog admin nie istnieje';
 $string['phpextension'] = '{$a} rozszerzenie PHP';
 $string['phpversion'] = 'Wersja PHP';
index d11e5ff..178c85c 100644 (file)
@@ -680,6 +680,13 @@ $string['maintinprogress'] = 'Maintenance is in progress...';
 $string['managelang'] = 'Manage';
 $string['managelicenses'] = 'Manage licences';
 $string['manageqtypes'] = 'Manage question types';
+$string['maturity50'] = 'Alpha';
+$string['maturity100'] = 'Beta';
+$string['maturity150'] = 'Release candidate';
+$string['maturity200'] = 'Stable version';
+$string['maturitycorewarning'] = 'You are going to install or upgrade Moodle to a version marked as "{$a}"
+that is not considered as production-ready yet. Please make sure this is intentional
+and that you are using correct checkout of Moodle source code.';
 $string['maxbytes'] = 'Maximum uploaded file size';
 $string['maxconsecutiveidentchars'] = 'Consecutive identical characters';
 $string['maxeditingtime'] = 'Maximum time to edit posts';
index 7c9d27d..1958e41 100644 (file)
@@ -386,7 +386,7 @@ $string['currentlyselectedusers'] = 'Currently selected users';
 $string['currentpicture'] = 'Current picture';
 $string['currentrelease'] = 'Current release information';
 $string['currentversion'] = 'Current version';
-$string['databasechecking'] = 'Upgrading Moodle database from version {$a->oldversion} to {$a->newversion}...';
+$string['databasechecking'] = 'Upgrading Moodle database from version {$a->oldversion} to {$a->newversion}';
 $string['databaseperformance'] = 'Database performance';
 $string['databasesetup'] = 'Setting up database';
 $string['databasesuccess'] = 'Database was successfully upgraded';
index ab7107e..8c88572 100644 (file)
@@ -1015,6 +1015,19 @@ class dml_test extends UnitTestCase {
         $inskey6 = $DB->insert_record($tablename, array('course' => 1));
         $inskey7 = $DB->insert_record($tablename, array('course' => 0));
 
+        $table2 = $this->get_test_table("2");
+        $tablename2 = $table2->getName();
+        $table2->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table2->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table2->add_field('nametext', XMLDB_TYPE_TEXT, 'small', null, null, null, null);
+        $table2->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_table($table2);
+
+        $DB->insert_record($tablename2, array('course'=>3, 'nametext'=>'badabing'));
+        $DB->insert_record($tablename2, array('course'=>4, 'nametext'=>'badabang'));
+        $DB->insert_record($tablename2, array('course'=>5, 'nametext'=>'badabung'));
+        $DB->insert_record($tablename2, array('course'=>6, 'nametext'=>'badabong'));
+
         $records = $DB->get_records_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3));
         $this->assertEqual(2, count($records));
         $this->assertEqual($inskey1, reset($records)->id);
@@ -1068,6 +1081,28 @@ class dml_test extends UnitTestCase {
         $this->assertEqual($inskey3, reset($records)->id);
         $this->assertEqual($inskey2, end($records)->id);
 
+        // test 2 tables with aliases and limits with order bys
+        $sql = "SELECT t1.id, t1.course AS cid, t2.nametext FROM {{$tablename}} t1, {{$tablename2}} t2
+            WHERE t2.course=t1.course ORDER BY t1.course, ". $DB->sql_compare_text('t2.nametext');
+        $records = $DB->get_records_sql($sql, null, 2, 2); // Skip courses 3 and 6, get 4 and 5
+        $this->assertEqual(2, count($records));
+        $this->assertEqual('5', end($records)->cid);
+        $this->assertEqual('4', reset($records)->cid);
+
+        // test 2 tables with aliases and limits with order bys (limit which is out  of range)
+        $records = $DB->get_records_sql($sql, null, 2, 21098765432109876543210); // Skip course {3,6}, get {4,5}
+        $this->assertEqual(2, count($records));
+        $this->assertEqual('5', end($records)->cid);
+        $this->assertEqual('4', reset($records)->cid);
+
+        // test 2 tables with aliases and limits with order bys (limit which is out  of range)
+        $records = $DB->get_records_sql($sql, null, 21098765432109876543210, 2); // Skip all courses
+        $this->assertEqual(0, count($records));
+
+        // test 2 tables with aliases and limits with order bys (limit which is out  of range)
+        $records = $DB->get_records_sql($sql, null, 21098765432109876543210, 21098765432109876543210); // Skip all courses
+        $this->assertEqual(0, count($records));
+
         // TODO: Test limits in queries having DISTINCT clauses
 
         // note: fetching nulls, empties, LOBs already tested by test_update_record() no needed here
@@ -2040,6 +2075,7 @@ class dml_test extends UnitTestCase {
         $record = $DB->get_record($tablename, array('course' => 2));
         $this->assertEqual($newclob, $record->onetext, 'Test "small" CLOB update (full contents output disabled)');
         $this->assertEqual($newblob, $record->onebinary, 'Test "small" BLOB update (full contents output disabled)');
+
     }
 
     public function test_set_field() {
@@ -2401,6 +2437,70 @@ class dml_test extends UnitTestCase {
         $this->assertTrue($DB->record_exists_sql("SELECT * FROM {{$tablename}} WHERE course = ?", array(3)));
     }
 
+    public function test_recordset_locks_delete() {
+        $DB = $this->tdb;
+        $dbman = $DB->get_manager();
+
+        //Setup
+        $table = $this->get_test_table();
+        $tablename = $table->getName();
+
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_table($table);
+
+        $DB->insert_record($tablename, array('course' => 1));
+        $DB->insert_record($tablename, array('course' => 2));
+        $DB->insert_record($tablename, array('course' => 3));
+        $DB->insert_record($tablename, array('course' => 4));
+        $DB->insert_record($tablename, array('course' => 5));
+        $DB->insert_record($tablename, array('course' => 6));
+
+        // Test against db write locking while on an open recordset
+        $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4}
+        foreach ($rs as $record) {
+            $cid = $record->course;
+            $DB->delete_records($tablename, array('course' => $cid));
+            $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
+        }
+        $rs->close();
+
+        $this->assertEqual(4, $DB->count_records($tablename, array()));
+    }
+
+    public function test_recordset_locks_update() {
+        $DB = $this->tdb;
+        $dbman = $DB->get_manager();
+
+        //Setup
+        $table = $this->get_test_table();
+        $tablename = $table->getName();
+
+        $table->add_field('id', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
+        $table->add_field('course', XMLDB_TYPE_INTEGER, '10', XMLDB_UNSIGNED, XMLDB_NOTNULL, null, '0');
+        $table->add_key('primary', XMLDB_KEY_PRIMARY, array('id'));
+        $dbman->create_table($table);
+
+        $DB->insert_record($tablename, array('course' => 1));
+        $DB->insert_record($tablename, array('course' => 2));
+        $DB->insert_record($tablename, array('course' => 3));
+        $DB->insert_record($tablename, array('course' => 4));
+        $DB->insert_record($tablename, array('course' => 5));
+        $DB->insert_record($tablename, array('course' => 6));
+
+        // Test against db write locking while on an open recordset
+        $rs = $DB->get_recordset($tablename, array(), null, 'course', 2, 2); // get courses = {3,4}
+        foreach ($rs as $record) {
+            $cid = $record->course;
+            $DB->set_field($tablename, 'course', 10, array('course' => $cid));
+            $this->assertFalse($DB->record_exists($tablename, array('course' => $cid)));
+        }
+        $rs->close();
+
+        $this->assertEqual(2, $DB->count_records($tablename, array('course' => 10)));
+    }
+
     public function test_delete_records() {
         $DB = $this->tdb;
         $dbman = $DB->get_manager();
index ac6e42a..a319935 100644 (file)
@@ -347,13 +347,18 @@ class sqlsrv_native_moodle_database extends moodle_database {
      * @param mixed $params array of params for binding. If NULL, they are ignored.
      * @param mixed $sql_query_type - Type of operation
      * @param mixed $free_result - Default true, transaction query will be freed.
+     * @param mixed $scrollable - Default false, to use for quickly seeking to target records
      */
-    private function do_query($sql, $params, $sql_query_type, $free_result = true) {
+    private function do_query($sql, $params, $sql_query_type, $free_result = true, $scrollable = false) {
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
 
         $sql = $this->emulate_bound_params($sql, $params);
         $this->query_start($sql, $params, $sql_query_type);
-        $result = sqlsrv_query($this->sqlsrv, $sql);
+        if (!$scrollable) { // Only supporting next row
+            $result = sqlsrv_query($this->sqlsrv, $sql);
+        } else { // Suporting absolute/relative rows
+            $result = sqlsrv_query($this->sqlsrv, $sql, array(), array('Scrollable' => SQLSRV_CURSOR_STATIC));
+        }
 
         if ($result === false) {
             // TODO do something with error or just use if DEV or DEBUG?
@@ -756,118 +761,18 @@ class sqlsrv_native_moodle_database extends moodle_database {
         $limitnum = max(0, $limitnum);
 
         if ($limitfrom or $limitnum) {
-            $sql = $this->limit_to_top_n($sql, $limitfrom, $limitnum);
-        }
-        $result = $this->do_query($sql, $params, SQL_QUERY_SELECT, false);
-        return $this->create_recordset($result);
-    }
-
-    /**
-     * Perform a emulation for LIMIT(offset, limit)
-     *
-     * @param string $sql
-     * @param int $offset
-     * @param int $limit
-     * @return string sql
-     */
-    private function limit_to_top_n($sql, $offset, $limit) {
-        // If there is no limit we can return immediately
-        if ($limit < 1 && $offset < 1) {
-            return $sql;
-        }
-
-        // Make sure they are at least 0
-        $limit = max(0, (int)$limit);
-        $offset = max(0, (int)$offset);
-        // This is an sqlserver bigint - -1 and will be used as a value
-        // for top when essentially we want everything.
-        // This needs to be a string so that it doesn't get malformed.
-        $bigint = '9223372036854775806';
-
-        // If limit is 0 set it to BITINT - 1
-        if (empty($limit)) {
-            $limit = $bigint;
-        } else {
-            $limit = $offset + $limit;
-        }
-
-        // Set up defaults for the next bit.
-        $columns = '*';      // Default to all columns
-        $columnnames = '*';  // As above
-        $firstcolumn = 'id'; // The default first column is id
-        $orderby = '';       // The order by of the main query
-
-        // We need to do a couple of maintenance tasks on the columns seeing as
-        // they are going to be wrapped in a sub select.
-        //
-        // 1. Collect the first column to use for an order by if there isn't an
-        //    explicit order by within the query.
-        // 2. Give all constant columns a proper alias, this is required because
-        //    of the subselect.
-        if (preg_match('#^(\s*SELECT\s+)(DISTINCT\s+)?(.*?)(\sFROM\s)#is', $sql, $columnmatches)) {
-            // Make sure we have some columns
-            if (!empty($columnmatches[3])) {
-                $columns = explode(',', $columnmatches[3]);
-                $firstcolumn = null;
-                $constantcount = 0;
-                foreach ($columns as $key=>$column) {
-                    // Trim, trim, trim, except during Movember.
-                    $column = trim($column);
-                    if (preg_match('#\sAS\s+(\w+)\s*$#si', $column, $matches)) {
-                        // Make sure we use the column alias if available.
-                        $column = $matches[1];
-                    } else if (preg_match("#^('[^']*'|\d+)$#", $column)) {
-                        // Give constants an alias in the main query and use the
-                        // alias for the new outer queries.
-                        $constantcount++;
-                        $column = 'constant_'.$constantcount;
-                        $columns[$key] .= ' AS '.$column;
-                    }
-
-                    // Store the first column for later abuse.
-                    if ($firstcolumn === null) {
-                        $firstcolumn = $column;
-                    }
-                }
-                // Glue things back together
-                $columns = join(', ', $columns);
-                // Switch out the fixed main columns (added constant aliases).
-                $sql = str_replace($columnmatches[0], $columnmatches[1].$columnmatches[2].$columns.$columnmatches[4], $sql);
+            if ($limitnum >= 1) { // Only apply TOP clause if we have any limitnum (limitfrom offset is handled later)
+                $fetch = $limitfrom + $limitnum;
+                $sql = preg_replace('/^([\s(])*SELECT([\s]+(DISTINCT|ALL))?(?!\s*TOP\s*\()/i',
+                                    "\\1SELECT\\2 TOP $fetch", $sql);
             }
         }
+        $result = $this->do_query($sql, $params, SQL_QUERY_SELECT, false, (bool)$limitfrom);
 
-        // Collect the orderby from the main query to use in the row number order by.
-        if (preg_match('#\sORDER\s+BY\s+([^)]+?)(GROUP\s+BY|$)#i', $sql, $matches)) {
-            // We need to remove it from the main query as well.
-            $sql = str_replace($matches[0], ' '.$matches[2], $sql);
-            $orderby = $matches[1];
-        } else {
-            // Default orderby to the first column.
-            $orderby = $firstcolumn;
-        }
-        // Remove any table aliases from the order by.
-        $orderby = preg_replace('#[^\s,]*\.([^\s,]*)#', '$1', $orderby);
-
-        // If the orderby is all tables everything will break, default to id.
-        if ($orderby == '*') {
-            $orderby = 'id';
+        if ($limitfrom) { // Skip $limitfrom records
+            sqlsrv_fetch($result, SQLSRV_SCROLL_ABSOLUTE, $limitfrom - 1);
         }
-
-        // Now we need to build the queries up so that we collect a row count field and then sort on it.
-        // To do this we need to nest the query twice. The first nesting selects all the rows from the
-        // query and then proceeds to use OVER to generate a row number.
-        // The second nesting we limit by selecting where rownumber between offset and limit
-        // In both cases we will select the original query fields using q.* this is important
-        // as there can be any number of crafty things going on. It does however mean that we
-        // end up with the first field being sqlsrvrownumber however sqlsrv_native_moodle_recordset
-        // strips that off during processing if it exists.
-        // Build the inner outer query.
-        $sql = "SELECT TOP $bigint ROW_NUMBER() OVER(ORDER BY $orderby) AS sqlsrvrownumber, q.* FROM ($sql) AS q";
-        // Build the outer most query.
-        $sql = "SELECT q.* FROM ($sql) AS q WHERE q.sqlsrvrownumber > $offset AND q.sqlsrvrownumber <= $limit";
-
-        // Return the now mangled query for use.
-        return $sql;
+        return $this->create_recordset($result);
     }
 
     /**
index 5c338b5..96a70f0 100644 (file)
@@ -220,6 +220,47 @@ function enrol_check_plugins($user) {
     unset($inprogress[$user->id]);  // Unset the flag
 }
 
+/**
+ * Do these two students share any course?
+ *
+ * The courses has to be visible and enrolments has to be active,
+ * timestart and timeend restrictions are ignored.
+ *
+ * @param stdClass|int $user1
+ * @param stdClass|int $user2
+ * @return bool
+ */
+function enrol_sharing_course($user1, $user2) {
+    global $DB, $CFG;
+
+    $user1 = !empty($user1->id) ? $user1->id : $user1;
+    $user2 = !empty($user2->id) ? $user2->id : $user2;
+
+    if (empty($user1) or empty($user2)) {
+        return false;
+    }
+
+    if (!$plugins = explode(',', $CFG->enrol_plugins_enabled)) {
+        return false;
+    }
+
+    list($plugins, $params) = $DB->get_in_or_equal($plugins, SQL_PARAMS_NAMED, 'ee00');
+    $params['enabled'] = ENROL_INSTANCE_ENABLED;
+    $params['active1'] = ENROL_USER_ACTIVE;
+    $params['active2'] = ENROL_USER_ACTIVE;
+    $params['user1']   = $user1;
+    $params['user2']   = $user2;
+
+    $sql = "SELECT DISTINCT 'x'
+              FROM {enrol} e
+              JOIN {user_enrolments} ue1 ON (ue1.enrolid = e.id AND ue1.status = :active1 AND ue1.userid = :user1)
+              JOIN {user_enrolments} ue2 ON (ue1.enrolid = e.id AND ue1.status = :active2 AND ue2.userid = :user2)
+              JOIN {course} c ON (c.id = e.courseid AND c.visible = 1)
+             WHERE e.status = :enabled AND e.enrol $plugins";
+
+    return $DB->record_exists_sql($sql, $params);
+}
+
 /**
  * This function adds necessary enrol plugins UI into the course edit form.
  *
index 1addd11..def025c 100644 (file)
@@ -138,6 +138,15 @@ class MoodleExcelWorksheet {
      */
     function MoodleExcelWorksheet($name, &$workbook, $latin_output=false) {
 
+        if (strlen($name) > 31) {
+            // Excel does not seem able to cope with sheet names > 31 chars.
+            // With $latin_output = false, it does not cope at all.
+            // With $latin_output = true it is supposed to work, but in our experience,
+            // it doesn't. Therefore, truncate in all circumstances.
+            $textlib = textlib_get_instance();
+            $name = $textlib->substr($name, 0, 31);
+        }
+
     /// Internally, add one sheet to the workbook
         $this->pear_excel_worksheet =& $workbook->addWorksheet($name);
         $this->latin_output = $latin_output;
index e1d5a6e..ab586af 100644 (file)
@@ -9300,6 +9300,13 @@ function get_performance_info() {
         $info['txt'] .= "serverload: {$info['serverload']} ";
     }
 
+    // Display size of session if session started
+    if (session_id()) {
+        $info['sessionsize'] = display_size(strlen(session_encode()));
+        $info['html'] .= '<span class="sessionsize">Session: ' . $info['sessionsize'] . '</span> ';
+        $info['txt'] .= "Session: {$info['sessionsize']} ";
+    }
+
 /*    if (isset($rcache->hits) && isset($rcache->misses)) {
         $info['rcachehits'] = $rcache->hits;
         $info['rcachemisses'] = $rcache->misses;
index 12c543d..c4b07ee 100644 (file)
@@ -53,6 +53,14 @@ define('MEMORY_EXTRA', -3);
 /** Extremely large memory limit - not recommended for standard scripts */
 define('MEMORY_HUGE', -4);
 
+/**
+ * Software maturity levels used by the core and plugins
+ */
+define('MATURITY_ALPHA',    50);    // internals can be tested using white box techniques
+define('MATURITY_BETA',     100);   // feature complete, ready for preview and testing
+define('MATURITY_RC',       150);   // tested, will be released unless there are fatal bugs
+define('MATURITY_STABLE',   200);   // ready for production deployment
+
 /**
  * Simple class. It is usually used instead of stdClass because it looks
  * more familiar to Java developers ;-) Do not use for type checking of
index 4af49a1..eb5467d 100644 (file)
@@ -143,6 +143,32 @@ class web_test extends UnitTestCase {
         $this->assertEqual('lala xx', clean_text($text, FORMAT_MOODLE));
         $this->assertEqual('lala xx', clean_text($text, FORMAT_HTML));
     }
+
+    /**
+     * Tests the 'allowid' option for format_text.
+     */
+    public function test_format_text_allowid() {
+        global $CFG;
+
+        // This test relates only to html purifier, so switch to it if not in use
+        $oldcfg = $CFG->enablehtmlpurifier;
+        $CFG->enablehtmlpurifier = true;
+
+        // Start off by not allowing ids (default)
+        $options = array(
+            'nocache' => true
+        );
+        $result = format_text('<div id="example">Frog</div>', FORMAT_HTML, $options);
+        $this->assertEqual('<div>Frog</div>', $result);
+
+        // Now allow ids
+        $options['allowid'] = true;
+        $result = format_text('<div id="example">Frog</div>', FORMAT_HTML, $options);
+        $this->assertEqual('<div id="example">Frog</div>', $result);
+
+        // Switch config back
+        $CFG->enablehtmlpurifier = $oldcfg;
+    }
 }
 
 
index 85ef747..6136507 100644 (file)
@@ -958,6 +958,8 @@ function format_text_menu() {
  *      context     :   The context that will be used for filtering.
  *      overflowdiv :   If set to true the formatted text will be encased in a div
  *                      with the class no-overflow before being returned. Default false.
+ *      allowid     :   If true then id attributes will not be removed, even when
+ *                      using htmlpurifier. Default false.
  * </pre>
  *
  * @todo Finish documenting this function
@@ -974,7 +976,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
     global $CFG, $COURSE, $DB, $PAGE;
     static $croncache = array();
 
-    if ($text === '') {
+    if ($text === '' || is_null($text)) {
         return ''; // no need to do any filters and cleaning
     }
 
@@ -1069,7 +1071,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
     switch ($format) {
         case FORMAT_HTML:
             if (!$options['noclean']) {
-                $text = clean_text($text, FORMAT_HTML);
+                $text = clean_text($text, FORMAT_HTML, $options);
             }
             $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_HTML));
             break;
@@ -1092,7 +1094,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
         case FORMAT_MARKDOWN:
             $text = markdown_to_html($text);
             if (!$options['noclean']) {
-                $text = clean_text($text, FORMAT_HTML);
+                $text = clean_text($text, FORMAT_HTML, $options);
             }
             $text = $filtermanager->filter_text($text, $context, array('originalformat' => FORMAT_MARKDOWN));
             break;
@@ -1100,7 +1102,7 @@ function format_text($text, $format = FORMAT_MOODLE, $options = NULL, $courseid_
         default:  // FORMAT_MOODLE or anything else
             $text = text_to_html($text, null, $options['para'], $options['newlines']);
             if (!$options['noclean']) {
-                $text = clean_text($text, FORMAT_HTML);
+                $text = clean_text($text, FORMAT_HTML, $options);
             }
             $text = $filtermanager->filter_text($text, $context, array('originalformat' => $format));
             break;
@@ -1445,9 +1447,11 @@ function trusttext_active() {
  *
  * @param string $text The text to be cleaned
  * @param int $format deprecated parameter, should always contain FORMAT_HTML or FORMAT_MOODLE
+ * @param array $options Array of options; currently only option supported is 'allowid' (if true,
+ *   does not remove id attributes when cleaning)
  * @return string The cleaned up text
  */
-function clean_text($text, $format = FORMAT_HTML) {
+function clean_text($text, $format = FORMAT_HTML, $options = array()) {
     global $ALLOWED_TAGS, $CFG;
 
     if (empty($text) or is_numeric($text)) {
@@ -1464,7 +1468,7 @@ function clean_text($text, $format = FORMAT_HTML) {
     }
 
     if (!empty($CFG->enablehtmlpurifier)) {
-        $text = purify_html($text);
+        $text = purify_html($text, $options);
     } else {
     /// Fix non standard entity notations
         $text = fix_non_standard_entities($text);
@@ -1492,16 +1496,19 @@ function clean_text($text, $format = FORMAT_HTML) {
  *
  * @global object
  * @param string $text The (X)HTML string to purify
+ * @param array $options Array of options; currently only option supported is 'allowid' (if set,
+ *   does not remove id attributes when cleaning)
  */
-function purify_html($text) {
+function purify_html($text, $options = array()) {
     global $CFG;
 
     // this can not be done only once because we sometimes need to reset the cache
     $cachedir = $CFG->dataroot.'/cache/htmlpurifier';
     check_dir_exists($cachedir);
 
-    static $purifier = false;
-    if ($purifier === false) {
+    $type = !empty($options['allowid']) ? 'allowid' : 'normal';
+    static $purifiers = array();
+    if (empty($purifiers[$type])) {
         require_once $CFG->libdir.'/htmlpurifier/HTMLPurifier.safe-includes.php';
         $config = HTMLPurifier_Config::createDefault();
 
@@ -1522,6 +1529,10 @@ function purify_html($text) {
             $config->set('HTML.SafeEmbed', true);
         }
 
+        if ($type === 'allowid') {
+            $config->set('Attr.EnableID', true);
+        }
+
         $def = $config->getHTMLDefinition(true);
         $def->addElement('nolink', 'Block', 'Flow', array());                       // skip our filters inside
         $def->addElement('tex', 'Inline', 'Inline', array());                       // tex syntax, equivalent to $$xx$$
@@ -1530,6 +1541,9 @@ function purify_html($text) {
         $def->addAttribute('span', 'xxxlang', 'CDATA');                             // current problematic multilang
 
         $purifier = new HTMLPurifier($config);
+        $purifiers[$type] = $purifier;
+    } else {
+        $purifier = $purifiers[$type];
     }
 
     $multilang = (strpos($text, 'class="multilang"') !== false);
index 29298db..32a1f46 100644 (file)
     require('../config.php');
     require('lib.php');
 
+    //the same URL params as in 1.9
     $userid     = required_param('id', PARAM_INT);
     $noframesjs = optional_param('noframesjs', 0, PARAM_BOOL);
 
-    $params = array('id'=>$userid);
+    $params = array('user2'=>$userid);
     if (!empty($noframesjs)) {
         $params['noframesjs'] = $noframesjs;
     }
index e5f4915..6910282 100644 (file)
@@ -37,21 +37,21 @@ if (empty($CFG->messaging)) {
     print_error('disabled', 'message');
 }
 
-//VIEW_PARAM is the preferred URL parameter but we'll still accept usergroup in case its referenced externally
-$usergroup = optional_param('usergroup', VIEW_UNREAD_MESSAGES, PARAM_ALPHANUMEXT);
-$viewing = optional_param(VIEW_PARAM, $usergroup, PARAM_ALPHANUMEXT);
+//'viewing' is the preferred URL parameter but we'll still accept usergroup in case its referenced externally
+$usergroup = optional_param('usergroup', MESSAGE_VIEW_UNREAD_MESSAGES, PARAM_ALPHANUMEXT);
+$viewing = optional_param('viewing', $usergroup, PARAM_ALPHANUMEXT);
 
 $history   = optional_param('history', MESSAGE_HISTORY_SHORT, PARAM_INT);
 $search    = optional_param('search', '', PARAM_CLEAN); //TODO: use PARAM_RAW, but make sure we use s() and p() properly
 
 //the same param as 1.9 and the param we have been logging. Use this parameter.
-$user1id   = optional_param(MESSAGE_USER1_PARAM, $USER->id, PARAM_INT);
+$user1id   = optional_param('user1', $USER->id, PARAM_INT);
 //2.0 shipped using this param. Retaining it only for compatibility. It should be removed.
 $user1id   = optional_param('user', $user1id, PARAM_INT);
 
 //the same param as 1.9 and the param we have been logging. Use this parameter.
-$user2id   = optional_param(MESSAGE_USER2_PARAM, 0, PARAM_INT);
-//2.0 shipped using this param. Retaining it only for compatibility. It should be removed.
+$user2id   = optional_param('user2', 0, PARAM_INT);
+//The class send_form supplies the receiving user id as 'id'
 $user2id   = optional_param('id', $user2id, PARAM_INT);
 
 $addcontact     = optional_param('addcontact',     0, PARAM_INT); // adding a contact
@@ -68,15 +68,15 @@ $page = optional_param('page', 0, PARAM_INT);
 $url = new moodle_url('/message/index.php');
 
 if ($user2id !== 0) {
-    $url->param('id', $user2id);
+    $url->param('user2', $user2id);
 }
 
 if ($user2id !== 0) {
     //Switch view back to contacts if:
     //1) theyve searched and selected a user
     //2) they've viewed recent messages or notifications and clicked through to a user
-    if ($viewing == VIEW_SEARCH || $viewing == VIEW_SEARCH || $viewing == VIEW_RECENT_NOTIFICATIONS) {
-        $viewing = VIEW_CONTACTS;
+    if ($viewing == MESSAGE_VIEW_SEARCH || $viewing == MESSAGE_VIEW_SEARCH || $viewing == MESSAGE_VIEW_RECENT_NOTIFICATIONS) {
+        $viewing = MESSAGE_VIEW_CONTACTS;
     }
 }
 $url->param('viewing', $viewing);
@@ -91,7 +91,7 @@ $context = get_context_instance(CONTEXT_SYSTEM);
 
 $user1 = null;
 $currentuser = true;
-$showcontactactionlinks = SHOW_ACTION_LINKS_IN_CONTACT_LIST;
+$showcontactactionlinks = true;
 if ($user1id != $USER->id) {
     $user1 = $DB->get_record('user', array('id' => $user1id));
     if (!$user1) {
@@ -122,7 +122,7 @@ if ($user1->id != $USER->id && (!empty($user2) && $user2->id != $USER->id) && !h
 if ($addcontact and confirm_sesskey()) {
     add_to_log(SITEID, 'message', 'add contact', 'index.php?user1='.$addcontact.'&amp;user2='.$USER->id, $addcontact);
     message_add_contact($addcontact);
-    redirect($CFG->wwwroot . '/message/index.php?'.VIEW_PARAM.'=contacts&id='.$addcontact);
+    redirect($CFG->wwwroot . '/message/index.php?viewing=contacts&id='.$addcontact);
 }
 if ($removecontact and confirm_sesskey()) {
     add_to_log(SITEID, 'message', 'remove contact', 'index.php?user1='.$removecontact.'&amp;user2='.$USER->id, $removecontact);
@@ -140,7 +140,6 @@ if ($unblockcontact and confirm_sesskey()) {
 //was a message sent? Do NOT allow someone looking at someone else's messages to send them.
 $messageerror = null;
 if ($currentuser && !empty($user2) && has_capability('moodle/site:sendmessage', $context)) {
-
     // Check that the user is not blocking us!!
     if ($contact = $DB->get_record('message_contacts', array('userid' => $user2->id, 'contactid' => $user1->id))) {
         if ($contact->blocked and !has_capability('moodle/site:readallmessages', $context)) {
@@ -167,13 +166,12 @@ if ($currentuser && !empty($user2) && has_capability('moodle/site:sendmessage',
             if (!confirm_sesskey()) {
                 print_error('invalidsesskey');
             }
-
             $messageid = message_post_message($user1, $user2, $data->message, FORMAT_MOODLE);
             if (!empty($messageid)) {
                 //including the id of the user sending the message in the logged URL so the URL works for admins
                 //note message ID may be misleading as the message may potentially get a different ID when moved from message to message_read
                 add_to_log(SITEID, 'message', 'write', 'index.php?user='.$user1->id.'&id='.$user2->id.'&history=1#m'.$messageid, $user1->id);
-                redirect($CFG->wwwroot . '/message/index.php?'.VIEW_PARAM.'='.$viewing.'&id='.$user2->id);
+                redirect($CFG->wwwroot . '/message/index.php?viewing='.$viewing.'&id='.$user2->id);
             }
         }
     }
@@ -206,16 +204,16 @@ if (!empty($user2)) {
     if ($countunread>0) {
         //mark the messages we're going to display as read
         message_mark_messages_read($user1->id, $user2->id);
-         if($viewing == VIEW_UNREAD_MESSAGES) {
+         if($viewing == MESSAGE_VIEW_UNREAD_MESSAGES) {
              $viewingnewmessages = true;
          }
     }
 }
 $countunreadtotal = message_count_unread_messages($user1);
 
-if ($countunreadtotal == 0 && $viewing == VIEW_UNREAD_MESSAGES && empty($user2)) {
+if ($countunreadtotal == 0 && $viewing == MESSAGE_VIEW_UNREAD_MESSAGES && empty($user2)) {
     //default to showing the search
-    $viewing = VIEW_SEARCH;
+    $viewing = MESSAGE_VIEW_SEARCH;
 }
 
 $blockedusers = message_get_blocked_users($user1, $user2);
@@ -298,11 +296,11 @@ echo html_writer::start_tag('div', array('class' => 'messagearea mdl-align'));
                 }
             echo html_writer::end_tag('div');
         }
-    } else if ($viewing == VIEW_SEARCH) {
+    } else if ($viewing == MESSAGE_VIEW_SEARCH) {
         message_print_search($advancedsearch, $user1);
-    } else if ($viewing == VIEW_RECENT_CONVERSATIONS) {
+    } else if ($viewing == MESSAGE_VIEW_RECENT_CONVERSATIONS) {
         message_print_recent_conversations($user1);
-    } else if ($viewing == VIEW_RECENT_NOTIFICATIONS) {
+    } else if ($viewing == MESSAGE_VIEW_RECENT_NOTIFICATIONS) {
         message_print_recent_notifications($user1);
     }
 echo html_writer::end_tag('div');
index c2cb7af..c0524b9 100644 (file)
@@ -25,7 +25,6 @@
 
 require_once($CFG->libdir.'/eventslib.php');
 
-
 define ('MESSAGE_SHORTLENGTH', 300);
 
 //$PAGE isnt set if we're being loaded by cron which doesnt display popups anyway
@@ -38,32 +37,16 @@ define ('MESSAGE_DISCUSSION_HEIGHT',500);
 
 define ('MESSAGE_SHORTVIEW_LIMIT', 8);//the maximum number of messages to show on the short message history
 
-define ('CONTACT_ID','id');
-
 define('MESSAGE_HISTORY_SHORT',0);
 define('MESSAGE_HISTORY_ALL',1);
 
-//some constants used as function arguments. Just to make function calls a bit more understandable
-define('IS_CONTACT',true);
-define('IS_NOT_CONTACT',false);
-
-define('IS_BLOCKED',true);
-define('IS_NOT_BLOCKED',false);
-
-define('VIEW_PARAM','viewing');
-
-define('VIEW_UNREAD_MESSAGES','unread');
-define('VIEW_RECENT_CONVERSATIONS','recentconversations');
-define('VIEW_RECENT_NOTIFICATIONS','recentnotifications');
-define('VIEW_CONTACTS','contacts');
-define('VIEW_BLOCKED','blockedusers');
-define('VIEW_COURSE','course_');
-define('VIEW_SEARCH','search');
-
-define('MESSAGE_USER1_PARAM','user1');
-define('MESSAGE_USER2_PARAM','user2');
-
-define('SHOW_ACTION_LINKS_IN_CONTACT_LIST', true);
+define('MESSAGE_VIEW_UNREAD_MESSAGES','unread');
+define('MESSAGE_VIEW_RECENT_CONVERSATIONS','recentconversations');
+define('MESSAGE_VIEW_RECENT_NOTIFICATIONS','recentnotifications');
+define('MESSAGE_VIEW_CONTACTS','contacts');
+define('MESSAGE_VIEW_BLOCKED','blockedusers');
+define('MESSAGE_VIEW_COURSE','course_');
+define('MESSAGE_VIEW_SEARCH','search');
 
 define('MESSAGE_SEARCH_MAX_RESULTS', 200);
 
@@ -84,7 +67,7 @@ if (!isset($CFG->message_offline_time)) {
 * Print the selector that allows the user to view their contacts, course participants, their recent
 * conversations etc
 * @param int $countunreadtotal how many unread messages does the user have?
-* @param int $viewing What is the user viewing? ie VIEW_UNREAD_MESSAGES, VIEW_SEARCH etc
+* @param int $viewing What is the user viewing? ie MESSAGE_VIEW_UNREAD_MESSAGES, MESSAGE_VIEW_SEARCH etc
 * @param object $user1 the user whose messages are being viewed
 * @param object $user2 the user $user1 is talking to
 * @param array $blockedusers an array of users blocked by $user1
@@ -101,13 +84,13 @@ function message_print_contact_selector($countunreadtotal, $viewing, $user1, $us
     echo html_writer::start_tag('div', array('class' => 'contactselector mdl-align'));
 
     //if 0 unread messages and they've requested unread messages then show contacts
-    if ($countunreadtotal == 0 && $viewing == VIEW_UNREAD_MESSAGES) {
-        $viewing = VIEW_CONTACTS;
+    if ($countunreadtotal == 0 && $viewing == MESSAGE_VIEW_UNREAD_MESSAGES) {
+        $viewing = MESSAGE_VIEW_CONTACTS;
     }
 
     //if they have no blocked users and they've requested blocked users switch them over to contacts
-    if (count($blockedusers) == 0 && $viewing == VIEW_BLOCKED) {
-        $viewing = VIEW_CONTACTS;
+    if (count($blockedusers) == 0 && $viewing == MESSAGE_VIEW_BLOCKED) {
+        $viewing = MESSAGE_VIEW_CONTACTS;
     }
 
     $onlyactivecourses = true;
@@ -121,13 +104,13 @@ function message_print_contact_selector($countunreadtotal, $viewing, $user1, $us
 
     message_print_usergroup_selector($viewing, $courses, $coursecontexts, $countunreadtotal, count($blockedusers), $strunreadmessages);
 
-    if ($viewing == VIEW_UNREAD_MESSAGES) {
+    if ($viewing == MESSAGE_VIEW_UNREAD_MESSAGES) {
         message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $PAGE->url, 1, $showcontactactionlinks,$strunreadmessages, $user2);
-    } else if ($viewing == VIEW_CONTACTS || $viewing == VIEW_SEARCH || $viewing == VIEW_RECENT_CONVERSATIONS || $viewing == VIEW_RECENT_NOTIFICATIONS) {
+    } else if ($viewing == MESSAGE_VIEW_CONTACTS || $viewing == MESSAGE_VIEW_SEARCH || $viewing == MESSAGE_VIEW_RECENT_CONVERSATIONS || $viewing == MESSAGE_VIEW_RECENT_NOTIFICATIONS) {
         message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $PAGE->url, 0, $showcontactactionlinks, $strunreadmessages, $user2);
-    } else if ($viewing == VIEW_BLOCKED) {
+    } else if ($viewing == MESSAGE_VIEW_BLOCKED) {
         message_print_blocked_users($blockedusers, $PAGE->url, $showcontactactionlinks, null, $user2);
-    } else if (substr($viewing, 0, 7) == VIEW_COURSE) {
+    } else if (substr($viewing, 0, 7) == MESSAGE_VIEW_COURSE) {
         $courseidtoshow = intval(substr($viewing, 7));
 
         if (!empty($courseidtoshow)
@@ -144,11 +127,11 @@ function message_print_contact_selector($countunreadtotal, $viewing, $user1, $us
     echo html_writer::start_tag('form', array('action' => 'index.php','method' => 'GET'));
     echo html_writer::start_tag('fieldset');
     $managebuttonclass = 'visible';
-    if ($viewing == VIEW_SEARCH) {
+    if ($viewing == MESSAGE_VIEW_SEARCH) {
         $managebuttonclass = 'hiddenelement';
     }
     $strmanagecontacts = get_string('search','message');
-    echo html_writer::empty_tag('input', array('type' => 'hidden','name' => VIEW_PARAM,'value' => VIEW_SEARCH));
+    echo html_writer::empty_tag('input', array('type' => 'hidden','name' => 'viewing','value' => MESSAGE_VIEW_SEARCH));
     echo html_writer::empty_tag('input', array('type' => 'submit','value' => $strmanagecontacts,'class' => $managebuttonclass));
     echo html_writer::end_tag('fieldset');
     echo html_writer::end_tag('form');
@@ -270,8 +253,10 @@ function message_print_blocked_users($blockedusers, $contactselecturl=null, $sho
         echo html_writer::tag('td', get_string('blockedusers', 'message', $countblocked), array('colspan' => 3, 'class' => 'heading'));
         echo html_writer::end_tag('tr');
 
+        $isuserblocked = true;
+        $isusercontact = false;
         foreach ($blockedusers as $blockeduser) {
-            message_print_contactlist_user($blockeduser, IS_NOT_CONTACT, IS_BLOCKED, $contactselecturl, $showactionlinks, $user2);
+            message_print_contactlist_user($blockeduser, $isusercontact, $isuserblocked, $contactselecturl, $showactionlinks, $user2);
         }
     }
 
@@ -377,6 +362,7 @@ function message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $
     $countonlinecontacts  = count($onlinecontacts);
     $countofflinecontacts = count($offlinecontacts);
     $countstrangers       = count($strangers);
+    $isuserblocked = null;
 
     if ($countonlinecontacts + $countofflinecontacts == 0) {
         echo html_writer::tag('div', get_string('contactlistempty', 'message'), array('class' => 'heading'));
@@ -395,9 +381,11 @@ function message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $
             message_print_heading(get_string('onlinecontacts', 'message', $countonlinecontacts));
         }
 
+        $isuserblocked = false;
+        $isusercontact = true;
         foreach ($onlinecontacts as $contact) {
             if ($minmessages == 0 || $contact->messagecount >= $minmessages) {
-                message_print_contactlist_user($contact, IS_CONTACT, IS_NOT_BLOCKED, $contactselecturl, $showactionlinks, $user2);
+                message_print_contactlist_user($contact, $isusercontact, $isuserblocked, $contactselecturl, $showactionlinks, $user2);
             }
         }
     }
@@ -409,9 +397,11 @@ function message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $
             message_print_heading(get_string('offlinecontacts', 'message', $countofflinecontacts));
         }
 
+        $isuserblocked = false;
+        $isusercontact = true;
         foreach ($offlinecontacts as $contact) {
             if ($minmessages == 0 || $contact->messagecount >= $minmessages) {
-                message_print_contactlist_user($contact, IS_CONTACT, IS_NOT_BLOCKED, $contactselecturl, $showactionlinks, $user2);
+                message_print_contactlist_user($contact, $isusercontact, $isuserblocked, $contactselecturl, $showactionlinks, $user2);
             }
         }
 
@@ -421,9 +411,11 @@ function message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $
     if ($countstrangers) {
         message_print_heading(get_string('incomingcontacts', 'message', $countstrangers));
 
+        $isuserblocked = false;
+        $isusercontact = false;
         foreach ($strangers as $stranger) {
             if ($minmessages == 0 || $stranger->messagecount >= $minmessages) {
-                message_print_contactlist_user($stranger, IS_NOT_CONTACT, IS_NOT_BLOCKED, $contactselecturl, $showactionlinks, $user2);
+                message_print_contactlist_user($stranger, $isusercontact, $isuserblocked, $contactselecturl, $showactionlinks, $user2);
             }
         }
     }
@@ -438,7 +430,7 @@ function message_print_contacts($onlinecontacts, $offlinecontacts, $strangers, $
 /**
 * Print a select box allowing the user to choose to view new messages, course participants etc.
 * Called by message_print_contact_selector()
-* @param int $viewing What page is the user viewing ie VIEW_UNREAD_MESSAGES, VIEW_RECENT_CONVERSATIONS etc
+* @param int $viewing What page is the user viewing ie MESSAGE_VIEW_UNREAD_MESSAGES, MESSAGE_VIEW_RECENT_CONVERSATIONS etc
 * @param array $courses array of course objects. The courses the user is enrolled in.
 * @param array $coursecontexts array of course contexts. Keyed on course id.
 * @param int $countunreadtotal how many unread messages does the user have?
@@ -451,14 +443,14 @@ function message_print_usergroup_selector($viewing, $courses, $coursecontexts, $
     $textlib = textlib_get_instance(); // going to use textlib services
 
     if ($countunreadtotal>0) { //if there are unread messages
-        $options[VIEW_UNREAD_MESSAGES] = $strunreadmessages;
+        $options[MESSAGE_VIEW_UNREAD_MESSAGES] = $strunreadmessages;
     }
 
     $str = get_string('mycontacts', 'message');
-    $options[VIEW_CONTACTS] = $str;
+    $options[MESSAGE_VIEW_CONTACTS] = $str;
 
-    $options[VIEW_RECENT_CONVERSATIONS] = get_string('mostrecentconversations', 'message');
-    $options[VIEW_RECENT_NOTIFICATIONS] = get_string('mostrecentnotifications', 'message');
+    $options[MESSAGE_VIEW_RECENT_CONVERSATIONS] = get_string('mostrecentconversations', 'message');
+    $options[MESSAGE_VIEW_RECENT_NOTIFICATIONS] = get_string('mostrecentnotifications', 'message');
 
     if (!empty($courses)) {
         $courses_options = array();
@@ -467,9 +459,9 @@ function message_print_usergroup_selector($viewing, $courses, $coursecontexts, $
             if (has_capability('moodle/course:viewparticipants', $coursecontexts[$course->id])) {
                 //Not using short_text() as we want the end of the course name. Not the beginning.
                 if ($textlib->strlen($course->shortname) > MESSAGE_MAX_COURSE_NAME_LENGTH) {
-                    $courses_options[VIEW_COURSE.$course->id] = '...'.$textlib->substr($course->shortname, -MESSAGE_MAX_COURSE_NAME_LENGTH);
+                    $courses_options[MESSAGE_VIEW_COURSE.$course->id] = '...'.$textlib->substr($course->shortname, -MESSAGE_MAX_COURSE_NAME_LENGTH);
                 } else {
-                    $courses_options[VIEW_COURSE.$course->id] = $course->shortname;
+                    $courses_options[MESSAGE_VIEW_COURSE.$course->id] = $course->shortname;
                 }
             }
         }
@@ -481,12 +473,12 @@ function message_print_usergroup_selector($viewing, $courses, $coursecontexts, $
 
     if ($countblocked>0) {
         $str = get_string('blockedusers','message', $countblocked);
-        $options[VIEW_BLOCKED] = $str;
+        $options[MESSAGE_VIEW_BLOCKED] = $str;
     }
 
     echo html_writer::start_tag('form', array('id' => 'usergroupform','method' => 'get','action' => ''));
         echo html_writer::start_tag('fieldset');
-            echo html_writer::select($options, VIEW_PARAM, $viewing, false, array('id' => VIEW_PARAM,'onchange' => 'this.form.submit()'));
+            echo html_writer::select($options, 'viewing', $viewing, false, array('id' => 'viewing','onchange' => 'this.form.submit()'));
         echo html_writer::end_tag('fieldset');
     echo html_writer::end_tag('form');
 }
@@ -2039,7 +2031,7 @@ function message_print_contactlist_user($contact, $incontactlist = true, $isbloc
 
     $link = $action = null;
     if (!empty($selectcontacturl)) {
-        $link = new moodle_url($selectcontacturl.'&'.CONTACT_ID.'='.$contact->id);
+        $link = new moodle_url($selectcontacturl.'&user2='.$contact->id);
     } else {
         //can $selectcontacturl be removed and maybe the be removed and hardcoded?
         $link = new moodle_url("/message/index.php?id=$contact->id");
index edbe055..7adedc2 100644 (file)
@@ -6,7 +6,7 @@
             <input type="hidden" name="sesskey" value="<?php p(sesskey()); ?>" />
             <input type="text" name="combinedsearch" size="40" id="combinedsearch" value="<?php p($combinedsearchstring); ?>" />
             <input type="submit" name="combinedsubmit" value="<?php print_string('searchcombined','message') ?>" />
-            <a href="index.php?usergroup=<?php echo VIEW_SEARCH ?>&advanced=1" id="advancedcontactsearchlink"><?php print_string('advanced') ?></a>
+            <a href="index.php?usergroup=<?php echo MESSAGE_VIEW_SEARCH ?>&advanced=1" id="advancedcontactsearchlink"><?php print_string('advanced') ?></a>
         </td>
     </tr>
 </table>
index e6fffc8..1eac7dd 100644 (file)
@@ -2,7 +2,7 @@
 <div id="advancedcontactssearchspan">
 <table cellpadding="5" class="message_form mdl-left">
     <tr>
-        <td colspan="3" class="message_heading mdl-left"><div id="hideadvancedsearch" class="mdl-align"><a href="index.php?usergroup=<?php echo VIEW_SEARCH ?>" id="combinedcontactsearchlink"><?php print_string('hideadvanced','form') ?></a></div></td>
+        <td colspan="3" class="message_heading mdl-left"><div id="hideadvancedsearch" class="mdl-align"><a href="index.php?usergroup=<?php echo MESSAGE_VIEW_SEARCH ?>" id="combinedcontactsearchlink"><?php print_string('hideadvanced','form') ?></a></div></td>
     </tr>
     <tr>
         <td colspan="3" class="message_heading mdl-left"><p class="heading"><?php echo get_string('searchforperson', 'message') ?></p></td>
index 7817e98..1efe7a8 100644 (file)
@@ -462,32 +462,43 @@ class quiz_attempt extends quiz {
     }
 
     /**
-     * Static function to create a new quiz_attempt object given an attemptid.
-     *
-     * @param integer $attemptid the attempt id.
-     * @return quiz_attempt the new quiz_attempt object
+     * Used by {create()} and {create_from_usage_id()}.
+     * @param array $conditions passed to $DB->get_record('quiz_attempts', $conditions).
      */
-    static public function create($attemptid) {
+    static protected function create_helper($conditions) {
         global $DB;
 
-        if (!$attempt = quiz_load_attempt($attemptid)) {
-            throw new moodle_exception('invalidattemptid', 'quiz');
-        }
-        if (!$quiz = $DB->get_record('quiz', array('id' => $attempt->quiz))) {
-            throw new moodle_exception('invalidquizid', 'quiz');
-        }
-        if (!$course = $DB->get_record('course', array('id' => $quiz->course))) {
-            throw new moodle_exception('invalidcoursemodule');
-        }
-        if (!$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id)) {
-            throw new moodle_exception('invalidcoursemodule');
-        }
+        $attempt = $DB->get_record('quiz_attempts', $conditions, '*', MUST_EXIST);
+        $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz), '*', MUST_EXIST);
+        $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
+        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id, false, MUST_EXIST);
+
         // Update quiz with override information
         $quiz = quiz_update_effective_access($quiz, $attempt->userid);
 
         return new quiz_attempt($attempt, $quiz, $cm, $course);
     }
 
+    /**
+     * Static function to create a new quiz_attempt object given an attemptid.
+     *
+     * @param int $attemptid the attempt id.
+     * @return quiz_attempt the new quiz_attempt object
+     */
+    static public function create($attemptid) {
+        return self::create_helper(array('id' => $attemptid));
+    }
+
+    /**
+     * Static function to create a new quiz_attempt object given a usage id.
+     *
+     * @param int $usageid the attempt usage id.
+     * @return quiz_attempt the new quiz_attempt object
+     */
+    static public function create_from_unique_id($usageid) {
+        return self::create_helper(array('uniqueid' => $usageid));
+    }
+
     // Functions for loading more data =====================================================
     /**
      * Load the state of a number of questions that have already been loaded.
index 353cc16..a57bb93 100644 (file)
@@ -89,7 +89,7 @@ $string['aon'] = 'AON format';
 $string['areyousureremoveselected'] = 'Are you sure you want to remove all the selected questions?';
 $string['asshownoneditscreen'] = 'As shown on the edit screen';
 $string['attempt'] = 'Attempt {$a}';
-$string['attemptalreadyclosed'] = 'This attempt has already be finished.';
+$string['attemptalreadyclosed'] = 'This attempt has already been finished.';
 $string['attemptclosed'] = 'Attempt has not closed yet';
 $string['attemptduration'] = 'Time taken';
 $string['attemptedon'] = 'Attempted on';
index 0030c2a..ac8e09b 100644 (file)
@@ -1770,11 +1770,11 @@ function quiz_pluginfile($course, $cm, $context, $filearea, $args, $forcedownloa
  * @return bool false if file not found, does not return if found - justsend the file
  */
 function quiz_question_pluginfile($course, $context, $component,
-        $filearea, $attemptid, $questionid, $args, $forcedownload) {
+        $filearea, $uniqueid, $questionid, $args, $forcedownload) {
     global $USER, $CFG;
     require_once($CFG->dirroot . '/mod/quiz/locallib.php');
 
-    $attemptobj = quiz_attempt::create($attemptid);
+    $attemptobj = quiz_attempt::create_from_unique_id($uniqueid);
     require_login($attemptobj->get_courseid(), false, $attemptobj->get_cm());
     $questionids = array($questionid);
     $attemptobj->load_questions($questionids);
index fa892be..0da3ec6 100644 (file)
@@ -344,17 +344,26 @@ class mod_quiz_mod_form extends moodleform_mod {
             foreach ($this->_feedbacks as $feedback){
                 $draftid = file_get_submitted_draft_itemid('feedbacktext['.$key.']');
                 $default_values['feedbacktext['.$key.']']['text'] = file_prepare_draft_area(
-                    $draftid,       // draftid
-                    $this->context->id,    // context
-                    'mod_quiz',   // component
+                    $draftid,               // draftid
+                    $this->context->id,     // context
+                    'mod_quiz',             // component
                     'feedback',             // filarea
-                    !empty($feedback->id)?(int)$feedback->id:null, // itemid
+                    !empty($feedback->id) ? (int) $feedback->id : null, // itemid
                     null,
-                    $feedback->feedbacktext      // text
+                    $feedback->feedbacktext // text
                 );
                 $default_values['feedbacktext['.$key.']']['format'] = $feedback->feedbacktextformat;
                 $default_values['feedbacktext['.$key.']']['itemid'] = $draftid;
 
+                if ($default_values['grade'] == 0) {
+                    // When a quiz is un-graded, there can only be one lot of
+                    // feedback. If the quiz previously had a maximum grade and
+                    // several lots of feedback, we must now avoid putting text
+                    // into input boxes that are disabled, but which the
+                    // validation will insist are blank.
+                    break;
+                }
+
                 if ($feedback->mingrade > 0) {
                     $default_values['feedbackboundaries['.$key.']'] = (100.0 * $feedback->mingrade / $default_values['grade']) . '%';
                 }
index 240de7b..105d758 100644 (file)
@@ -40,7 +40,7 @@
             redirect($attemptobj->attempt_url(0, $page));
         } else if (!$options->responses) {
             $accessmanager->back_to_view_page($attemptobj->is_preview_user(),
-                    $accessmanager->cannot_review_message($attemptobj->get_attempt_state()));
+                    $accessmanager->cannot_review_message($options));
         }
 
     } else if (!$attemptobj->is_review_allowed()) {
index 145ed5f..099480d 100644 (file)
@@ -74,7 +74,7 @@ if ($lastattempt && !$lastattempt->timefinish) {
 
 /// Get number for the next or unfinished attempt
 if ($lastattempt && !$lastattempt->preview && !$quizobj->is_preview_user()) {
-    $lastattemptid = $lastattempt->id;
+    $lastattemptid = $lastattempt->uniqueid;
     $attemptnumber = $lastattempt->attempt + 1;
 } else {
     $lastattempt = false;
index 86b3de4..ed08cf4 100644 (file)
@@ -93,7 +93,7 @@
                         if ($sco = scorm_get_sco($scoid)) {
                             $userdata->course_id = $sco->identifier;
                             $userdata->datafromlms = isset($sco->datafromlms)?$sco->datafromlms:'';
-                            $userdata->mastery_score = isset($sco->mastery_score)?$sco->mastery_score:'';
+                            $userdata->mastery_score = isset($sco->mastery_score) && is_numeric($sco->mastery_score)?trim($sco->mastery_score):'';
                             $userdata->max_time_allowed = isset($sco->max_time_allowed)?$sco->max_time_allowed:'';
                             $userdata->time_limit_action = isset($sco->time_limit_action)?$sco->time_limit_action:'';
 
                             }
                             if ($mode == 'normal') {
                                 if ($sco = scorm_get_sco($scoid)) {
-                                    if (!empty($sco->mastery_score)) {
-                                        if (!empty($score)) {
-                                            if ($score >= $sco->mastery_score) {
+                                    if (isset($sco->mastery_score) && is_numeric($sco->mastery_score)) {
+                                        if ($score != '') { // $score is correctly initialized w/ an empty string, see above
+                                            if ($score >= trim($sco->mastery_score)) {
                                                 $lessonstatus = 'passed';
                                             } else {
                                                 $lessonstatus = 'failed';
index 1e292b1..5b23e64 100644 (file)
@@ -42,7 +42,7 @@ $capabilities = array(
         )
     ),
 
-    // Ability to change the current phase (stage) of the workshop, it est for example
+    // Ability to change the current phase (stage) of the workshop, for example
     // allow submitting, start assessment period, close workshop etc.
     'mod/workshop:switchphase' => array(
         'captype' => 'write',
@@ -110,7 +110,7 @@ $capabilities = array(
         )
     ),
 
-    // Ability to publish submissions, it est make them available when workshop is closed
+    // Ability to publish submissions, i.e. make them available when workshop is closed
     'mod/workshop:publishsubmissions' => array(
         'captype' => 'write',
         'contextlevel' => CONTEXT_MODULE,
@@ -134,7 +134,7 @@ $capabilities = array(
         )
     ),
 
-    // Ability to identify the reviewer of the given submission (ie the owner of the assessment)
+    // Ability to identify the reviewer of the given submission (i.e. the owner of the assessment)
     'mod/workshop:viewreviewernames' => array(
         'captype' => 'read',
         'contextlevel' => CONTEXT_MODULE,
@@ -170,6 +170,18 @@ $capabilities = array(
         )
     ),
 
+    // Ability to view the authors of published submissions.
+    'mod/workshop:viewauthorpublished' => array(
+        'captype' => 'read',
+        'contextlevel' => CONTEXT_MODULE,
+        'archetypes' => array(
+            'student' => CAP_ALLOW,
+            'teacher' => CAP_ALLOW,
+            'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
+        )
+    ),
+
     // Ability to always view the assessments of other users' work and the calculated grades, regardless the phase.
     // Applies to the user's group only or - if the user is allowed to access all groups - applies to any assessment
     'mod/workshop:viewallassessments' => array(
index 813235a..0857396 100644 (file)
@@ -178,7 +178,7 @@ class workshop_best_evaluation implements workshop_evaluation {
             $distances[$asid] = $this->assessments_distance($assessment, $average, $diminfo, $settings);
         }
 
-        // identify the best assessments - it est those with the shortest distance from the best assessment
+        // identify the best assessments - that is those with the shortest distance from the best assessment
         $bestids = array_keys($distances, min($distances));
 
         // for every assessment, calculate its distance from the nearest best assessment
index 1e7237e..aa73a52 100644 (file)
@@ -63,7 +63,7 @@ function workshopform_accumulative_upgrade_legacy() {
         // refetch them from DB so that this function can be called during recovery
         $newelementids = workshop_upgrade_element_id_mappings('accumulative');
 
-        // migrate all grades for these elements (it est the values that reviewers put into forms)
+        // migrate all grades for these elements (i.e. the values that reviewers put into forms)
         echo $OUTPUT->notification('Copying assessment form grades', 'notifysuccess');
         $sql = "SELECT *
                   FROM {workshop_grades_old}
index ee45f2a..f870422 100644 (file)
@@ -60,7 +60,7 @@ function workshopform_comments_upgrade_legacy() {
         // now we need to reload the legacy element ids
         $newelementids = workshop_upgrade_element_id_mappings('comments');
 
-        // migrate all comments for these elements (it est the values that reviewers put into forms)
+        // migrate all comments for these elements (i.e. the values that reviewers put into forms)
         echo $OUTPUT->notification('Copying assessment form comments', 'notifysuccess');
         $sql = "SELECT *
                   FROM {workshop_grades_old}
index e27d61e..364e9e0 100644 (file)
@@ -83,7 +83,7 @@ function workshopform_numerrors_upgrade_legacy() {
         // refetch them from DB so that this function can be called during recovery
         $newelementids = workshop_upgrade_element_id_mappings('numerrors');
 
-        // migrate all grades for these elements (it est the values that reviewers put into forms)
+        // migrate all grades for these elements (i.e. the values that reviewers put into forms)
         echo $OUTPUT->notification('Copying assessment form grades', 'notifysuccess');
         $sql = "SELECT *
                   FROM {workshop_grades_old}
index f43d175..123160b 100644 (file)
@@ -556,7 +556,7 @@ class workshop_numerrors_strategy implements workshop_strategy {
         if (empty($grades)) {
             return null;
         }
-        $sumerrors  = 0;    // sum of the weighted errors (ie the negative responses)
+        $sumerrors  = 0;    // sum of the weighted errors (i.e. the negative responses)
         foreach ($grades as $grade) {
             if (grade_floats_different($grade->grade, 1.00000)) {
                 // negative reviewer's response
index 50c4fbd..c41a4ac 100644 (file)
@@ -92,7 +92,7 @@ function workshopform_rubric_upgrade_legacy_criterion() {
         // reload the mappings - this must be reloaded to that we can run this during recovery
         $newelementids = workshop_upgrade_element_id_mappings('rubric_levels');
 
-        // migrate all grades for these elements (it est the values that reviewers put into forms)
+        // migrate all grades for these elements (i.e. the values that reviewers put into forms)
         echo $OUTPUT->notification('Copying criterion assessment form grades', 'notifysuccess');
         $sql = "SELECT *
                   FROM {workshop_grades_old}
@@ -199,7 +199,7 @@ function workshopform_rubric_upgrade_legacy_rubric() {
         unset($oldweights);
         unset($element);
 
-        // migrate all grades for these elements (it est the values that reviewers put into forms)
+        // migrate all grades for these elements (i.e. the values that reviewers put into forms)
         echo $OUTPUT->notification('Copying rubric assessment form grades', 'notifysuccess');
         $sql = "SELECT *
                   FROM {workshop_grades_old}
index b1fb16f..d68a95c 100644 (file)
@@ -254,6 +254,7 @@ $string['workshop:view'] = 'View workshop';
 $string['workshop:viewallassessments'] = 'View all assessments';
 $string['workshop:viewallsubmissions'] = 'View all submissions';
 $string['workshop:viewauthornames'] = 'View author names';
+$string['workshop:viewauthorpublished'] = 'View authors of published submissions';
 $string['workshop:viewpublishedsubmissions'] = 'View published submissions';
 $string['workshop:viewreviewernames'] = 'View reviewer names';
 $string['yoursubmission'] = 'Your submission';
index 0f943af..e3aba61 100644 (file)
@@ -458,7 +458,7 @@ class workshop {
     }
 
     /**
-     * Returns the list of all allocations (it est assigned assessments) in the workshop
+     * Returns the list of all allocations (i.e. assigned assessments) in the workshop
      *
      * Assessments of example submissions are ignored
      *
@@ -721,7 +721,7 @@ class workshop {
      * Returns the list of all assessments in the workshop with some data added
      *
      * Fetches data from {workshop_assessments} and adds some useful information from other
-     * tables. The returned object does not contain textual fields (ie comments) to prevent memory
+     * tables. The returned object does not contain textual fields (i.e. comments) to prevent memory
      * lack issues.
      *
      * @return array [assessmentid] => assessment stdclass
@@ -2397,7 +2397,7 @@ class workshop_user_plan implements renderable {
  */
 abstract class workshop_submission_base {
 
-    /** @var bool is the submission anonymous (ie contains author information) */
+    /** @var bool is the submission anonymous (i.e. contains author information) */
     protected $anonymous;
 
     /* @var array of columns from workshop_submissions that are assigned as properties */
index 3c65483..22c4851 100644 (file)
     margin: 0.75em auto;
 }
 
+.path-mod-workshop #workshop-viewlet-assignedassessments div.singlebutton,
+.path-mod-workshop #workshop-viewlet-allexamples div.singlebutton,
+.path-mod-workshop #workshop-viewlet-examples div.singlebutton {
+    text-align: left;
+}
+
 /**
  * Submission - one line summary display
  */
index a0766e6..81b5e9a 100644 (file)
@@ -72,6 +72,9 @@ $canoverride    = (($workshop->phase == workshop::PHASE_EVALUATION) and has_capa
 $userassessment = $workshop->get_assessment_of_submission_by_user($submission->id, $USER->id);
 $isreviewer     = !empty($userassessment);
 $editable       = ($cansubmit and $ownsubmission);
+$ispublished    = ($workshop->phase == workshop::PHASE_CLOSED
+                    and $submission->published == 1
+                    and has_capability('mod/workshop:viewpublishedsubmissions', $workshop->context));
 
 if (empty($submission->id) and !$workshop->creating_submission_allowed()) {
     $editable = false;
@@ -93,8 +96,13 @@ if ($editable and $workshop->useexamples and $workshop->examplesmode == workshop
 }
 $edit = ($editable and $edit);
 
+$seenaspublished = false; // is the submission seen as a published submission?
+
 if ($submission->id and ($ownsubmission or $canviewall or $isreviewer)) {
     // ok you can go
+} elseif ($submission->id and $ispublished) {
+    // ok you can go
+    $seenaspublished = true;
 } elseif (is_null($submission->id) and $cansubmit) {
     // ok you can go
 } else {
@@ -238,7 +246,12 @@ if ($edit) {
 // else display the submission
 
 if ($submission->id) {
-    echo $output->render($workshop->prepare_submission($submission, has_capability('mod/workshop:viewauthornames', $workshop->context)));
+    if ($seenaspublished) {
+        $showauthor = has_capability('mod/workshop:viewauthorpublished', $workshop->context);
+    } else {
+        $showauthor = has_capability('mod/workshop:viewauthornames', $workshop->context);
+    }
+    echo $output->render($workshop->prepare_submission($submission, $showauthor));
 } else {
     echo $output->box(get_string('noyoursubmission', 'workshop'));
 }
index 11348c1..54bdc72 100644 (file)
@@ -29,6 +29,6 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version  = 2011021100;
+$module->version  = 2011030400;
 $module->requires = 2011020900;  // Requires this Moodle version
 //$module->cron     = 60;
index a13f97f..2cd32a0 100644 (file)
@@ -409,7 +409,7 @@ case workshop::PHASE_EVALUATION:
         print_collapsible_region_start('', 'workshop-viewlet-ownsubmission', get_string('yoursubmission', 'workshop'));
         echo $output->box_start('generalbox ownsubmission');
         if ($submission = $workshop->get_submission_by_author($USER->id)) {
-            echo $output->render(new workshop_submission_summary($submission, true));
+            echo $output->render($workshop->prepare_submission_summary($submission, true));
         } else {
             echo $output->container(get_string('noyoursubmission', 'workshop'));
         }
@@ -492,16 +492,48 @@ case workshop::PHASE_CLOSED:
         print_collapsible_region_end();
     }
     if (has_capability('mod/workshop:viewpublishedsubmissions', $workshop->context)) {
+        $shownames = has_capability('mod/workshop:viewauthorpublished', $workshop->context);
         if ($submissions = $workshop->get_published_submissions()) {
             print_collapsible_region_start('', 'workshop-viewlet-publicsubmissions', get_string('publishedsubmissions', 'workshop'));
             foreach ($submissions as $submission) {
                 echo $output->box_start('generalbox submission-summary');
-                echo $output->render($workshop->prepare_submission_summary($submission, true));
+                echo $output->render($workshop->prepare_submission_summary($submission, $shownames));
                 echo $output->box_end();
             }
             print_collapsible_region_end();
         }
     }
+    if ($assessments = $workshop->get_assessments_by_reviewer($USER->id)) {
+        print_collapsible_region_start('', 'workshop-viewlet-assignedassessments', get_string('assignedassessments', 'workshop'));
+        $shownames = has_capability('mod/workshop:viewauthornames', $PAGE->context);
+        foreach ($assessments as $assessment) {
+            $submission                     = new stdclass();
+            $submission->id                 = $assessment->submissionid;
+            $submission->title              = $assessment->submissiontitle;
+            $submission->timecreated        = $assessment->submissioncreated;
+            $submission->timemodified       = $assessment->submissionmodified;
+            $submission->authorid           = $assessment->authorid;
+            $submission->authorfirstname    = $assessment->authorfirstname;
+            $submission->authorlastname     = $assessment->authorlastname;
+            $submission->authorpicture      = $assessment->authorpicture;
+            $submission->authorimagealt     = $assessment->authorimagealt;
+            $submission->authoremail        = $assessment->authoremail;
+
+            if (is_null($assessment->grade)) {
+                $class = ' notgraded';
+                $submission->status = 'notgraded';
+                $buttontext = get_string('assess', 'workshop');
+            } else {
+                $class = ' graded';
+                $submission->status = 'graded';
+                $buttontext = get_string('reassess', 'workshop');
+            }
+            echo $output->box_start('generalbox assessment-summary' . $class);
+            echo $output->render($workshop->prepare_submission_summary($submission, $shownames));
+            echo $output->box_end();
+        }
+        print_collapsible_region_end();
+    }
     break;
 default:
 }
index 102b26f..8b77589 100644 (file)
@@ -87,7 +87,8 @@ class question_essay_qtype extends default_questiontype {
         $feedback = '';
         if ($options->feedback && !empty($answers)) {
             foreach ($answers as $answer) {
-                $feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php', $context->id, 'qtype_essay', 'feedback', array($state->attempt, $state->question), $answer->id);
+                $feedback = quiz_rewrite_question_urls($answer->feedback, 'pluginfile.php',
+                        $context->id, 'question', 'answerfeedback', array($state->attempt, $state->question), $answer->id);
                 $feedback = format_text($feedback, $answer->feedbackformat, $formatoptions, $cmoptions->course);
             }
         }
@@ -127,6 +128,16 @@ class question_essay_qtype extends default_questiontype {
         return true;
     }
 
+    /**
+     * @param string response is a response.
+     * @return formatted response
+     */
+    function format_response($response, $format) {
+        $safeformatoptions = new stdClass();
+        $safeformatoptions->para = false;
+        return s(html_to_text(format_text($response, FORMAT_MOODLE, $safeformatoptions), 0, false));
+    }
+
     /**
      * Runs all the code required to set up and save an essay question for testing purposes.
      * Alternate DB table prefix may be used to facilitate data deletion.
index 012cc43..ff1b340 100644 (file)
@@ -166,10 +166,6 @@ class embedded_cloze_qtype extends default_questiontype {
         }
 
         $question->category = $authorizedquestion->category;
-        $form->course = $course; // To pass the course object to
-                                 // save_question_options, where it is
-                                 // needed to call type specific
-                                 // save_question methods.
         $form->defaultgrade = $question->defaultgrade;
         $form->questiontext = $question->questiontext;
         $form->questiontextformat = 0;
@@ -599,6 +595,37 @@ class embedded_cloze_qtype extends default_questiontype {
         echo '</div>';
     }
 
+    public function compare_responses($question, $state, $teststate) {
+        global $QTYPES;
+
+        foreach ($question->options->questions as $key => $wrapped) {
+            if (empty($wrapped)) {
+                continue;
+            }
+
+            $stateforquestion = clone($state);
+            if (isset($state->responses[$key])) {
+                $stateforquestion->responses[''] = $state->responses[$key];
+            } else {
+                $stateforquestion->responses[''] = '';
+            }
+
+            $teststateforquestion = clone($teststate);
+            if (isset($teststate->responses[$key])) {
+                $teststateforquestion->responses[''] = $teststate->responses[$key];
+            } else {
+                $teststateforquestion->responses[''] = '';
+            }
+
+            if (!$QTYPES[$wrapped->qtype]->compare_responses($wrapped,
+                    $stateforquestion, $teststateforquestion)) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     function grade_responses(&$question, &$state, $cmoptions) {
         global $QTYPES;
         $teststate = clone($state);
index 12d678d..b8b9704 100644 (file)
@@ -63,9 +63,9 @@
     ?> size="<?php echo $textlength;?>"/>
     <?php if ($state->responses['unit'] != '') echo $feedbackimgunit; 
     if ($options->feedback &&  $question->options->unitgradingtype == 1 && ! $valid_numerical_unit && ! $answerasterisk ){
-        if ( $empty_unit) {
+        if (!empty($empty_unit)) {
             print_string('unitmandatory', 'qtype_numerical'); 
-        }else {
+        } else {
             if(isset($question->options->units) && count($question->options->units) > 0){
                 $found = 0 ;
                 $valid_unit_found = 0 ;
index ebc37b8..ef0b1f0 100644 (file)
@@ -608,7 +608,7 @@ class question_numerical_qtype extends question_shortanswer_qtype {
             if($question->options->unitgradingtype == 1){
                 $raw_unitpenalty = $question->options->unitpenalty * $rawgrade ;
             }else {
-                $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade;
+                $raw_unitpenalty = $question->options->unitpenalty ;
             }
             $state->options->raw_unitpenalty = $raw_unitpenalty ;
         }
@@ -818,7 +818,7 @@ class question_numerical_qtype extends question_shortanswer_qtype {
             if($question->options->unitgradingtype == 1){
                 $raw_unitpenalty = $question->options->unitpenalty * $state->raw_grade ;
             }else {
-                $raw_unitpenalty = $question->options->unitpenalty * $question->maxgrade;
+                $raw_unitpenalty = $question->options->unitpenalty ;
             }
             $state->raw_grade -= $raw_unitpenalty ;
         }
index 11843cd..e5dacab 100644 (file)
@@ -1348,13 +1348,15 @@ class default_questiontype {
         }
         return $toreturn;
     }
+
     /**
      * @param string response is a response.
      * @return formatted response
      */
-    function format_response($response, $format){
-        return s($response);
+    function format_response($response, $format) {
+        return s(html_to_text($this->format_text($response, $format), 0, false));
     }
+
     /**
     * Renders the question for printing and returns the LaTeX source produced
     *
index 72c5aab..0ecefd4 100644 (file)
@@ -260,6 +260,12 @@ class random_qtype extends default_questiontype {
                 $wrappedquestion->qtype = 'missingtype';
             }
             $state->responses[''] = substr($state->responses[''], strlen('random' . $questionid . '-'));
+            if ($state->responses[''] === false) {
+                // In PHP, if $response === $prefix, then
+                // substr($response, strlen($prefix)) returns false, not '',
+                // which is stupid, and caused MDL-26520. Fix up that case here.
+                $state->responses[''] = '';
+            }
         }
 
         if (!$QTYPES[$wrappedquestion->qtype]
index 1388f5e..bd84dc1 100644 (file)
@@ -223,6 +223,14 @@ class question_shortanswer_qtype extends default_questiontype {
         return preg_match($regexp, trim($string));
     }
 
+    /**
+     * @param string response is a response.
+     * @return formatted response
+     */
+    function format_response($response, $format){
+        return s($response);
+    }
+
     /*
      * Override the parent class method, to remove escaping from asterisks.
      */
@@ -318,7 +326,7 @@ class question_shortanswer_qtype extends default_questiontype {
                 // waiting for the new question engine code for a permanent one
                 if(isset($state->options->raw_unitpenalty) && $state->options->raw_unitpenalty > 0.0 ){
                     echo ' ';
-                    print_string('unitappliedpenalty','qtype_numerical',question_format_grade($cmoptions, $state->options->raw_unitpenalty ));
+                    print_string('unitappliedpenalty','qtype_numerical',question_format_grade($cmoptions, $state->options->raw_unitpenalty * $question->maxgrade ));
                 }
                 if ($cmoptions->penaltyscheme) {
                     // print details of grade adjustment due to penalties
index 36b5694..7beb53b 100644 (file)
@@ -23,6 +23,9 @@
 #page-admin-index .adminerror,
 #page-admin-index .adminwarning {margin:20px;}
 
+#page-admin-index .maturitywarning {margin-left:auto;margin-right:auto;text-align:center;width:60%;background-color:#ffd3d9;}
+#page-admin-index .releasenoteslink {margin-left:auto;margin-right:auto;text-align:center;width:60%;}
+
 #page-admin-enrol .enrolplugintable {width:700px;margin:1em auto;}
 
 #page-admin-report-capability-index #settingsform h2,
@@ -144,4 +147,4 @@ table.flexible .r1 {background-color: #FAFAFA;}
 */
 #page-admin-webservice-service_users .missingcaps {color: #ff6600;font-size: 90%;}
 #page-admin-setting-webservicetokens .missingcaps {color: #ff6600;font-size: 90%;}
-#page-admin-webservice-service_functions .functiondesc {font-size: 90%;}
\ No newline at end of file
+#page-admin-webservice-service_functions .functiondesc {font-size: 90%;}
index a373051..8559d19 100644 (file)
@@ -63,8 +63,10 @@ if (!$currentuser &&
     !empty($CFG->forceloginforprofiles) &&
     !has_capability('moodle/user:viewdetails', $context) &&
     !has_coursecontact_role($userid)) {
+
     // Course managers can be browsed at site level. If not forceloginforprofiles, allow access (bug #4366)
     $struser = get_string('user');
+    $PAGE->set_context(get_context_instance(CONTEXT_SYSTEM));
     $PAGE->set_title("$SITE->shortname: $struser");  // Do not leak the name
     $PAGE->set_heading("$SITE->shortname: $struser");
     $PAGE->set_url('/user/profile.php', array('id'=>$userid));
@@ -247,9 +249,10 @@ if (has_capability('moodle/user:viewhiddendetails', $context)) {
     }
 }
 
-if ($user->maildisplay == 1
-   or ($user->maildisplay == 2 && !isguestuser())
-   or has_capability('moodle/course:useremail', $context)) {
+if ($currentuser
+  or $user->maildisplay == 1
+  or has_capability('moodle/course:useremail', $context)
+  or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER))) {
 
     print_row(get_string("email").":", obfuscate_mailto($user->email, ''));
 }
index 67c36ba..265ab74 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
-$version = 2011030300.00;  // YYYYMMDD   = date of the last version bump
-                        //         XX = daily increments
 
-$release = '2.0.2+ (Build: 20110303)';  // Human-friendly version name
+$version  = 2011030900.00;              // YYYYMMDD   = date of the last version bump
+                                        //         XX = daily increments
+
+$release  = '2.0.2+ (Build: 20110309)'; // Human-friendly version name
+
+$maturity = MATURITY_STABLE;            // this version's maturity level