Merge branch 'MDL-27818_23' of git://github.com/timhunt/moodle into MOODLE_23_STABLE
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 3 Sep 2012 23:07:51 +0000 (01:07 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 3 Sep 2012 23:07:51 +0000 (01:07 +0200)
49 files changed:
admin/environment.xml
backup/moodle2/backup_custom_fields.php
backup/upgrade.txt [new file with mode: 0644]
backup/util/dbops/backup_plan_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/plan/backup_structure_step.class.php
backup/util/plan/restore_structure_step.class.php
backup/util/structure/backup_nested_element.class.php
backup/util/ui/backup_ui_stage.class.php
backup/util/ui/restore_ui_stage.class.php
blocks/completionstatus/block_completionstatus.php
blocks/completionstatus/details.php
blocks/completionstatus/lang/en/block_completionstatus.php
blog/locallib.php
install/lang/lt/admin.php
lang/en/admin.php
lang/en/backup.php
lang/en/moodle.php
lib/db/upgradelib.php
lib/moodlelib.php
lib/pluginlib.php
lib/tests/moodlelib_test.php
lib/tests/pluginlib_test.php
mod/data/field/checkbox/mod.html
mod/data/field/latlong/field.class.php
mod/data/field/menu/mod.html
mod/data/field/multimenu/mod.html
mod/data/field/picture/field.class.php
mod/data/field/picture/mod.html
mod/data/field/radiobutton/mod.html
mod/data/field/textarea/mod.html
mod/data/lib.php
mod/data/styles.css
mod/data/templates.php
mod/lesson/format.php
mod/quiz/styles.css
mod/workshop/form/comments/backup/moodle1/lib.php
mod/workshop/form/numerrors/backup/moodle1/lib.php
mod/workshop/form/rubric/backup/moodle1/lib.php
question/type/multichoice/styles.css
report/backups/index.php
report/stats/lib.php
theme/afterburner/style/afterburner_styles.css
theme/anomaly/style/general.css
theme/base/style/admin.css
theme/yui_combo.php
user/profile.php
version.php

index 004cab0..584a9b0 100644 (file)
         </FEEDBACK>
       </PHP_SETTING>
     </PHP_SETTINGS>
-</MOODLE>
+  </MOODLE>
+  <MOODLE version="2.4" requires="2.2">
+    <UNICODE level="required">
+      <FEEDBACK>
+        <ON_ERROR message="unicoderequired" />
+      </FEEDBACK>
+    </UNICODE>
+    <DATABASE level="required">
+      <VENDOR name="mysql" version="5.1.33" />
+      <VENDOR name="postgres" version="8.3" />
+      <VENDOR name="mssql" version="9.0" />
+      <VENDOR name="odbc_mssql" version="9.0" />
+      <VENDOR name="mssql_n" version="9.0" />
+      <VENDOR name="oracle" version="10.2" />
+      <VENDOR name="sqlite" version="2.0" />
+    </DATABASE>
+    <PHP version="5.3.2" level="required">
+    </PHP>
+    <PCREUNICODE level="optional">
+      <FEEDBACK>
+        <ON_CHECK message="pcreunicodewarning" />
+      </FEEDBACK>
+    </PCREUNICODE>
+    <PHP_EXTENSIONS>
+      <PHP_EXTENSION name="iconv" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="iconvrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="mbstring" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="mbstringrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="curl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="curlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="openssl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="opensslrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="tokenizer" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="tokenizerrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xmlrpc" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="xmlrpcrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="soap" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="soaprecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="ctype" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ctyperequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="zip" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="ziprequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="gd" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="gdrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="simplexml" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="simplexmlrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="spl" level="required">
+        <FEEDBACK>
+          <ON_CHECK message="splrequired" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="pcre" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="dom" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="xml" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="intl" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="intlrecommended" />
+        </FEEDBACK>
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="json" level="required">
+      </PHP_EXTENSION>
+      <PHP_EXTENSION name="hash" level="required"/>
+    </PHP_EXTENSIONS>
+    <PHP_SETTINGS>
+      <PHP_SETTING name="memory_limit" value="40M" level="required">
+        <FEEDBACK>
+          <ON_ERROR message="settingmemorylimit" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="safe_mode" value="0" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingsafemode" />
+        </FEEDBACK>
+      </PHP_SETTING>
+      <PHP_SETTING name="file_uploads" value="1" level="optional">
+        <FEEDBACK>
+          <ON_CHECK message="settingfileuploads" />
+        </FEEDBACK>
+      </PHP_SETTING>
+    </PHP_SETTINGS>
+  </MOODLE>
 </COMPATIBILITY_MATRIX>
index e0ceb33..12929de 100644 (file)
@@ -96,14 +96,19 @@ class file_nested_element extends backup_nested_element {
         if (is_null($this->backupid)) {
             $this->backupid = $processor->get_var(backup::VAR_BACKUPID);
         }
-        parent::process($processor);
+        return parent::process($processor);
     }
 
     public function fill_values($values) {
         // Fill values
         parent::fill_values($values);
         // Do our own tasks (copy file from moodle to backup)
-        backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
+        try {
+            backup_file_manager::copy_file_moodle2backup($this->backupid, $values);
+        } catch (file_exception $e) {
+            $this->add_result(array('missing_files_in_pool' => true));
+            $this->add_log('missing file in pool: ' . $e->debuginfo, backup::LOG_WARNING);
+        }
     }
 }
 
diff --git a/backup/upgrade.txt b/backup/upgrade.txt
new file mode 100644 (file)
index 0000000..cb02095
--- /dev/null
@@ -0,0 +1,17 @@
+This files describes API changes in /backup/*,
+information provided here is intended especially for developers.
+
+=== 2.4 ===
+
+* Since 2.3.1+ the backup file name schema has changed. The ID of the course will always be part of
+    the filename regardless of the setting 'backup_shortname'. See MDL-33812.
+
+=== 2.3 ===
+
+* Since 2.3.1+ the backup file name schema has changed. The ID of the course will always be part of
+    the filename regardless of the setting 'backup_shortname'. See MDL-33812.
+
+=== 2.2 ===
+
+* Since 2.2.4+ the backup file name schema has changed. The ID of the course will always be part of
+    the filename regardless of the setting 'backup_shortname'. See MDL-33812.
\ No newline at end of file
index 30714ed..0752f0d 100644 (file)
@@ -197,19 +197,19 @@ abstract class backup_plan_dbops extends backup_dbops {
     * @param int $courseid/$sectionid/$cmid
     * @param bool $users Should be true is users were included in the backup
     * @param bool $anonymised Should be true is user information was anonymized.
-    * @param bool $useidasname true to use id, false to use strings (default)
+    * @param bool $useidonly only use the ID in the file name
     * @return string The filename to use
     */
-    public static function get_default_backup_filename($format, $type, $id, $users, $anonymised, $useidasname = false) {
+    public static function get_default_backup_filename($format, $type, $id, $users, $anonymised, $useidonly = false) {
         global $DB;
 
         // Calculate backup word
         $backupword = str_replace(' ', '_', textlib::strtolower(get_string('backupfilename')));
         $backupword = trim(clean_filename($backupword), '_');
 
+        // Not $useidonly, lets fetch the name
         $shortname = '';
-        // Not $useidasname, lets calculate it, else $id will be used
-        if (!$useidasname) {
+        if (!$useidonly) {
             // Calculate proper name element (based on type)
             switch ($type) {
                 case backup::TYPE_1COURSE:
@@ -231,7 +231,11 @@ abstract class backup_plan_dbops extends backup_dbops {
             $shortname = textlib::strtolower(trim(clean_filename($shortname), '_'));
         }
 
-        $name = empty($shortname) ? $id : $shortname;
+        // The name will always contain the ID, but we append the course short name if requested.
+        $name = $id;
+        if (!$useidonly && $shortname != '') {
+            $name .= '-' . $shortname;
+        }
 
         // Calculate date
         $backupdateformat = str_replace(' ', '_', get_string('backupnameformat', 'langconfig'));
index 2e73e8f..fb7beb3 100644 (file)
@@ -819,10 +819,13 @@ abstract class restore_dbops {
      * @param int|null $olditemid
      * @param int|null $forcenewcontextid explicit value for the new contextid (skip mapping)
      * @param bool $skipparentitemidctxmatch
+     * @return array of result object
      */
     public static function send_files_to_pool($basepath, $restoreid, $component, $filearea, $oldcontextid, $dfltuserid, $itemname = null, $olditemid = null, $forcenewcontextid = null, $skipparentitemidctxmatch = false) {
         global $DB;
 
+        $results = array();
+
         if ($forcenewcontextid) {
             // Some components can have "forced" new contexts (example: questions can end belonging to non-standard context mappings,
             // with questions originally at system/coursecat context in source being restored to course context in target). So we need
@@ -902,8 +905,14 @@ abstract class restore_dbops {
                 // this is a regular file, it must be present in the backup pool
                 $backuppath = $basepath . backup_file_manager::get_backup_content_file_location($file->contenthash);
 
+                // The file is not found in the backup.
                 if (!file_exists($backuppath)) {
-                    throw new restore_dbops_exception('file_not_found_in_pool', $file);
+                    $result = new stdClass();
+                    $result->code = 'file_missing_in_backup';
+                    $result->message = sprintf('missing file %s%s in backup', $file->filepath, $file->filename);
+                    $result->level = backup::LOG_WARNING;
+                    $results[] = $result;
+                    continue;
                 }
 
                 // create the file in the filepool if it does not exist yet
@@ -960,6 +969,7 @@ abstract class restore_dbops {
             }
         }
         $rs->close();
+        return $results;
     }
 
     /**
index b6d81ab..f7c6bc1 100644 (file)
@@ -46,6 +46,8 @@ abstract class backup_cron_automated_helper {
     const BACKUP_STATUS_UNFINISHED = 2;
     /** Course automated backup was skipped */
     const BACKUP_STATUS_SKIPPED = 3;
+    /** Course automated backup had warnings */
+    const BACKUP_STATUS_WARNING = 4;
 
     /** Run if required by the schedule set in config. Default. **/
     const RUN_ON_SCHEDULE = 0;
@@ -139,7 +141,7 @@ abstract class backup_cron_automated_helper {
                     $params = array('courseid' => $course->id, 'time' => $now-31*24*60*60, 'action' => '%view%');
                     $logexists = $DB->record_exists_select('log', $sqlwhere, $params);
                     if (!$logexists) {
-                        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
+                        $backupcourse->laststatus = self::BACKUP_STATUS_SKIPPED;
                         $backupcourse->nextstarttime = $nextstarttime;
                         $DB->update_record('backup_courses', $backupcourse);
                         mtrace('Skipping unchanged course '.$course->fullname);
@@ -160,7 +162,7 @@ abstract class backup_cron_automated_helper {
                         $starttime = time();
 
                         $backupcourse->laststarttime = time();
-                        $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED;
+                        $backupcourse->laststatus = self::BACKUP_STATUS_UNFINISHED;
                         $DB->update_record('backup_courses', $backupcourse);
 
                         $backupcourse->laststatus = backup_cron_automated_helper::launch_automated_backup($course, $backupcourse->laststarttime, $admin->id);
@@ -169,7 +171,7 @@ abstract class backup_cron_automated_helper {
 
                         $DB->update_record('backup_courses', $backupcourse);
 
-                        if ($backupcourse->laststatus) {
+                        if ($backupcourse->laststatus === self::BACKUP_STATUS_OK) {
                             // Clean up any excess course backups now that we have
                             // taken a successful backup.
                             $removedcount = backup_cron_automated_helper::remove_excess_backups($course);
@@ -188,17 +190,18 @@ abstract class backup_cron_automated_helper {
             $message = "";
 
             $count = backup_cron_automated_helper::get_backup_status_array();
-            $haserrors = ($count[backup_cron_automated_helper::BACKUP_STATUS_ERROR] != 0 || $count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED] != 0);
+            $haserrors = ($count[self::BACKUP_STATUS_ERROR] != 0 || $count[self::BACKUP_STATUS_UNFINISHED] != 0);
 
             //Build the message text
             //Summary
             $message .= get_string('summary')."\n";
             $message .= "==================================================\n";
             $message .= "  ".get_string('courses').": ".array_sum($count)."\n";
-            $message .= "  ".get_string('ok').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_OK]."\n";
-            $message .= "  ".get_string('skipped').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_SKIPPED]."\n";
-            $message .= "  ".get_string('error').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_ERROR]."\n";
-            $message .= "  ".get_string('unfinished').": ".$count[backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED]."\n\n";
+            $message .= "  ".get_string('ok').": ".$count[self::BACKUP_STATUS_OK]."\n";
+            $message .= "  ".get_string('skipped').": ".$count[self::BACKUP_STATUS_SKIPPED]."\n";
+            $message .= "  ".get_string('error').": ".$count[self::BACKUP_STATUS_ERROR]."\n";
+            $message .= "  ".get_string('unfinished').": ".$count[self::BACKUP_STATUS_UNFINISHED]."\n";
+            $message .= "  ".get_string('warning').": ".$count[self::BACKUP_STATUS_WARNING]."\n\n";
 
             //Reference
             if ($haserrors) {
@@ -261,6 +264,7 @@ abstract class backup_cron_automated_helper {
             self::BACKUP_STATUS_OK => 0,
             self::BACKUP_STATUS_UNFINISHED => 0,
             self::BACKUP_STATUS_SKIPPED => 0,
+            self::BACKUP_STATUS_WARNING => 0
         );
 
         $statuses = $DB->get_records_sql('SELECT DISTINCT bc.laststatus, COUNT(bc.courseid) AS statuscount FROM {backup_courses} bc GROUP BY bc.laststatus');
@@ -334,7 +338,7 @@ abstract class backup_cron_automated_helper {
      */
     public static function launch_automated_backup($course, $starttime, $userid) {
 
-        $outcome = true;
+        $outcome = self::BACKUP_STATUS_OK;
         $config = get_config('backup');
         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid);
 
@@ -369,6 +373,7 @@ abstract class backup_cron_automated_helper {
 
             $bc->execute_plan();
             $results = $bc->get_results();
+            $outcome = self::outcome_from_results($results);
             $file = $results['backup_destination']; // may be empty if file already moved to target location
             $dir = $config->backup_auto_destination;
             $storage = (int)$config->backup_auto_storage;
@@ -377,8 +382,10 @@ abstract class backup_cron_automated_helper {
             }
             if ($file && !empty($dir) && $storage !== 0) {
                 $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, !$config->backup_shortname);
-                $outcome = $file->copy_content_to($dir.'/'.$filename);
-                if ($outcome && $storage === 1) {
+                if (!$file->copy_content_to($dir.'/'.$filename)) {
+                    $outcome = self::BACKUP_STATUS_ERROR;
+                }
+                if ($outcome != self::BACKUP_STATUS_ERROR && $storage === 1) {
                     $file->delete();
                 }
             }
@@ -387,7 +394,7 @@ abstract class backup_cron_automated_helper {
             $bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header.
             $bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem.
             $bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information.
-            $outcome = false;
+            $outcome = self::BACKUP_STATUS_ERROR;
         }
 
         $bc->destroy();
@@ -396,6 +403,30 @@ abstract class backup_cron_automated_helper {
         return $outcome;
     }
 
+    /**
+     * Returns the backup outcome by analysing its results.
+     *
+     * @param array $results returned by a backup
+     * @return int {@link self::BACKUP_STATUS_OK} and other constants
+     */
+    public static function outcome_from_results($results) {
+        $outcome = self::BACKUP_STATUS_OK;
+        foreach ($results as $code => $value) {
+            // Each possible error and warning code has to be specified in this switch
+            // which basically analyses the results to return the correct backup status.
+            switch ($code) {
+                case 'missing_files_in_pool':
+                    $outcome = self::BACKUP_STATUS_WARNING;
+                    break;
+            }
+            // If we found the highest error level, we exit the loop.
+            if ($outcome == self::BACKUP_STATUS_ERROR) {
+                break;
+            }
+        }
+        return $outcome;
+    }
+
     /**
      * Removes deleted courses fromn the backup_courses table so that we don't
      * waste time backing them up.
@@ -530,18 +561,7 @@ abstract class backup_cron_automated_helper {
         if (!empty($dir) && ($storage == 1 || $storage == 2)) {
             // Calculate backup filename regex, ignoring the date/time/info parts that can be
             // variable, depending of languages, formats and automated backup settings
-
-
-            // MDL-33531: use different filenames depending on backup_shortname option
-            if ( !empty($config->backup_shortname) ) {
-                $context = get_context_instance(CONTEXT_COURSE, $course->id);
-                $courseref = format_string($course->shortname, true, array('context' => $context));
-                $courseref = str_replace(' ', '_', $courseref);
-                $courseref = textlib::strtolower(trim(clean_filename($courseref), '_'));
-            } else {
-                $courseref = $course->id;
-            }
-            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$courseref . '-';
+            $filename = $backupword . '-' . backup::FORMAT_MOODLE . '-' . backup::TYPE_1COURSE . '-' .$course->id . '-';
             $regex = '#^'.preg_quote($filename, '#').'.*\.mbz$#';
 
             // Store all the matching files into fullpath => timemodified array
index f62fee9..964dd3f 100644 (file)
@@ -94,11 +94,22 @@ abstract class backup_structure_step extends backup_step {
         // Process structure definition
         $structure->process($pr);
 
+        // Get the results from the nested elements
+        $results = $structure->get_results();
+
+        // Get the log messages to append to the log
+        $logs = $structure->get_logs();
+        foreach ($logs as $log) {
+            $this->log($log->message, $log->level, $log->a, $log->depth, $log->display);
+        }
+
         // Close everything
         $xw->stop();
 
         // Destroy the structure. It helps PHP 5.2 memory a lot!
         $structure->destroy();
+
+        return $results;
     }
 
     /**
index 4b0ff58..b6c0ead 100644 (file)
@@ -218,8 +218,14 @@ abstract class restore_structure_step extends restore_step {
      */
     public function add_related_files($component, $filearea, $mappingitemname, $filesctxid = null, $olditemid = null) {
         $filesctxid = is_null($filesctxid) ? $this->task->get_old_contextid() : $filesctxid;
-        restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
-                                          $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid);
+        $results = restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component,
+                $filearea, $filesctxid, $this->task->get_userid(), $mappingitemname, $olditemid);
+        $resultstoadd = array();
+        foreach ($results as $result) {
+            $this->log($result->message, $result->level);
+            $resultstoadd[$result->code] = true;
+        }
+        $this->task->add_result($resultstoadd);
     }
 
     /**
index 9a47901..8557ec8 100644 (file)
@@ -37,6 +37,8 @@ class backup_nested_element extends base_nested_element implements processable {
     protected $aliases;   // Define DB->final element aliases
     protected $fileannotations;   // array of file areas to be searched by file annotations
     protected $counter;   // Number of instances of this element that have been processed
+    protected $results;  // Logs the results we encounter during the process.
+    protected $logs;     // Some log messages that could be retrieved later.
 
     /**
      * Constructor - instantiates one backup_nested_element, specifying its basic info.
@@ -55,8 +57,16 @@ class backup_nested_element extends base_nested_element implements processable {
         $this->aliases   = array();
         $this->fileannotations = array();
         $this->counter   = 0;
+        $this->results  = array();
+        $this->logs     = array();
     }
 
+    /**
+     * Process the nested element
+     *
+     * @param object $processor the processor
+     * @return void
+     */
     public function process($processor) {
         if (!$processor instanceof base_processor) { // No correct processor, throw exception
             throw new base_element_struct_exception('incorrect_processor');
@@ -113,6 +123,69 @@ class backup_nested_element extends base_nested_element implements processable {
         $iterator->close();
     }
 
+    /**
+     * Saves a log message to an array
+     *
+     * @see backup_helper::log()
+     * @param string $message to add to the logs
+     * @param int $level level of importance {@link backup::LOG_DEBUG} and other constants
+     * @param mixed $a to be included in $message
+     * @param int $depth of the message
+     * @param display $bool supporting translation via get_string() if true
+     * @return void
+     */
+    protected function add_log($message, $level, $a = null, $depth = null, $display = false) {
+        // Adding the result to the oldest parent.
+        if ($this->get_parent()) {
+            $parent = $this->get_grandparent();
+            $parent->add_log($message, $level, $a, $depth, $display);
+        } else {
+            $log = new stdClass();
+            $log->message = $message;
+            $log->level = $level;
+            $log->a = $a;
+            $log->depth = $depth;
+            $log->display = $display;
+            $this->logs[] = $log;
+        }
+    }
+
+    /**
+     * Saves the results to an array
+     *
+     * @param array $result associative array
+     * @return void
+     */
+    protected function add_result($result) {
+        if (is_array($result)) {
+            // Adding the result to the oldest parent.
+            if ($this->get_parent()) {
+                $parent = $this->get_grandparent();
+                $parent->add_result($result);
+            } else {
+                $this->results = array_merge($this->results, $result);
+            }
+        }
+    }
+
+    /**
+     * Returns the logs
+     *
+     * @return array of log objects
+     */
+    public function get_logs() {
+        return $this->logs;
+    }
+
+    /**
+     * Returns the results
+     *
+     * @return associative array of results
+     */
+    public function get_results() {
+        return $this->results;
+    }
+
     public function set_source_array($arr) {
         // TODO: Only elements having final elements can set source
         $this->var_array = $arr;
index 7995561..0b90e15 100644 (file)
@@ -487,6 +487,9 @@ class backup_ui_stage_complete extends backup_ui_stage_final {
         if (!empty($this->results['include_file_references_to_external_content'])) {
             $output .= $renderer->notification(get_string('filereferencesincluded', 'backup'), 'notifyproblem');
         }
+        if (!empty($this->results['missing_files_in_pool'])) {
+            $output .= $renderer->notification(get_string('missingfilesinpool', 'backup'), 'notifyproblem');
+        }
         $output .= $renderer->notification(get_string('executionsuccess', 'backup'), 'notifysuccess');
         $output .= $renderer->continue_button($restorerul);
         $output .= $renderer->box_end();
index 7e92069..34bff92 100644 (file)
@@ -772,6 +772,9 @@ class restore_ui_stage_complete extends restore_ui_stage_process {
             $html .= $renderer->box_end();
         }
         $html .= $renderer->box_start();
+        if (array_key_exists('file_missing_in_backup', $this->results)) {
+            $html .= $renderer->notification(get_string('restorefileweremissing', 'backup'), 'notifyproblem');
+        }
         $html .= $renderer->notification(get_string('restoreexecutionsuccess', 'backup'), 'notifysuccess');
         $html .= $renderer->continue_button(new moodle_url('/course/view.php', array(
             'id' => $this->get_ui()->get_controller()->get_courseid())), 'get');
index 0daec7c..449e632 100644 (file)
  *
  * @package    block
  * @subpackage completion
- * @copyright  2009 Catalyst IT Ltd
+ * @copyright  2009-2012 Catalyst IT Ltd
  * @author     Aaron Barnes <aaronb@catalyst.net.nz>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
-
-require_once($CFG->libdir.'/completionlib.php');
+require_once("{$CFG->libdir}/completionlib.php");
 
 /**
  * Course completion status
@@ -36,25 +35,27 @@ require_once($CFG->libdir.'/completionlib.php');
 class block_completionstatus extends block_base {
 
     public function init() {
-        $this->title   = get_string('pluginname', 'block_completionstatus');
+        $this->title = get_string('pluginname', 'block_completionstatus');
     }
 
     public function get_content() {
-        global $USER, $CFG, $DB, $COURSE;
+        global $USER;
 
         // If content is cached
         if ($this->content !== NULL) {
             return $this->content;
         }
 
+        $course  = $this->page->course;
+
         // Create empty content
-        $this->content = new stdClass;
+        $this->content = new stdClass();
 
         // Can edit settings?
-        $can_edit = has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $this->page->course->id));
+        $can_edit = has_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id));
 
         // Get course completion data
-        $info = new completion_info($this->page->course);
+        $info = new completion_info($course);
 
         // Don't display if completion isn't enabled!
         if (!completion_info::is_enabled_for_site()) {
@@ -84,9 +85,9 @@ class block_completionstatus extends block_base {
         // Check this user is enroled
         if (!$info->is_tracked_user($USER->id)) {
             // If not enrolled, but are can view the report:
-            if (has_capability('report/completion:view', get_context_instance(CONTEXT_COURSE, $COURSE->id))) {
-                $this->content->text = '<a href="'.$CFG->wwwroot.'/report/completion/index.php?course='.$COURSE->id.
-                                       '">'.get_string('viewcoursereport', 'completion').'</a>';
+            if (has_capability('report/completion:view', get_context_instance(CONTEXT_COURSE, $course->id))) {
+                $report = new moodle_url('/report/completion/index.php', array('course' => $course->id));
+                $this->content->text = '<a href="'.$report->out().'">'.get_string('viewcoursereport', 'completion').'</a>';
                 return $this->content;
             }
 
@@ -187,7 +188,7 @@ class block_completionstatus extends block_base {
         // Load course completion
         $params = array(
             'userid' => $USER->id,
-            'course' => $COURSE->id
+            'course' => $course->id
         );
         $ccompletion = new completion_completion($params);
 
@@ -221,7 +222,8 @@ class block_completionstatus extends block_base {
         $this->content->text .= $shtml.'</tbody></table>';
 
         // Display link to detailed view
-        $this->content->footer = '<br><a href="'.$CFG->wwwroot.'/blocks/completionstatus/details.php?course='.$COURSE->id.'">'.get_string('moredetails', 'completion').'</a>';
+        $details = new moodle_url('/blocks/completionstatus/details.php', array('course' => $course->id));
+        $this->content->footer = '<br><a href="'.$details->out().'">'.get_string('moredetails', 'completion').'</a>';
 
         return $this->content;
     }
index a1ac771..84406e3 100644 (file)
  *
  * @package    block
  * @subpackage completion
- * @copyright  2009 Catalyst IT Ltd
+ * @copyright  2009-2012 Catalyst IT Ltd
  * @author     Aaron Barnes <aaronb@catalyst.net.nz>
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-require_once('../../config.php');
-require_once($CFG->libdir.'/completionlib.php');
-
-
-// TODO:  Make this page Moodle 2.0 compliant
+require_once(dirname(__FILE__).'/../../config.php');
+require_once("{$CFG->libdir}/completionlib.php");
 
 
 ///
 /// Load data
 ///
 $id = required_param('course', PARAM_INT);
-// User id
 $userid = optional_param('user', 0, PARAM_INT);
 
 // Load course
-$course = $DB->get_record('course', array('id' => $id));
+$course = $DB->get_record('course', array('id' => $id), '*', MUST_EXIST);
 
 // Load user
 if ($userid) {
@@ -76,21 +72,13 @@ if (!$can_view) {
 // Load completion data
 $info = new completion_info($course);
 
-$returnurl = "{$CFG->wwwroot}/course/view.php?id={$id}";
+$returnurl = new moodle_url('/course/view.php', array('id' => $id));
 
 // Don't display if completion isn't enabled!
 if (!$info->is_enabled()) {
     print_error('completionnotenabled', 'completion', $returnurl);
 }
 
-// Load criteria to display
-$completions = $info->get_completions($user->id);
-
-// Check if this course has any criteria
-if (empty($completions)) {
-    print_error('nocriteriaset', 'completion', $returnurl);
-}
-
 // Check this user is enroled
 if (!$info->is_tracked_user($user->id)) {
     if ($USER->id == $user->id) {
@@ -104,6 +92,7 @@ if (!$info->is_tracked_user($user->id)) {
 ///
 /// Display page
 ///
+$PAGE->set_context(context_course::instance($course->id));
 
 // Print header
 $page = get_string('completionprogressdetails', 'block_completionstatus');
@@ -111,7 +100,7 @@ $title = format_string($course->fullname) . ': ' . $page;
 
 $PAGE->navbar->add($page);
 $PAGE->set_pagelayout('standard');
-$PAGE->set_url('/blocks/completionstatus/details.php', array('course' => $course->id));
+$PAGE->set_url('/blocks/completionstatus/details.php', array('course' => $course->id, 'user' => $user->id));
 $PAGE->set_title(get_string('course') . ': ' . $course->fullname);
 $PAGE->set_heading($title);
 echo $OUTPUT->header();
@@ -135,122 +124,148 @@ $coursecomplete = $info->is_course_complete($user->id);
 // Has this user completed any criteria?
 $criteriacomplete = $info->count_course_user_data($user->id);
 
+// Load course completion
+$params = array(
+    'userid' => $user->id,
+    'course' => $course->id,
+);
+$ccompletion = new completion_completion($params);
+
 if ($coursecomplete) {
     echo get_string('complete');
-} else if (!$criteriacomplete) {
+} else if (!$criteriacomplete && !$ccompletion->timestarted) {
     echo '<i>'.get_string('notyetstarted', 'completion').'</i>';
 } else {
     echo '<i>'.get_string('inprogress','completion').'</i>';
 }
 
 echo '</td></tr>';
-echo '<tr><td colspan="2"><b>'.get_string('required').':</b> ';
 
-// Get overall aggregation method
-$overall = $info->get_aggregation_method();
+// Load criteria to display
+$completions = $info->get_completions($user->id);
 
-if ($overall == COMPLETION_AGGREGATION_ALL) {
-    echo get_string('criteriarequiredall', 'completion');
+// Check if this course has any criteria
+if (empty($completions)) {
+    echo '<tr><td colspan="2"><br />';
+    echo $OUTPUT->box(get_string('err_nocriteria', 'completion'), 'noticebox');
+    echo '</td></tr></tbody></table>';
 } else {
-    echo get_string('criteriarequiredany', 'completion');
-}
+    echo '<tr><td colspan="2"><b>'.get_string('required').':</b> ';
 
-echo '</td></tr></tbody></table>';
-
-// Generate markup for criteria statuses
-echo '<table class="generalbox boxaligncenter" cellpadding="3"><tbody>';
-echo '<tr class="ccheader">';
-echo '<th class="c0 header" scope="col">'.get_string('criteriagroup', 'block_completionstatus').'</th>';
-echo '<th class="c1 header" scope="col">'.get_string('criteria', 'completion').'</th>';
-echo '<th class="c2 header" scope="col">'.get_string('requirement', 'block_completionstatus').'</th>';
-echo '<th class="c3 header" scope="col">'.get_string('status').'</th>';
-echo '<th class="c4 header" scope="col">'.get_string('complete').'</th>';
-echo '<th class="c5 header" scope="col">'.get_string('completiondate', 'report_completion').'</th>';
-echo '</tr>';
-
-// Save row data
-$rows = array();
-
-global $COMPLETION_CRITERIA_TYPES;
-
-// Loop through course criteria
-foreach ($completions as $completion) {
-    $criteria = $completion->get_criteria();
-    $complete = $completion->is_complete();
-
-    $row = array();
-    $row['type'] = $criteria->criteriatype;
-    $row['title'] = $criteria->get_title();
-    $row['status'] = $completion->get_status();
-    $row['timecompleted'] = $completion->timecompleted;
-    $row['details'] = $criteria->get_details($completion);
-    $rows[] = $row;
-}
+    // Get overall aggregation method
+    $overall = $info->get_aggregation_method();
 
-// Print table
-$last_type = '';
-$agg_type = false;
+    if ($overall == COMPLETION_AGGREGATION_ALL) {
+        echo get_string('criteriarequiredall', 'completion');
+    } else {
+        echo get_string('criteriarequiredany', 'completion');
+    }
+
+    echo '</td></tr></tbody></table>';
+
+    // Generate markup for criteria statuses
+    echo '<table class="generalbox logtable boxaligncenter" id="criteriastatus" width="100%"><tbody>';
+    echo '<tr class="ccheader">';
+    echo '<th class="c0 header" scope="col">'.get_string('criteriagroup', 'block_completionstatus').'</th>';
+    echo '<th class="c1 header" scope="col">'.get_string('criteria', 'completion').'</th>';
+    echo '<th class="c2 header" scope="col">'.get_string('requirement', 'block_completionstatus').'</th>';
+    echo '<th class="c3 header" scope="col">'.get_string('status').'</th>';
+    echo '<th class="c4 header" scope="col">'.get_string('complete').'</th>';
+    echo '<th class="c5 header" scope="col">'.get_string('completiondate', 'report_completion').'</th>';
+    echo '</tr>';
 
-foreach ($rows as $row) {
+    // Save row data
+    $rows = array();
+
+    // Loop through course criteria
+    foreach ($completions as $completion) {
+        $criteria = $completion->get_criteria();
+
+        $row = array();
+        $row['type'] = $criteria->criteriatype;
+        $row['title'] = $criteria->get_title();
+        $row['status'] = $completion->get_status();
+        $row['complete'] = $completion->is_complete();
+        $row['timecompleted'] = $completion->timecompleted;
+        $row['details'] = $criteria->get_details($completion);
+        $rows[] = $row;
+    }
 
-    // Criteria group
-    echo '<td class="c0">';
-    if ($last_type !== $row['details']['type']) {
-        $last_type = $row['details']['type'];
-        echo $last_type;
+    // Print table
+    $last_type = '';
+    $agg_type = false;
+    $oddeven = 0;
 
-        // Reset agg type
-        $agg_type = true;
-    } else {
-        // Display aggregation type
-        if ($agg_type) {
-            $agg = $info->get_aggregation_method($row['type']);
+    foreach ($rows as $row) {
 
-            echo '(<i>';
+        echo '<tr class="r' . $oddeven . '">';
 
-            if ($agg == COMPLETION_AGGREGATION_ALL) {
-                echo strtolower(get_string('all', 'completion'));
-            } else {
-                echo strtolower(get_string('any', 'completion'));
-            }
+        // Criteria group
+        echo '<td class="cell c0">';
+        if ($last_type !== $row['details']['type']) {
+            $last_type = $row['details']['type'];
+            echo $last_type;
+
+            // Reset agg type
+            $agg_type = true;
+        } else {
+            // Display aggregation type
+            if ($agg_type) {
+                $agg = $info->get_aggregation_method($row['type']);
 
-            echo '</i> '.strtolower(get_string('required')).')';
-            $agg_type = false;
+                echo '(<i>';
+
+                if ($agg == COMPLETION_AGGREGATION_ALL) {
+                    echo strtolower(get_string('aggregateall', 'completion'));
+                } else {
+                    echo strtolower(get_string('aggregateany', 'completion'));
+                }
+
+                echo '</i> '.strtolower(get_string('required')).')';
+                $agg_type = false;
+            }
         }
+        echo '</td>';
+
+        // Criteria title
+        echo '<td class="cell c1">';
+        echo $row['details']['criteria'];
+        echo '</td>';
+
+        // Requirement
+        echo '<td class="cell c2">';
+        echo $row['details']['requirement'];
+        echo '</td>';
+
+        // Status
+        echo '<td class="cell c3">';
+        echo $row['details']['status'];
+        echo '</td>';
+
+        // Is complete
+        echo '<td class="cell c4">';
+        echo $row['complete'] ? get_string('yes') : get_string('no');
+        echo '</td>';
+
+        // Completion data
+        echo '<td class="cell c5">';
+        if ($row['timecompleted']) {
+            echo userdate($row['timecompleted'], get_string('strftimedate', 'langconfig'));
+        } else {
+            echo '-';
+        }
+        echo '</td>';
+        echo '</tr>';
+        // for row striping
+        $oddeven = $oddeven ? 0 : 1;
     }
-    echo '</td>';
-
-    // Criteria title
-    echo '<td class="c1">';
-    echo $row['details']['criteria'];
-    echo '</td>';
-
-    // Requirement
-    echo '<td class="c2">';
-    echo $row['details']['requirement'];
-    echo '</td>';
-
-    // Status
-    echo '<td class="c3">';
-    echo $row['details']['status'];
-    echo '</td>';
-
-    // Is complete
-    echo '<td class="c4">';
-    echo ($row['status'] === get_string('yes')) ? get_string('yes') : get_string('no');
-    echo '</td>';
-
-    // Completion data
-    echo '<td class="c5">';
-    if ($row['timecompleted']) {
-        echo userdate($row['timecompleted'], '%e %B %G');
-    } else {
-        echo '-';
-    }
-    echo '</td>';
-    echo '</tr>';
+
+    echo '</tbody></table>';
 }
 
-echo '</tbody></table>';
+echo '<div class="buttons">';
+$courseurl = new moodle_url("/course/view.php", array('id' => $course->id));
+echo $OUTPUT->single_button($courseurl, get_string('returntocourse', 'block_completionstatus'), 'get');
+echo '</div>';
 
 echo $OUTPUT->footer();
index fcc965a..6658c17 100644 (file)
@@ -5,3 +5,4 @@ $string['criteriagroup'] = 'Criteria group';
 $string['firstofsecond'] = '{$a->first} of {$a->second}';
 $string['pluginname'] = 'Course completion status';
 $string['requirement'] = 'Requirement';
+$string['returntocourse'] = 'Return to course';
index 6ac8944..a0d394e 100644 (file)
@@ -405,11 +405,10 @@ class blog_entry {
      * @return void
      */
     public function delete() {
-        global $DB, $USER;
-
-        $returnurl = '';
+        global $DB;
 
         $this->delete_attachments();
+        $this->remove_associations();
 
         $DB->delete_records('post', array('id' => $this->id));
         tag_set('post', $this->id, array());
index 3c005d8..9977e2b 100644 (file)
@@ -34,9 +34,11 @@ $string['clianswerno'] = 'n';
 $string['cliansweryes'] = 't';
 $string['cliincorrectvalueerror'] = 'Klaida, klaidinga "{$a->option}" reikšmė "{$a->value}"';
 $string['cliincorrectvalueretry'] = 'Klaidinga reikšmė, bandykite dar kartą';
-$string['clitypevalue'] = 'tipo reikšmė';
-$string['clitypevaluedefault'] = 'tipo reikšmė, paspauskite „Enter“, jei norite naudoti numatytąją reikšmę ({$a})';
-$string['cliunknowoption'] = 'Neatpažintos parinktys: {$a} naudokite --žinyno parinktį.';
+$string['clitypevalue'] = 'įveskite reikšmę';
+$string['clitypevaluedefault'] = 'įveskite reikšmę, paspauskite „Enter“, jei norite naudoti numatytąją reikšmę ({$a})';
+$string['cliunknowoption'] = 'Neatpažintos parinktys:
+{$a}
+Naudokite --help parinktį.';
 $string['cliyesnoprompt'] = 'įveskite t (taip) arba n (ne)';
 $string['environmentrequireinstall'] = 'turi būti įdiegta ir įgalinta';
 $string['environmentrequireversion'] = 'reikalinga {$a->needed} versija, o Jūs turite {$a->current}';
index 26fbe3b..5d26680 100644 (file)
@@ -68,7 +68,7 @@ $string['availablelicenses'] = 'Available licences';
 $string['backgroundcolour'] = 'Transparent colour';
 $string['backups'] = 'Backups';
 $string['backup_shortname'] = 'Use course name in backup filename';
-$string['backup_shortnamehelp'] = 'Use the course name as part of the backup filename instead of the course id number.';
+$string['backup_shortnamehelp'] = 'Use the course name as part of the backup filename.';
 $string['badwordsconfig'] = 'Enter your list of bad words separated by commas.';
 $string['badwordsdefault'] = 'If the custom list is empty, a default list from the language pack will be used.';
 $string['badwordslist'] = 'Custom bad words list';
index 5fde19e..e053c96 100644 (file)
@@ -163,6 +163,7 @@ $string['lockedbypermission'] = 'You don\'t have sufficient permissions to chang
 $string['lockedbyconfig'] = 'This setting has been locked by the default backup settings';
 $string['lockedbyhierarchy'] = 'Locked by dependencies';
 $string['managefiles'] = 'Manage backup files';
+$string['missingfilesinpool'] = 'Some files could not be saved during the backup, it won\'t be possible to restore them.';
 $string['moodleversion'] = 'Moodle version';
 $string['moreresults'] = 'There are too many results, enter a more specific search.';
 $string['nomatchingcourses'] = 'There are no courses to display';
@@ -177,6 +178,7 @@ $string['restoreactivity'] = 'Restore activity';
 $string['restorecourse'] = 'Restore course';
 $string['restorecoursesettings'] = 'Course settings';
 $string['restoreexecutionsuccess'] = 'The course was restored successfully, clicking the continue button below will take you to view the course you restored.';
+$string['restorefileweremissing'] = 'Some files could not be restored because they were missing in the backup.';
 $string['restorenewcoursefullname'] = 'New course name';
 $string['restorenewcourseshortname'] = 'New course short name';
 $string['restorenewcoursestartdate'] = 'New start date';
index 377b840..a3d28ab 100644 (file)
@@ -1803,6 +1803,7 @@ $string['virusfounduser'] = 'The file you have uploaded, {$a->filename}, has bee
 $string['virusplaceholder'] = 'This file that has been uploaded was found to contain a virus and has been moved or deleted and the user notified.';
 $string['visible'] = 'Visible';
 $string['visibletostudents'] = 'Visible to {$a}';
+$string['warning'] = 'Warning';
 $string['warningdeleteresource'] = 'Warning: {$a} is referred in a resource. Would you like to update the resource?';
 $string['webpage'] = 'Web page';
 $string['week'] = 'Week';
index 5103e0b..5247e49 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+/**
+ * Returns all non-view and non-temp tables with sane names.
+ * Prints list of non-supported tables using $OUTPUT->notification()
+ *
+ * @return array
+ */
+function upgrade_mysql_get_supported_tables() {
+    global $OUTPUT, $DB;
+
+    $tables = array();
+    $patprefix = str_replace('_', '\\_', $DB->get_prefix());
+    $pregprefix = preg_quote($DB->get_prefix(), '/');
+
+    $sql = "SHOW FULL TABLES LIKE '$patprefix%'";
+    $rs = $DB->get_recordset_sql($sql);
+    foreach ($rs as $record) {
+        $record = array_change_key_case((array)$record, CASE_LOWER);
+        $type = $record['table_type'];
+        unset($record['table_type']);
+        $fullname = array_shift($record);
+
+        if ($pregprefix === '') {
+            $name = $fullname;
+        } else {
+            $count = null;
+            $name = preg_replace("/^$pregprefix/", '', $fullname, -1, $count);
+            if ($count !== 1) {
+                continue;
+            }
+        }
+
+        if (!preg_match("/^[a-z][a-z0-9_]*$/", $name)) {
+            echo $OUTPUT->notification("Database table with invalid name '$fullname' detected, skipping.", 'notifyproblem');
+            continue;
+        }
+        if ($type === 'VIEW') {
+            echo $OUTPUT->notification("Unsupported database table view '$fullname' detected, skipping.", 'notifyproblem');
+            continue;
+        }
+        $tables[$name] = $name;
+    }
+    $rs->close();
+
+    return $tables;
+}
 
 /**
  * Remove all signed numbers from current database - mysql only.
@@ -50,7 +95,7 @@ function upgrade_mysql_fix_unsigned_columns() {
     $pbar = new progress_bar('mysqlconvertunsigned', 500, true);
 
     $prefix = $DB->get_prefix();
-    $tables = $DB->get_tables();
+    $tables = upgrade_mysql_get_supported_tables();
 
     $tablecount = count($tables);
     $i = 0;
@@ -115,7 +160,7 @@ function upgrade_mysql_fix_lob_columns() {
     $pbar = new progress_bar('mysqlconvertlobs', 500, true);
 
     $prefix = $DB->get_prefix();
-    $tables = $DB->get_tables();
+    $tables = upgrade_mysql_get_supported_tables();
     asort($tables);
 
     $tablecount = count($tables);
index 58a6c59..0c2a693 100644 (file)
@@ -3915,15 +3915,45 @@ function truncate_userinfo($info) {
  * Any plugin that needs to purge user data should register the 'user_deleted' event.
  *
  * @param stdClass $user full user object before delete
- * @return boolean always true
+ * @return boolean success
+ * @throws coding_exception if invalid $user parameter detected
  */
-function delete_user($user) {
+function delete_user(stdClass $user) {
     global $CFG, $DB;
     require_once($CFG->libdir.'/grouplib.php');
     require_once($CFG->libdir.'/gradelib.php');
     require_once($CFG->dirroot.'/message/lib.php');
     require_once($CFG->dirroot.'/tag/lib.php');
 
+    // Make sure nobody sends bogus record type as parameter.
+    if (!property_exists($user, 'id') or !property_exists($user, 'username')) {
+        throw new coding_exception('Invalid $user parameter in delete_user() detected');
+    }
+
+    // Better not trust the parameter and fetch the latest info,
+    // this will be very expensive anyway.
+    if (!$user = $DB->get_record('user', array('id'=>$user->id))) {
+        debugging('Attempt to delete unknown user account.');
+        return false;
+    }
+
+    // There must be always exactly one guest record,
+    // originally the guest account was identified by username only,
+    // now we use $CFG->siteguest for performance reasons.
+    if ($user->username === 'guest' or isguestuser($user)) {
+        debugging('Guest user account can not be deleted.');
+        return false;
+    }
+
+    // Admin can be theoretically from different auth plugin,
+    // but we want to prevent deletion of internal accoutns only,
+    // if anything goes wrong ppl may force somebody to be admin via
+    // config.php setting $CFG->siteadmins.
+    if ($user->auth === 'manual' and is_siteadmin($user)) {
+        debugging('Local administrator accounts can not be deleted.');
+        return false;
+    }
+
     // delete all grades - backup is kept in grade_grades_history table
     grade_user_delete($user->id);
 
index 43de510..65e1c35 100644 (file)
@@ -1064,7 +1064,7 @@ class available_update_checker {
             return true;
         }
 
-        if ($now - $recent > HOURSECS) {
+        if ($now - $recent > 24 * HOURSECS) {
             return false;
         }
 
index dd9665a..1abdf80 100644 (file)
@@ -1910,4 +1910,64 @@ class moodlelib_testcase extends advanced_testcase {
         $this->assertEquals('5.43000', format_float(5.43, 5, false));
         $this->assertEquals('5.43', format_float(5.43, 5, false, true));
     }
+
+    /**
+     * Test deleting of users.
+     */
+    public function test_delete_user() {
+        global $DB, $CFG;
+
+        $this->resetAfterTest();
+
+        $guest = $DB->get_record('user', array('id'=>$CFG->siteguest), '*', MUST_EXIST);
+        $admin = $DB->get_record('user', array('id'=>$CFG->siteadmins), '*', MUST_EXIST);
+        $this->assertEquals(0, $DB->count_records('user', array('deleted'=>1)));
+
+        $user = $this->getDataGenerator()->create_user(array('idnumber'=>'abc'));
+        $user2 = $this->getDataGenerator()->create_user(array('idnumber'=>'xyz'));
+
+        $result = delete_user($user);
+        $this->assertTrue($result);
+        $deluser = $DB->get_record('user', array('id'=>$user->id), '*', MUST_EXIST);
+        $this->assertEquals(1, $deluser->deleted);
+        $this->assertEquals(0, $deluser->picture);
+        $this->assertSame('', $deluser->idnumber);
+        $this->assertSame(md5($user->username), $deluser->email);
+        $this->assertRegExp('/^'.preg_quote($user->email, '/').'\.\d*$/', $deluser->username);
+
+        $this->assertEquals(1, $DB->count_records('user', array('deleted'=>1)));
+
+        // Try invalid params.
+
+        $record = new stdClass();
+        $record->grrr = 1;
+        try {
+            delete_user($record);
+            $this->fail('Expecting exception for invalid delete_user() $user parameter');
+        } catch (coding_exception $e) {
+            $this->assertTrue(true);
+        }
+        $record->id = 1;
+        try {
+            delete_user($record);
+            $this->fail('Expecting exception for invalid delete_user() $user parameter');
+        } catch (coding_exception $e) {
+            $this->assertTrue(true);
+        }
+
+        $CFG->debug = DEBUG_MINIMAL; // Prevent standard debug warnings.
+
+        $record = new stdClass();
+        $record->id = 666;
+        $record->username = 'xx';
+        $this->assertFalse($DB->record_exists('user', array('id'=>666))); // Any non-existent id is ok.
+        $result = delete_user($record);
+        $this->assertFalse($result);
+
+        $result = delete_user($guest);
+        $this->assertFalse($result);
+
+        $result = delete_user($admin);
+        $this->assertFalse($result);
+    }
 }
index eb29268..2ec4d0d 100644 (file)
@@ -103,7 +103,7 @@ class available_update_checker_test extends advanced_testcase {
      */
     public function test_cron_has_fresh_fetch() {
         $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = time() - 59 * MINSECS; // fetched an hour ago
+        $provider->fakerecentfetch = time() - 23 * HOURSECS; // fetched 23 hours ago
         $provider->fakecurrenttimestamp = -1;
         $provider->cron();
         $this->assertTrue(true); // we should get here with no exception thrown
@@ -127,23 +127,52 @@ class available_update_checker_test extends advanced_testcase {
      */
     public function test_cron_offset_execution_not_yet() {
         $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = time() - 24 * HOURSECS;
-        $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM
+        $provider->fakecurrenttimestamp = mktime(1, 40, 02); // 01:40:02 AM today
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS;
         $provider->cron();
         $this->assertTrue(true); // we should get here with no exception thrown
     }
 
     /**
-     * The first cron after 01:42 AM today should fetch the data
+     * The first cron after 01:42 AM today should fetch the data and then
+     * it is supposed to wait next 24 hours.
      *
      * @see testable_available_update_checker::cron_execution_offset()
      */
     public function test_cron_offset_execution() {
         $provider = testable_available_update_checker::instance();
-        $provider->fakerecentfetch = time() - 24 * HOURSECS;
-        $provider->fakecurrenttimestamp = mktime(1, 45, 02); // 01:45:02 AM
-        $this->setExpectedException('testable_available_update_checker_cron_executed');
-        $provider->cron();
+
+        // the cron at 01:45 should fetch the data
+        $provider->fakecurrenttimestamp = mktime(1, 45, 02); // 01:45:02 AM today
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp - 24 * HOURSECS - 1;
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertTrue($executed, 'Cron should be executed at 01:45:02 but it was not.');
+
+        // another cron at 06:45 should still consider data as fresh enough
+        $provider->fakerecentfetch = $provider->fakecurrenttimestamp;
+        $provider->fakecurrenttimestamp = mktime(6, 45, 03); // 06:45:03 AM
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertFalse($executed, 'Cron should not be executed at 06:45:03 but it was.');
+
+        // the next scheduled execution should happen the next day
+        $provider->fakecurrenttimestamp = $provider->fakerecentfetch + 24 * HOURSECS + 1;
+        $executed = false;
+        try {
+            $provider->cron();
+        } catch (testable_available_update_checker_cron_executed $e) {
+            $executed = true;
+        }
+        $this->assertTrue($executed, 'Cron should be executed the next night but it was not.');
     }
 
     public function test_compare_responses_both_empty() {
@@ -503,7 +532,7 @@ class testable_available_update_checker extends available_update_checker {
     }
 
     protected function cron_execute() {
-        throw new testable_available_update_checker_cron_executed('Cron executed but it should not!');
+        throw new testable_available_update_checker_cron_executed('Cron executed!');
     }
 }
 
index 6d8bef3..a881292 100644 (file)
@@ -9,6 +9,6 @@
     </tr>
     <tr>
         <td class="c0" valign="top"><label for="param1"><?php echo get_string('fieldoptions', 'data'); ?></label></td>
-        <td class="c1"><textarea style="width:300px; height:150px;" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
+        <td class="c1"><textarea class="optionstextarea" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
     </tr>
 </table>
index a6f6889..e941699 100644 (file)
@@ -130,7 +130,6 @@ class data_field_latlong extends data_field_base {
             } else {
                 $compasslong = sprintf('%01.4f', $long) . '°E';
             }
-            $str = '<form style="display:inline;">';
 
             // Now let's create the jump-to-services link
             $servicesshown = explode(',', $this->field->param1);
@@ -148,10 +147,11 @@ class data_field_latlong extends data_field_base {
             );
 
             if(sizeof($servicesshown)==1 && $servicesshown[0]) {
-                $str .= " <a href='"
+                $str = " <a href='"
                           . str_replace(array_keys($urlreplacements), array_values($urlreplacements), $this->linkoutservices[$servicesshown[0]])
                           ."' title='$servicesshown[0]'>$compasslat, $compasslong</a>";
             } elseif (sizeof($servicesshown)>1) {
+                $str = '<form id="latlongfieldbrowse">';
                 $str .= "$compasslat, $compasslong\n";
                 $str .= "<label class='accesshide' for='jumpto'>". get_string('jumpto') ."</label>";
                 $str .= "<select id='jumpto' name='jumpto'>";
@@ -164,10 +164,11 @@ class data_field_latlong extends data_field_base {
                 // NB! If you are editing this, make sure you don't break the javascript reference "previousSibling"
                 //   which allows the "Go" button to refer to the drop-down selector.
                 $str .= "\n</select><input type='button' value='" . get_string('go') . "' onclick='if(previousSibling.value){self.location=previousSibling.value}'/>";
+                $str .= '</form>';
             } else {
-                $str.= "$compasslat, $compasslong";
+                $str = "$compasslat, $compasslong";
             }
-            $str.= '</form>';
+
             return $str;
         }
         return false;
index 58fa709..f92fb13 100644 (file)
@@ -9,6 +9,6 @@
     </tr>
     <tr>
         <td class="c0" valign="top"><label for="param1"><?php echo get_string('fieldoptions', 'data'); ?></label></td>
-        <td class="c1"><textarea style="width:300px; height:150px;" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
+        <td class="c1"><textarea class="optionstextarea" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
     </tr>
 </table>
index 6d8bef3..a881292 100644 (file)
@@ -9,6 +9,6 @@
     </tr>
     <tr>
         <td class="c0" valign="top"><label for="param1"><?php echo get_string('fieldoptions', 'data'); ?></label></td>
-        <td class="c1"><textarea style="width:300px; height:150px;" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
+        <td class="c1"><textarea class="optionstextarea" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
     </tr>
 </table>
index 67d80f8..f569f53 100644 (file)
@@ -152,13 +152,13 @@ class data_field_picture extends data_field_base {
         if ($template == 'listtemplate') {
             $src = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_data/content/'.$content->id.'/'.'thumb_'.$content->content);
             // no need to add width/height, because the thumb is resized properly
-            $str = '<a href="view.php?d='.$this->field->dataid.'&amp;rid='.$recordid.'"><img src="'.$src.'" alt="'.s($alt).'" title="'.s($title).'" style="border:0px" /></a>';
+            $str = '<a href="view.php?d='.$this->field->dataid.'&amp;rid='.$recordid.'"><img src="'.$src.'" alt="'.s($alt).'" title="'.s($title).'" class="list_picture"/></a>';
 
         } else {
             $src = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.$this->context->id.'/mod_data/content/'.$content->id.'/'.$content->content);
             $width  = $this->field->param1 ? ' width="'.s($this->field->param1).'" ':' ';
             $height = $this->field->param2 ? ' height="'.s($this->field->param2).'" ':' ';
-            $str = '<a href="'.$src.'"><img '.$width.$height.' src="'.$src.'" alt="'.s($alt).'" title="'.s($title).'" style="border:0px" /></a>';
+            $str = '<a href="'.$src.'"><img '.$width.$height.' src="'.$src.'" alt="'.s($alt).'" title="'.s($title).'" class="list_picture"/></a>';
         }
 
         return $str;
index 4b32474..2ea0778 100644 (file)
         <td class="c0"><label for="param1">
             <?php echo get_string('fieldwidthsingleview', 'data');?></label></td>
         <td class="c1">
-            <input style="width:70px;" type="text" name="param1" id="param1" value="<?php if (!empty($this->field->param1)) p($this->field->param1); ?>" />
+            <input class="picturefieldsize" type="text" name="param1" id="param1" value="<?php if (!empty($this->field->param1)) p($this->field->param1); ?>" />
         </td>
     </tr>
     <tr>
         <td class="c0"><label for="param2">
             <?php echo get_string('fieldheightsingleview', 'data');?></label></td>
         <td class="c1">
-            <input style="width:70px;" type="text" name="param2" id="param2" value="<?php if (!empty($this->field->param2)) p($this->field->param2); ?>" />
+            <input class="picturefieldsize" type="text" name="param2" id="param2" value="<?php if (!empty($this->field->param2)) p($this->field->param2); ?>" />
         </td>
     </tr>
     <tr>
         <td class="c0"><label for="param4">
             <?php echo get_string('fieldwidthlistview', 'data');?></label></td>
-        <td class="c1"><input style="width:70px;" type="text" name="param4" id="param4" value="<?php if (!empty($this->field->param4)) p($this->field->param4); ?>" />
+        <td class="c1"><input class="picturefieldsize" type="text" name="param4" id="param4" value="<?php if (!empty($this->field->param4)) p($this->field->param4); ?>" />
         </td>
     </tr>
     <tr>
         <td class="c0"><label for="param5">
             <?php echo get_string('fieldheightlistview', 'data');?></label></td>
         <td class="c1">
-            <input style="width:70px;" type="text" name="param5" id="param5" value="<?php if (!empty($this->field->param5)) p($this->field->param5); ?>" />
+            <input class="picturefieldsize" type="text" name="param5" id="param5" value="<?php if (!empty($this->field->param5)) p($this->field->param5); ?>" />
         </td>
     </tr>
     <tr>
index 6d8bef3..a881292 100644 (file)
@@ -9,6 +9,6 @@
     </tr>
     <tr>
         <td class="c0" valign="top"><label for="param1"><?php echo get_string('fieldoptions', 'data'); ?></label></td>
-        <td class="c1"><textarea style="width:300px; height:150px;" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
+        <td class="c1"><textarea class="optionstextarea" name="param1" id="param1" cols="80" rows="10"><?php if($this->field->param1) {p($this->field->param1);} ?></textarea></td>
     </tr>
 </table>
index fb68424..e75365a 100644 (file)
@@ -14,7 +14,7 @@
         <td class="c0"><label for="param2">
             <?php echo get_string('fieldwidth', 'data'); ?></label></td>
         <td class="c1">
-            <input style="width:50px;" type="text" name="param2" id="param2" value=
+            <input class="textareafieldsize" type="text" name="param2" id="param2" value=
             <?php
                 if (empty($this->field->param2)) {
                     echo('"60"');
@@ -28,7 +28,7 @@
         <td class="c0"><label for="param3">
             <?php echo get_string('fieldheight', 'data'); ?></label></td>
         <td class="c1">
-            <input style="width:50px;" type="text" name="param3" id="param3" value=
+            <input class="textareafieldsize" type="text" name="param3" id="param3" value=
             <?php
                 if (empty($this->field->param3)) {
                     echo('"35"');
index 7b139af..fc26f78 100644 (file)
@@ -244,7 +244,7 @@ class data_field_base {     // Base class for Database Field Types (see field/*/
 
         $str = '<div title="'.s($this->field->description).'">';
         $str .= '<label class="accesshide" for="field_'.$this->field->id.'">'.$this->field->description.'</label>';
-        $str .= '<input style="width:300px;" type="text" name="field_'.$this->field->id.'" id="field_'.$this->field->id.'" value="'.s($content).'" />';
+        $str .= '<input class="basefieldinput" type="text" name="field_'.$this->field->id.'" id="field_'.$this->field->id.'" value="'.s($content).'" />';
         $str .= '</div>';
 
         return $str;
@@ -1480,14 +1480,16 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order='
     $pagesizes = array(2=>2,3=>3,4=>4,5=>5,6=>6,7=>7,8=>8,9=>9,10=>10,15=>15,
                        20=>20,30=>30,40=>40,50=>50,100=>100,200=>200,300=>300,400=>400,500=>500,1000=>1000);
     echo html_writer::select($pagesizes, 'perpage', $perpage, false, array('id'=>'pref_perpage'));
-    echo '<div id="reg_search" style="display: ';
+
     if ($advanced) {
-        echo 'none';
-    }
-    else {
-        echo 'inline';
+        $regsearchclass = 'search_none';
+        $advancedsearchclass = 'search_inline';
+    } else {
+        $regsearchclass = 'search_inline';
+        $advancedsearchclass = 'search_none';
     }
-    echo ';" >&nbsp;&nbsp;&nbsp;<label for="pref_search">'.get_string('search').'</label> <input type="text" size="16" name="search" id= "pref_search" value="'.s($search).'" /></div>';
+    echo '<div id="reg_search" class="' . $regsearchclass . '" >&nbsp;&nbsp;&nbsp;';
+    echo '<label for="pref_search">'.get_string('search').'</label> <input type="text" size="16" name="search" id= "pref_search" value="'.s($search).'" /></div>';
     echo '&nbsp;&nbsp;&nbsp;<label for="pref_sortby">'.get_string('sortby').'</label> ';
     // foreach field, print the option
     echo '<select name="sort" id="pref_sortby">';
@@ -1547,15 +1549,7 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order='
     echo '&nbsp;<input type="submit" value="'.get_string('savesettings','data').'" />';
 
     echo '<br />';
-    echo '<div class="dataadvancedsearch" id="data_adv_form" style="display: ';
-
-    if ($advanced) {
-        echo 'inline';
-    }
-    else {
-        echo 'none';
-    }
-    echo ';margin-left:auto;margin-right:auto;" >';
+    echo '<div class="' . $advancedsearchclass . '" id="data_adv_form">';
     echo '<table class="boxaligncenter">';
 
     // print ASC or DESC
@@ -1620,7 +1614,7 @@ function data_print_preference_form($data, $perpage, $search, $sort='', $order='
     echo format_text($newtext, FORMAT_HTML, $options);
     echo '</td></tr>';
 
-    echo '<tr><td colspan="4" style="text-align: center;"><br/><input type="submit" value="'.get_string('savesettings','data').'" /><input type="submit" name="resetadv" value="'.get_string('resetsettings','data').'" /></td></tr>';
+    echo '<tr><td colspan="4"><br/><input type="submit" value="'.get_string('savesettings','data').'" /><input type="submit" name="resetadv" value="'.get_string('resetsettings','data').'" /></td></tr>';
     echo '</table>';
     echo '</div>';
     echo '</div>';
index 4f68465..600c79d 100644 (file)
@@ -7,6 +7,24 @@
 .path-mod-data-field .c0,
 #page-mod-data-view #sortsearch .c0 {text-align: right;}
 #page-mod-data-view .approve img.icon {width:34px;height:34px;}
+#page-mod-data-view img.list_picture {
+    border:0px;
+}
+#page-mod-data-view div.search_none {
+    display: none;
+}
+#page-mod-data-view div.search_inline,
+#page-mod-data-view form#latlongfieldbrowse {
+    display: inline;
+}
+#page-mod-data-view div#data_adv_form {
+    margin-left:auto;
+    margin-right:auto;
+}
+
+#page-mod-data-edit .basefieldinput {
+    width:300px;
+}
 
 /** Styles for preset.php **/
 #page-mod-data-preset .presetmapping table {text-align: left;margin-left: auto;margin-right: auto;}
 .path-mod-data-field .sortdefault select {margin-left: 1em;}
 .path-mod-data-field .fieldname,
 .path-mod-data-field .fielddescription {width:300px;}
+.path-mod-data-field textarea.optionstextarea {
+    width:300px;
+    height:150px;
+}
+.path-mod-data-field input.textareafieldsize {
+    width:50px;
+}
+.path-mod-data-field input.picturefieldsize {
+    width:70px;
+}
 
 /** UI Usability Hacks **/
 #page-mod-data-export #notice span {padding:0 10px;}
 .mod-data-default-template .template-token {text-align:left;}
 .mod-data-default-template .controls {text-align:center;}
 .mod-data-default-template searchcontrols {text-align:right;}
+#page-mod-data-templates td.save_template,
+#page-mod-data-templates .template_heading {
+     text-align:center;
+}
 
 .dir-rtl .mod-data-default-template .template-field {text-align:left;}
 .dir-rtl .mod-data-default-template .template-token {text-align:right;}
index 404f28d..953fc89 100644 (file)
@@ -141,7 +141,7 @@ if (($mytemplate = data_submitted()) && confirm_sesskey()) {
         }
     }
 } else {
-    echo '<div class="littleintro" style="text-align:center">'.get_string('header'.$mode,'data').'</div>';
+    echo '<div class="template_heading">'.get_string('header'.$mode,'data').'</div>';
 }
 
 /// If everything is empty then generate some defaults
@@ -198,7 +198,7 @@ if ($mode == 'listtemplate'){
     echo '<tr>';
     echo '<td>&nbsp;</td>';
     echo '<td>';
-    echo '<div style="text-align:center"><label for="edit-listtemplateheader">'.get_string('header','data').'</label></div>';
+    echo '<div class="template_heading"><label for="edit-listtemplateheader">'.get_string('header','data').'</label></div>';
 
     $field = 'listtemplateheader';
     $editor->use_editor($field, $options);
@@ -290,9 +290,9 @@ echo '</td>';
 
 echo '<td valign="top">';
 if ($mode == 'listtemplate'){
-    echo '<div style="text-align:center"><label for="edit-template">'.get_string('multientry','data').'</label></div>';
+    echo '<div class="template_heading"><label for="edit-template">'.get_string('multientry','data').'</label></div>';
 } else {
-    echo '<div style="text-align:center"><label for="edit-template">'.get_string($mode,'data').'</label></div>';
+    echo '<div class="template_heading"><label for="edit-template">'.get_string($mode,'data').'</label></div>';
 }
 
 $field = 'template';
@@ -305,7 +305,7 @@ if ($mode == 'listtemplate'){
     echo '<tr>';
     echo '<td>&nbsp;</td>';
     echo '<td>';
-    echo '<div style="text-align:center"><label for="edit-listtemplatefooter">'.get_string('footer','data').'</label></div>';
+    echo '<div class="template_heading"><label for="edit-listtemplatefooter">'.get_string('footer','data').'</label></div>';
 
     $field = 'listtemplatefooter';
     $editor->use_editor($field, $options);
@@ -316,7 +316,7 @@ if ($mode == 'listtemplate'){
     echo '<tr>';
     echo '<td>&nbsp;</td>';
     echo '<td>';
-    echo '<div style="text-align:center"><label for="edit-rsstitletemplate">'.get_string('rsstitletemplate','data').'</label></div>';
+    echo '<div class="template_heading"><label for="edit-rsstitletemplate">'.get_string('rsstitletemplate','data').'</label></div>';
 
     $field = 'rsstitletemplate';
     $editor->use_editor($field, $options);
@@ -325,7 +325,7 @@ if ($mode == 'listtemplate'){
     echo '</tr>';
 }
 
-echo '<tr><td style="text-align:center" colspan="2">';
+echo '<tr><td class="save_template" colspan="2">';
 echo '<input type="submit" value="'.get_string('savetemplate','data').'" />&nbsp;';
 
 echo '</td></tr></table>';
index 574bee1..95bb645 100644 (file)
@@ -384,6 +384,15 @@ class qformat_default {
                         $newpage->title = "Page $count";
                     }
                     $newpage->contents = $question->questiontext;
+                    $newpage->contentsformat = isset($question->questionformat) ? $question->questionformat : FORMAT_HTML;
+
+                    // Sometimes, questiontext is not a simple text, but one array
+                    // containing both text and format, so we need to support here
+                    // that case with the following dirty patch. MDL-35147
+                    if (is_array($question->questiontext)) {
+                        $newpage->contents = isset($question->questiontext['text']) ? $question->questiontext['text'] : '';
+                        $newpage->contentsformat = isset($question->questiontext['format']) ? $question->questiontext['format'] : FORMAT_HTML;
+                    }
 
                     // set up page links
                     if ($pageid) {
@@ -580,6 +589,93 @@ class qformat_default {
         return html_to_text(format_text($question->questiontext,
                 $question->questiontextformat, $formatoptions), 0, false);
     }
+
+    /**
+     * Since the lesson module tries to re-use the question bank import classes in
+     * a crazy way, this is necessary to stop things breaking.
+     */
+    protected function add_blank_combined_feedback($question) {
+        return $question;
+    }
 }
 
 
+/**
+ * Since the lesson module tries to re-use the question bank import classes in
+ * a crazy way, this is necessary to stop things breaking. This should be exactly
+ * the same as the class defined in question/format.php.
+ */
+class qformat_based_on_xml extends qformat_default {
+    /**
+     * A lot of imported files contain unwanted entities.
+     * This method tries to clean up all known problems.
+     * @param string str string to correct
+     * @return string the corrected string
+     */
+    public function cleaninput($str) {
+
+        $html_code_list = array(
+            "&#039;" => "'",
+            "&#8217;" => "'",
+            "&#8220;" => "\"",
+            "&#8221;" => "\"",
+            "&#8211;" => "-",
+            "&#8212;" => "-",
+        );
+        $str = strtr($str, $html_code_list);
+        // Use textlib entities_to_utf8 function to convert only numerical entities.
+        $str = textlib::entities_to_utf8($str, false);
+        return $str;
+    }
+
+    /**
+     * Return the array moodle is expecting
+     * for an HTML text. No processing is done on $text.
+     * qformat classes that want to process $text
+     * for instance to import external images files
+     * and recode urls in $text must overwrite this method.
+     * @param array $text some HTML text string
+     * @return array with keys text, format and files.
+     */
+    public function text_field($text) {
+        return array(
+            'text' => trim($text),
+            'format' => FORMAT_HTML,
+            'files' => array(),
+        );
+    }
+
+    /**
+     * Return the value of a node, given a path to the node
+     * if it doesn't exist return the default value.
+     * @param array xml data to read
+     * @param array path path to node expressed as array
+     * @param mixed default
+     * @param bool istext process as text
+     * @param string error if set value must exist, return false and issue message if not
+     * @return mixed value
+     */
+    public function getpath($xml, $path, $default, $istext=false, $error='') {
+        foreach ($path as $index) {
+            if (!isset($xml[$index])) {
+                if (!empty($error)) {
+                    $this->error($error);
+                    return false;
+                } else {
+                    return $default;
+                }
+            }
+
+            $xml = $xml[$index];
+        }
+
+        if ($istext) {
+            if (!is_string($xml)) {
+                $this->error(get_string('invalidxml', 'qformat_xml'));
+            }
+            $xml = trim($xml);
+        }
+
+        return $xml;
+    }
+}
index 6361024..d07442b 100644 (file)
@@ -178,6 +178,7 @@ table.quizreviewsummary td.cell {padding: 1px 1em 1px 0.5em;text-align: left;bac
 #page-mod-quiz-edit h2.main{display:inline;padding-right:1em;clear:left;}
 
 #categoryquestions .r1 {background: #e4e4e4;}
+#categoryquestions .r1.highlight {background-color:#AAFFAA;}
 #categoryquestions .header {text-align: center;padding: 0 2px;border: 0 none;}
 #categoryquestions th.modifiername .sorters,
 #categoryquestions th.creatorname .sorters {font-weight: normal;font-size: 0.8em;}
index 5d557a4..2e9a0c0 100644 (file)
@@ -32,8 +32,13 @@ class moodle1_workshopform_comments_handler extends moodle1_workshopform_handler
 
     /**
      * Converts <ELEMENT> into <workshopform_comments_dimension>
+     *
+     * @param array $data legacy element data
+     * @param array $raw raw element data
+     *
+     * @return array converted
      */
-    public function process_legacy_element($data, $raw) {
+    public function process_legacy_element(array $data, array $raw) {
         // prepare a fake record and re-use the upgrade logic
         $fakerecord = (object)$data;
         $converted = (array)workshopform_comments_upgrade_element($fakerecord, 12345678);
index d2c6310..e18a2f2 100644 (file)
@@ -49,9 +49,12 @@ class moodle1_workshopform_numerrors_handler extends moodle1_workshopform_handle
     /**
      * Converts <ELEMENT> into <workshopform_numerrors_dimension> and stores it for later writing
      *
+     * @param array $data legacy element data
+     * @param array $raw raw element data
+     *
      * @return array to be written to workshop.xml
      */
-    public function process_legacy_element($data, $raw) {
+    public function process_legacy_element(array $data, array $raw) {
 
         $workshop = $this->parenthandler->get_current_workshop();
 
index be0a6c6..fe604f8 100644 (file)
@@ -46,8 +46,11 @@ class moodle1_workshopform_rubric_handler extends moodle1_workshopform_handler {
 
     /**
      * Processes one <ELEMENT>
+     *
+     * @param array $data legacy element data
+     * @param array $raw raw element data
      */
-    public function process_legacy_element($data, $raw) {
+    public function process_legacy_element(array $data, array $raw) {
         $this->elements[] = $data;
         $this->rubrics[$data['id']] = array();
     }
index 3cadf62..4aa77fd 100644 (file)
@@ -10,3 +10,6 @@
 .que.multichoice .answer div.r1 {
     padding: 0.3em;
 }
+.que.multichoice .feedback .rightanswer * {
+    display: inline;
+}
index 5a70587..7685117 100644 (file)
@@ -27,6 +27,9 @@ require_once('../../config.php');
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->dirroot.'/backup/lib.php');
 
+// Required for constants in backup_cron_automated_helper
+require_once($CFG->dirroot.'/backup/util/helper/backup_cron_helper.class.php');
+
 admin_externalpage_setup('reportbackups', '', null, '', array('pagelayout'=>'report'));
 
 $table = new html_table;
@@ -45,6 +48,7 @@ $strerror = get_string("error");
 $strok = get_string("ok");
 $strunfinished = get_string("unfinished");
 $strskipped = get_string("skipped");
+$strwarning = get_string("warning");
 
 list($select, $join) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
 $sql = "SELECT bc.*, c.fullname $select
@@ -58,15 +62,18 @@ foreach ($rs as $backuprow) {
     context_instance_preload($backuprow);
 
     // Prepare a cell to display the status of the entry
-    if ($backuprow->laststatus == 1) {
+    if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_OK) {
         $status = $strok;
         $statusclass = 'backup-ok'; // Green
-    } else if ($backuprow->laststatus == 2) {
+    } else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_UNFINISHED) {
         $status = $strunfinished;
         $statusclass = 'backup-unfinished'; // Red
-    } else if ($backuprow->laststatus == 3) {
+    } else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_SKIPPED) {
         $status = $strskipped;
         $statusclass = 'backup-skipped'; // Green
+    } else if ($backuprow->laststatus == backup_cron_automated_helper::BACKUP_STATUS_WARNING) {
+        $status = $strwarning;
+        $statusclass = 'backup-warning'; // Orange
     } else {
         $status = $strerror;
         $statusclass = 'backup-error'; // Red
index c185fb3..1c191a4 100644 (file)
@@ -36,7 +36,7 @@ defined('MOODLE_INTERNAL') || die;
  */
 function report_stats_extend_navigation_course($navigation, $course, $context) {
     global $CFG;
-    if (!empty($CFG->enablestats)) {
+    if (empty($CFG->enablestats)) {
         return;
     }
     if (has_capability('report/stats:view', $context)) {
@@ -54,7 +54,7 @@ function report_stats_extend_navigation_course($navigation, $course, $context) {
  */
 function report_stats_extend_navigation_user($navigation, $user, $course) {
     global $CFG;
-    if (!empty($CFG->enablestats)) {
+    if (empty($CFG->enablestats)) {
         return;
     }
     if (report_stats_can_access_user_report($user, $course)) {
index eb9260e..5d78380 100644 (file)
@@ -206,6 +206,10 @@ select, input, button {
     background-color: #34637f;
     color: #fff;
 }
+.ie7 select { /* fixes compatibility view */
+    background-color: #eee;
+    color: #036;
+}
 #loginbtn, input, button, select {
     cursor: pointer;
     margin-left: 5px;
@@ -465,4 +469,4 @@ body#page-course-view-topics.path-course div.moodle-dialogue-base div.yui3-widge
 .file-picker button,
 .file-picker textarea {
     background-color: #EEE;
-}
\ No newline at end of file
+}
index d420e1e..d695186 100644 (file)
@@ -491,8 +491,7 @@ h1.headermain {
     padding-top: 10px;
 }
 
-.forumpost .content .shortenedpost a,
-.forumpost .content p {
+.forumpost .content .shortenedpost a {
     margin: 0 10px;
     padding: 0;
 }
index 8c0ce47..df0e64f 100644 (file)
@@ -42,6 +42,7 @@
 #page-admin-report-backups-index .backup-unfinished {color: #f00000;}
 #page-admin-report-backups-index .backup-skipped,
 #page-admin-report-backups-index .backup-ok {color: #006400;}
+#page-admin-report-backups-index .backup-warning {color: #ff9900;}
 
 #page-admin-qbehaviours .disabled {color: gray;}
 #page-admin-qbehaviours th {white-space: normal;}
index 2a77538..6f4f49e 100644 (file)
@@ -109,7 +109,8 @@ foreach ($parts as $part) {
         $contentfile = "$CFG->libdir/yui/$part";
     }
     if (!file_exists($contentfile) or !is_file($contentfile)) {
-        $content .= "\n// Combo resource $part ($contentfile) not found!\n";
+        $location = '$CFG->dirroot'.preg_replace('/^'.preg_quote($CFG->dirroot, '/').'/', '', $contentfile);
+        $content .= "\n// Combo resource $part ($location) not found!\n";
         continue;
     }
     $filecontent = file_get_contents($contentfile);
index b5cb4eb..81e5e0b 100644 (file)
@@ -118,6 +118,12 @@ if (has_capability('moodle/user:viewhiddendetails', $context)) {
     $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
 }
 
+if (has_capability('moodle/site:viewuseridentity', $context)) {
+    $identityfields = array_flip(explode(',', $CFG->showuseridentity));
+} else {
+    $identityfields = array();
+}
+
 // Start setting up the page
 $strpublicprofile = get_string('publicprofile');
 
@@ -247,23 +253,34 @@ if (! isset($hiddenfields['city']) && $user->city) {
     print_row(get_string('city') . ':', $user->city);
 }
 
-if (has_capability('moodle/user:viewhiddendetails', $context)) {
-    if ($user->address) {
-        print_row(get_string("address").":", "$user->address");
-    }
-    if ($user->phone1) {
-        print_row(get_string("phone").":", "$user->phone1");
-    }
-    if ($user->phone2) {
-        print_row(get_string("phone2").":", "$user->phone2");
-    }
+if (isset($identityfields['address']) && $user->address) {
+    print_row(get_string("address").":", "$user->address");
+}
+
+if (isset($identityfields['phone1']) && $user->phone1) {
+    print_row(get_string("phone").":", "$user->phone1");
 }
 
-if ($currentuser
+if (isset($identityfields['phone2']) && $user->phone2) {
+    print_row(get_string("phone2").":", "$user->phone2");
+}
+
+if (isset($identityfields['institution']) && $user->institution) {
+    print_row(get_string("institution").":", "$user->institution");
+}
+
+if (isset($identityfields['department']) && $user->department) {
+    print_row(get_string("department").":", "$user->department");
+}
+
+if (isset($identityfields['idnumber']) && $user->idnumber) {
+    print_row(get_string("idnumber").":", "$user->idnumber");
+}
+
+if (isset($identityfields['email']) and ($currentuser
   or $user->maildisplay == 1
   or has_capability('moodle/course:useremail', $context)
-  or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER))) {
-
+  or ($user->maildisplay == 2 and enrol_sharing_course($user, $USER)))) {
     print_row(get_string("email").":", obfuscate_mailto($user->email, ''));
 }
 
index 2f379da..13f2855 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012062501.11;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012062501.12;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.3.1+ (Build: 20120823)'; // Human-friendly version name
+$release  = '2.3.1+ (Build: 20120831)'; // Human-friendly version name
 
 $branch   = '23';                       // this version's branch
 $maturity = MATURITY_STABLE;            // this version's maturity level