Merge branch 'MDL-34187_23' of git://github.com/timhunt/moodle into MOODLE_23_STABLE
authorDan Poltawski <dan@moodle.com>
Mon, 16 Jul 2012 07:57:31 +0000 (15:57 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 16 Jul 2012 07:57:31 +0000 (15:57 +0800)
39 files changed:
admin/cli/mysql_collation.php [new file with mode: 0644]
admin/roles/check.php
auth/shibboleth/login.php
blocks/blog_recent/block_blog_recent.php
blog/index.php
course/delete.php
course/dndupload.js
course/externallib.php
course/lib.php
enrol/authorize/index.php
enrol/database/tests/adodb_test.php
lang/en/error.php
lang/en/role.php
lib/accesslib.php
lib/completion/cron.php
lib/ddl/mysql_sql_generator.php
lib/dml/mysqli_native_moodle_database.php
lib/form/yui/dateselector/assets/skins/sam/dateselector.css
lib/form/yui/dateselector/dateselector.js
lib/formslib.php
lib/javascript-static.js
lib/yui/formchangechecker/formchangechecker.js
mod/assign/locallib.php
mod/assignment/lib.php
mod/chat/gui_ajax/module.js
mod/data/export.php
mod/forum/lib.php
mod/quiz/renderer.php
mod/quiz/report/overview/report.php
mod/quiz/report/statistics/report.php
notes/externallib.php
portfolio/googledocs/lib.php
portfolio/picasa/lib.php
question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
repository/filepicker.js
repository/googledocs/lib.php
repository/picasa/lib.php
theme/base/style/course.css
theme/base/style/filemanager.css

diff --git a/admin/cli/mysql_collation.php b/admin/cli/mysql_collation.php
new file mode 100644 (file)
index 0000000..9938a3a
--- /dev/null
@@ -0,0 +1,211 @@
+<?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/>.
+
+/**
+ * MySQL collation conversion tool.
+ *
+ * @package    core
+ * @copyright  2012 Petr Skoda (http://skodak.org)
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(dirname(dirname(dirname(__FILE__))).'/config.php');
+require_once($CFG->libdir.'/clilib.php');      // cli only functions
+
+if ($DB->get_dbfamily() !== 'mysql') {
+    cli_error('This function is designed for MySQL databases only!');
+}
+
+// now get cli options
+list($options, $unrecognized) = cli_get_params(array('help'=>false, 'list'=>false, 'collation'=>false, 'available'=>false),
+    array('h'=>'help', 'l'=>'list', 'a'=>'available'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+$help =
+    "MySQL collation conversions script.
+
+It is strongly recommended to stop the web server before the conversion.
+This script may be executed before the main upgrade - 1.9.x data for example.
+
+Options:
+--collation=COLLATION Convert MySQL tables to different collation
+-l, --list            Show table and column information
+-a, --available       Show list of available collations
+-h, --help            Print out this help
+
+Example:
+\$ sudo -u www-data /usr/bin/php admin/cli/mysql_collation.php --collation=utf8_general_ci
+";
+
+if (!empty($options['collation'])) {
+    $collations = mysql_get_collations();
+    $collation = clean_param($options['collation'], PARAM_ALPHANUMEXT);
+    $collation = strtolower($collation);
+    if (!isset($collations[$collation])) {
+        cli_error("Error: collation '$collation' is not available on this server!");
+    }
+
+    echo "Converting tables and columns to '$collation' for $CFG->wwwroot:\n";
+    $prefix = $DB->get_prefix();
+    $prefix = str_replace('_', '\\_', $prefix);
+    $sql = "SHOW TABLE STATUS WHERE Name LIKE BINARY '$prefix%'";
+    $rs = $DB->get_recordset_sql($sql);
+    $converted = 0;
+    $skipped   = 0;
+    $errors    = 0;
+    foreach ($rs as $table) {
+        echo str_pad($table->name, 40). " - ";
+
+        if ($table->collation === $collation) {
+            echo "NO CHANGE\n";
+            $skipped++;
+
+        } else {
+            $DB->change_database_structure("ALTER TABLE $table->name DEFAULT COLLATE = $collation");
+            echo "CONVERTED\n";
+            $converted++;
+        }
+
+        $sql = "SHOW FULL COLUMNS FROM $table->name WHERE collation IS NOT NULL";
+        $rs2 = $DB->get_recordset_sql($sql);
+        foreach ($rs2 as $column) {
+            $column = (object)array_change_key_case((array)$column, CASE_LOWER);
+            echo '    '.str_pad($column->field, 36). " - ";
+            if ($column->collation === $collation) {
+                echo "NO CHANGE\n";
+                $skipped++;
+                continue;
+            }
+
+            if ($column->type === 'tinytext' or $column->type === 'mediumtext' or $column->type === 'text' or $column->type === 'longtext') {
+                $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
+                $default = (!is_null($column->default) and $column->default !== '') ? "DEFAULT '$column->default'" : '';
+                // primary, unique and inc are not supported for texts
+                $sql = "ALTER TABLE $table->name MODIFY COLUMN $column->field $column->type COLLATE $collation $notnull $default";
+                $DB->change_database_structure($sql);
+
+            } else if (strpos($column->type, 'varchar') === 0) {
+                $notnull = ($column->null === 'NO') ? 'NOT NULL' : 'NULL';
+                $default = !is_null($column->default) ? "DEFAULT '$column->default'" : '';
+                // primary, unique and inc are not supported for texts
+                $sql = "ALTER TABLE $table->name MODIFY COLUMN $column->field $column->type COLLATE $collation $notnull $default";
+                $DB->change_database_structure($sql);
+            } else {
+                echo "ERROR (unknown column type: $column->type)\n";
+                $error++;
+                continue;
+            }
+            echo "CONVERTED\n";
+            $converted++;
+        }
+        $rs2->close();
+    }
+    $rs->close();
+    echo "Converted: $converted, skipped: $skipped, errors: $errors\n";
+    exit(0); // success
+
+} else if (!empty($options['list'])) {
+    echo "List of tables for $CFG->wwwroot:\n";
+    $prefix = $DB->get_prefix();
+    $prefix = str_replace('_', '\\_', $prefix);
+    $sql = "SHOW TABLE STATUS WHERE Name LIKE BINARY '$prefix%'";
+    $rs = $DB->get_recordset_sql($sql);
+    $counts = array();
+    foreach ($rs as $table) {
+        if (isset($counts[$table->collation])) {
+            $counts[$table->collation]++;
+        } else {
+            $counts[$table->collation] = 1;
+        }
+        echo str_pad($table->name, 40);
+        echo $table->collation.  "\n";
+        $collations = mysql_get_column_collations($table->name);
+        foreach ($collations as $columname=>$collation) {
+            if (isset($counts[$collation])) {
+                $counts[$collation]++;
+            } else {
+                $counts[$collation] = 1;
+            }
+            echo '    ';
+            echo str_pad($columname, 36);
+            echo $collation.  "\n";
+        }
+    }
+    $rs->close();
+
+    echo "\n";
+    echo "Table collations summary for $CFG->wwwroot:\n";
+    foreach ($counts as $collation => $count) {
+        echo "$collation: $count\n";
+    }
+    exit(0); // success
+
+} else if (!empty($options['available'])) {
+    echo "List of available MySQL collations for $CFG->wwwroot:\n";
+    $collations = mysql_get_collations();
+    foreach ($collations as $collation) {
+        echo " $collation\n";
+    }
+    die;
+
+} else {
+    echo $help;
+    die;
+}
+
+
+
+// ========== Some functions ==============
+
+function mysql_get_collations() {
+    global $DB;
+
+    $collations = array();
+    $sql = "SHOW COLLATION WHERE Collation LIKE 'utf8\_%' AND Charset = 'utf8'";
+    $rs = $DB->get_recordset_sql($sql);
+    foreach ($rs as $collation) {
+        $collations[$collation->collation] = $collation->collation;
+    }
+    $rs->close();
+
+    $collation = $DB->get_dbcollation();
+    if (isset($collations[$collation])) {
+        $collations[$collation] .= ' (default)';
+    }
+
+    return $collations;
+}
+
+function mysql_get_column_collations($tablename) {
+    global $DB;
+
+    $collations = array();
+    $sql = "SELECT column_name, collation_name
+              FROM INFORMATION_SCHEMA.COLUMNS
+             WHERE table_schema = DATABASE() AND table_name = ? AND collation_name IS NOT NULL";
+    $rs = $DB->get_recordset_sql($sql, array($tablename));
+    foreach($rs as $record) {
+        $collations[$record->column_name] = $record->collation_name;
+    }
+    $rs->close();
+    return $collations;
+}
index 1f6f6ac..470159e 100644 (file)
@@ -104,20 +104,45 @@ switch ($context->contextlevel) {
         break;
 }
 
+// Get the list of the reported-on user's role assignments - must be after
+// the page setup code above, or the language might be wrong.
+$reportuser = $userselector->get_selected_user();
+if (!is_null($reportuser)) {
+    $roleassignments = get_user_roles_with_special($context, $reportuser->id);
+    $rolenames = role_get_names($context);
+}
+
 echo $OUTPUT->header();
-// These are needed early because of tabs.php
-$assignableroles = get_assignable_roles($context, ROLENAME_BOTH);
-$overridableroles = get_overridable_roles($context, ROLENAME_BOTH);
 
 // Print heading.
 echo $OUTPUT->heading($title);
 
 // If a user has been chosen, show all the permissions for this user.
-$reportuser = $userselector->get_selected_user();
 if (!is_null($reportuser)) {
     echo $OUTPUT->box_start('generalbox boxaligncenter boxwidthwide');
-    echo $OUTPUT->heading(get_string('permissionsforuser', 'role', fullname($reportuser)), 3);
 
+    if (!empty($roleassignments)) {
+        echo $OUTPUT->heading(get_string('rolesforuser', 'role', fullname($reportuser)), 3);
+        echo html_writer::start_tag('ul');
+
+        $systemcontext = context_system::instance();
+        foreach ($roleassignments as $ra) {
+            $racontext = context::instance_by_id($ra->contextid);
+            $link = html_writer::link($racontext->get_url(), $racontext->get_context_name());
+
+            $rolename = $rolenames[$ra->roleid]->localname;
+            if (has_capability('moodle/role:manage', $systemcontext)) {
+                $rolename = html_writer::link(new moodle_url('/admin/roles/define.php',
+                        array('action' => 'view', 'roleid' => $ra->roleid)), $rolename);
+            }
+
+            echo html_writer::tag('li', get_string('roleincontext', 'role',
+                    array('role' => $rolename, 'context' => $link)));
+        }
+        echo html_writer::end_tag('ul');
+    }
+
+    echo $OUTPUT->heading(get_string('permissionsforuser', 'role', fullname($reportuser)), 3);
     $table = new check_capability_table($context, $reportuser, $contextname);
     $table->display();
     echo $OUTPUT->box_end();
index ee3372f..6f984c5 100644 (file)
@@ -63,6 +63,7 @@ $PAGE->https_required();
     $loginsite = get_string("loginsite");
 
     $PAGE->set_url('/auth/shibboleth/login.php');
+    $PAGE->set_context(context_system::instance());
     $PAGE->navbar->add($loginsite);
     $PAGE->set_title("$site->fullname: $loginsite");
     $PAGE->set_heading($site->fullname);
index b3d564f..fe6c970 100644 (file)
@@ -86,17 +86,20 @@ class block_blog_recent extends block_base {
 
         $context = $this->page->context;
 
+        $url = new moodle_url('/blog/index.php');
         $filter = array();
         if ($context->contextlevel == CONTEXT_MODULE) {
             $filter['module'] = $context->instanceid;
             $a = new stdClass;
             $a->type = get_string('modulename', $this->page->cm->modname);
             $strview = get_string('viewallmodentries', 'blog', $a);
+            $url->param('modid', $context->instanceid);
         } else if ($context->contextlevel == CONTEXT_COURSE) {
             $filter['course'] = $context->instanceid;
             $a = new stdClass;
             $a->type = get_string('course');
             $strview = get_string('viewblogentries', 'blog', $a);
+            $url->param('courseid', $context->instanceid);
         } else {
             $strview = get_string('viewsiteentries', 'blog');
         }
@@ -104,7 +107,6 @@ class block_blog_recent extends block_base {
 
         $bloglisting = new blog_listing($filter);
         $entries = $bloglisting->get_entries(0, $this->config->numberofrecentblogentries, 4);
-        $url = new moodle_url('/blog/index.php', $filter);
 
         if (!empty($entries)) {
             $entrieslist = array();
index 77476a9..c008427 100644 (file)
@@ -143,7 +143,7 @@ if (!empty($groupid)) {
     }
 
     if (!$course = $DB->get_record('course', array('id'=>$group->courseid))) {
-        print_error(get_string('invalidcourseid', 'blog'));
+        print_error('invalidcourseid');
     }
 
     $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
index ba9c3be..ebe6b78 100644 (file)
     $strcategories = get_string("categories");
 
     if (! $course = $DB->get_record("course", array("id"=>$id))) {
-        print_error("invalidcourseid", 'error', '', $id);
+        print_error("invalidcourseid");
     }
     if ($site->id == $course->id) {
         // can not delete frontpage!
-        print_error("invalidcourseid", 'error', '', $id);
+        print_error("invalidcourseid");
     }
 
     $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
index 4651120..7bfef1d 100644 (file)
@@ -514,7 +514,7 @@ M.course_dndupload = {
             namespan: document.createElement('span')
         };
 
-        preview.li.className = 'dndupload-preview activity resource modtype_resource dndupload-hidden';
+        preview.li.className = 'dndupload-preview dndupload-hidden';
 
         preview.div.className = 'mod-indent';
         preview.li.appendChild(preview.div);
index 41c5ab4..bf43d2b 100644 (file)
@@ -722,7 +722,7 @@ class core_course_external extends external_api {
         // Context validation.
 
         if (! ($course = $DB->get_record('course', array('id'=>$params['courseid'])))) {
-            throw new moodle_exception('invalidcourseid', 'error', '', $params['courseid']);
+            throw new moodle_exception('invalidcourseid', 'error');
         }
 
         // Category where duplicated course is going to be created.
index 374b309..0376a3a 100644 (file)
@@ -4033,7 +4033,7 @@ function average_number_of_participants() {
         WHERE ue.enrolid = e.id
             AND e.courseid <> :siteid
             AND c.id = e.courseid
-            AND c.visible = 1) as total';
+            AND c.visible = 1)';
     $params = array('siteid' => $SITE->id);
     $enrolmenttotal = $DB->count_records_sql($sql, $params);
 
@@ -4066,7 +4066,7 @@ function average_number_of_courses_modules() {
         WHERE c.id = cm.course
             AND c.id <> :siteid
             AND cm.visible = 1
-            AND c.visible = 1) as total';
+            AND c.visible = 1)';
     $params = array('siteid' => $SITE->id);
     $moduletotal = $DB->count_records_sql($sql, $params);
 
index 5bdee18..7b93b04 100644 (file)
@@ -53,7 +53,7 @@
 
 /// Get course
     if (!($course = $DB->get_record('course', array('id'=>$courseid)))) {
-        print_error('invalidcourseid', '', '', $courseid);
+        print_error('invalidcourseid');
     }
 
 /// Only SITE users can access to this page
index 2336d53..7f30c07 100644 (file)
@@ -70,7 +70,11 @@ class core_adodb_testcase extends advanced_testcase {
                 set_config('dbsetupsql', 'SET NAMES \'UTF-8\'', 'enrol_database');
                 set_config('dbsybasequoting', '0', 'enrol_database');
                 if (!empty($CFG->dboptions['dbsocket']) and ($CFG->dbhost === 'localhost' or $CFG->dbhost === '127.0.0.1')) {
-                    set_config('dbhost', $CFG->dboptions['dbsocket'], 'enrol_database');
+                    if (strpos($CFG->dboptions['dbsocket'], '/') !== false) {
+                      set_config('dbhost', $CFG->dboptions['dbsocket'], 'enrol_database');
+                    } else {
+                      set_config('dbhost', '', 'enrol_database');
+                    }
                 }
                 break;
 
index b40859b..7ad032c 100644 (file)
@@ -280,7 +280,7 @@ $string['invalidcomponent'] = 'Invalid component name';
 $string['invalidconfirmdata'] = 'Invalid confirmation data';
 $string['invalidcontext'] = 'Invalid context';
 $string['invalidcourse'] = 'Invalid course';
-$string['invalidcourseid'] = 'You are trying to use an invalid course ID: ({$a})';
+$string['invalidcourseid'] = 'You are trying to use an invalid course ID';
 $string['invalidcourselevel'] = 'Incorrect context level';
 $string['invalidcoursemodule'] = 'Invalid course module ID';
 $string['invalidcoursenameshort'] = 'Invalid short course name';
index a8058ad..ffbdec3 100644 (file)
@@ -301,6 +301,7 @@ $string['role:assign'] = 'Assign roles to users';
 $string['roleassignments'] = 'Role assignments';
 $string['roledefinitions'] = 'Role definitions';
 $string['rolefullname'] = 'Role name';
+$string['roleincontext'] = '{$a->role} in {$a->context}';
 $string['role:manage'] = 'Create and manage roles';
 $string['role:override'] = 'Override permissions for others';
 $string['role:review'] = 'Review permissions for others';
@@ -311,6 +312,7 @@ $string['roles_help'] = 'A role is a collection of permissions defined for the w
 $string['roles_link'] = 'roles';
 $string['role:safeoverride'] = 'Override safe permissions for others';
 $string['roleselect'] = 'Select role';
+$string['rolesforuser'] = 'Roles for user {$a}';
 $string['roleshortname'] = 'Short name';
 $string['role:switchroles'] = 'Switch to other roles';
 $string['roletoassign'] = 'Role to assign';
index de180b4..b8aa9f9 100644 (file)
@@ -3020,6 +3020,53 @@ function get_user_roles(context $context, $userid = 0, $checkparentcontexts = tr
     return $DB->get_records_sql($sql ,$params);
 }
 
+/**
+ * Like get_user_roles, but adds in the authenticated user role, and the front
+ * page roles, if applicable.
+ *
+ * @param context $context the context.
+ * @param int $userid optional. Defaults to $USER->id
+ * @return array of objects with fields ->userid, ->contextid and ->roleid.
+ */
+function get_user_roles_with_special(context $context, $userid = 0) {
+    global $CFG, $USER;
+
+    if (empty($userid)) {
+        if (empty($USER->id)) {
+            return array();
+        }
+        $userid = $USER->id;
+    }
+
+    $ras = get_user_roles($context, $userid);
+
+    // Add front-page role if relevant.
+    $defaultfrontpageroleid = isset($CFG->defaultfrontpageroleid) ? $CFG->defaultfrontpageroleid : 0;
+    $isfrontpage = ($context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID) ||
+            is_inside_frontpage($context);
+    if ($defaultfrontpageroleid && $isfrontpage) {
+        $frontpagecontext = context_course::instance(SITEID);
+        $ra = new stdClass();
+        $ra->userid = $userid;
+        $ra->contextid = $frontpagecontext->id;
+        $ra->roleid = $defaultfrontpageroleid;
+        $ras[] = $ra;
+    }
+
+    // Add authenticated user role if relevant.
+    $defaultuserroleid      = isset($CFG->defaultuserroleid) ? $CFG->defaultuserroleid : 0;
+    if ($defaultuserroleid && !isguestuser($userid)) {
+        $systemcontext = context_system::instance();
+        $ra = new stdClass();
+        $ra->userid = $userid;
+        $ra->contextid = $systemcontext->id;
+        $ra->roleid = $defaultuserroleid;
+        $ras[] = $ra;
+    }
+
+    return $ras;
+}
+
 /**
  * Creates a record in the role_allow_override table
  *
@@ -4136,6 +4183,15 @@ function role_get_name($role, context_course $coursecontext) {
     }
 }
 
+/**
+ * Get all the localised role names for a context.
+ * @param context $context the context
+ * @param array of role objects with a ->localname field containing the context-specific role name.
+ */
+function role_get_names(context $context) {
+    return role_fix_names(get_all_roles(), $context);
+}
+
 /**
  * Prepare list of roles for display, apply aliases and format text
  *
index 3645c06..bf2a646 100644 (file)
@@ -377,10 +377,11 @@ function completion_cron_completions() {
         SET
             reaggregate = 0
         WHERE
-            reaggregate < {$timestarted}
+            reaggregate < :timestarted
+        AND reaggregate > 0
     ";
 
-    $DB->execute($sql);
+    $DB->execute($sql, array('timestarted' => $timestarted));
 }
 
 /**
index 6ae310f..bbd6de2 100644 (file)
@@ -117,23 +117,25 @@ class mysql_sql_generator extends sql_generator {
      * by any of its comments, indexes and sequence creation SQL statements.
      */
     public function getCreateTableSQL($xmldb_table) {
-        // first find out if want some special db engine
-        $engine = null;
-        if (method_exists($this->mdb, 'get_dbengine')) {
-            $engine = $this->mdb->get_dbengine();
-        }
+        // First find out if want some special db engine.
+        $engine = $this->mdb->get_dbengine();
+        // Do we know collation?
+        $collation = $this->mdb->get_dbcollation();
 
         $sqlarr = parent::getCreateTableSQL($xmldb_table);
 
-        if (!$engine) {
-            // we rely on database defaults
-            return $sqlarr;
-        }
-
-        // let's inject the engine into SQL
+        // Let's inject the extra MySQL tweaks.
         foreach ($sqlarr as $i=>$sql) {
             if (strpos($sql, 'CREATE TABLE ') === 0) {
-                $sqlarr[$i] .= " ENGINE = $engine";
+                if ($engine) {
+                    $sqlarr[$i] .= " ENGINE = $engine";
+                }
+                if ($collation) {
+                    if (strpos($collation, 'utf8_') === 0) {
+                        $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8";
+                    }
+                    $sqlarr[$i] .= " DEFAULT COLLATE = $collation";
+                }
             }
         }
 
@@ -148,9 +150,26 @@ class mysql_sql_generator extends sql_generator {
      * @return array of sql statements
      */
     public function getCreateTempTableSQL($xmldb_table) {
+        // Do we know collation?
+        $collation = $this->mdb->get_dbcollation();
         $this->temptables->add_temptable($xmldb_table->getName());
-        $sqlarr = parent::getCreateTableSQL($xmldb_table); // we do not want the engine hack included in create table SQL
-        $sqlarr = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sqlarr);
+
+        $sqlarr = parent::getCreateTableSQL($xmldb_table);
+
+        // Let's inject the extra MySQL tweaks.
+        foreach ($sqlarr as $i=>$sql) {
+            if (strpos($sql, 'CREATE TABLE ') === 0) {
+                // We do not want the engine hack included in create table SQL.
+                $sqlarr[$i] = preg_replace('/^CREATE TABLE (.*)/s', 'CREATE TEMPORARY TABLE $1', $sql);
+                if ($collation) {
+                    if (strpos($collation, 'utf8_') === 0) {
+                        $sqlarr[$i] .= " DEFAULT CHARACTER SET utf8";
+                    }
+                    $sqlarr[$i] .= " DEFAULT COLLATE $collation";
+                }
+            }
+        }
+
         return $sqlarr;
     }
 
@@ -231,9 +250,21 @@ class mysql_sql_generator extends sql_generator {
                     $xmldb_length='255';
                 }
                 $dbtype .= '(' . $xmldb_length . ')';
+                if ($collation = $this->mdb->get_dbcollation()) {
+                    if (strpos($collation, 'utf8_') === 0) {
+                        $dbtype .= " CHARACTER SET utf8";
+                    }
+                    $dbtype .= " COLLATE $collation";
+                }
                 break;
             case XMLDB_TYPE_TEXT:
                 $dbtype = 'LONGTEXT';
+                if ($collation = $this->mdb->get_dbcollation()) {
+                    if (strpos($collation, 'utf8_') === 0) {
+                        $dbtype .= " CHARACTER SET utf8";
+                    }
+                    $dbtype .= " COLLATE $collation";
+                }
                 break;
             case XMLDB_TYPE_BINARY:
                 $dbtype = 'LONGBLOB';
@@ -333,18 +364,14 @@ class mysql_sql_generator extends sql_generator {
      */
     public function isNameInUse($object_name, $type, $table_name) {
 
-        // Calculate the real table name
-        $xmldb_table = new xmldb_table($table_name);
-        $tname = $this->getTableName($xmldb_table);
-
         switch($type) {
             case 'ix':
             case 'uix':
                 // First of all, check table exists
                 $metatables = $this->mdb->get_tables();
-                if (isset($metatables[$tname])) {
+                if (isset($metatables[$table_name])) {
                     // Fetch all the indexes in the table
-                    if ($indexes = $this->mdb->get_indexes($tname)) {
+                    if ($indexes = $this->mdb->get_indexes($table_name)) {
                         // Look for existing index in array
                         if (isset($indexes[$object_name])) {
                             return true;
index 93311bd..9294c07 100644 (file)
@@ -82,7 +82,13 @@ class mysqli_native_moodle_database extends moodle_database {
             throw new dml_connection_exception($dberr);
         }
 
-        $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci");
+        if (isset($dboptions['dbcollation']) and strpos($dboptions['dbcollation'], 'utf8_') === 0) {
+            $collation = $dboptions['dbcollation'];
+        } else {
+            $collation = 'utf8_unicode_ci';
+        }
+
+        $result = $conn->query("CREATE DATABASE $dbname DEFAULT CHARACTER SET utf8 DEFAULT COLLATE ".$collation);
 
         $conn->close();
 
@@ -146,22 +152,28 @@ class mysqli_native_moodle_database extends moodle_database {
             return $this->dboptions['dbengine'];
         }
 
+        if ($this->external) {
+            return null;
+        }
+
         $engine = null;
 
-        if (!$this->external) {
-            // look for current engine of our config table (the first table that gets created),
-            // so that we create all tables with the same engine
-            $sql = "SELECT engine FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config'";
-            $this->query_start($sql, NULL, SQL_QUERY_AUX);
-            $result = $this->mysqli->query($sql);
-            $this->query_end($result);
-            if ($rec = $result->fetch_assoc()) {
-                $engine = $rec['engine'];
-            }
-            $result->close();
+        // Look for current engine of our config table (the first table that gets created),
+        // so that we create all tables with the same engine.
+        $sql = "SELECT engine
+                  FROM INFORMATION_SCHEMA.TABLES
+                 WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config'";
+        $this->query_start($sql, NULL, SQL_QUERY_AUX);
+        $result = $this->mysqli->query($sql);
+        $this->query_end($result);
+        if ($rec = $result->fetch_assoc()) {
+            $engine = $rec['engine'];
         }
+        $result->close();
 
         if ($engine) {
+            // Cache the result to improve performance.
+            $this->dboptions['dbengine'] = $engine;
             return $engine;
         }
 
@@ -175,7 +187,7 @@ class mysqli_native_moodle_database extends moodle_database {
         }
         $result->close();
 
-        if (!$this->external and $engine === 'MyISAM') {
+        if ($engine === 'MyISAM') {
             // we really do not want MyISAM for Moodle, InnoDB or XtraDB is a reasonable defaults if supported
             $sql = "SHOW STORAGE ENGINES";
             $this->query_start($sql, NULL, SQL_QUERY_AUX);
@@ -196,9 +208,77 @@ class mysqli_native_moodle_database extends moodle_database {
             }
         }
 
+        // Cache the result to improve performance.
+        $this->dboptions['dbengine'] = $engine;
         return $engine;
     }
 
+    /**
+     * Returns the current MySQL db collation.
+     *
+     * This is an ugly workaround for MySQL default collation problems.
+     *
+     * @return string or null MySQL collation name
+     */
+    public function get_dbcollation() {
+        if (isset($this->dboptions['dbcollation'])) {
+            return $this->dboptions['dbcollation'];
+        }
+        if ($this->external) {
+            return null;
+        }
+
+        $collation = null;
+
+        // Look for current collation of our config table (the first table that gets created),
+        // so that we create all tables with the same collation.
+        $sql = "SELECT collation_name
+                  FROM INFORMATION_SCHEMA.COLUMNS
+                 WHERE table_schema = DATABASE() AND table_name = '{$this->prefix}config' AND column_name = 'value'";
+        $this->query_start($sql, NULL, SQL_QUERY_AUX);
+        $result = $this->mysqli->query($sql);
+        $this->query_end($result);
+        if ($rec = $result->fetch_assoc()) {
+            $collation = $rec['collation_name'];
+        }
+        $result->close();
+
+        if (!$collation) {
+            // Get the default database collation, but only if using UTF-8.
+            $sql = "SELECT @@collation_database";
+            $this->query_start($sql, NULL, SQL_QUERY_AUX);
+            $result = $this->mysqli->query($sql);
+            $this->query_end($result);
+            if ($rec = $result->fetch_assoc()) {
+                if (strpos($rec['@@collation_database'], 'utf8_') === 0) {
+                    $collation = $rec['@@collation_database'];
+                }
+            }
+            $result->close();
+        }
+
+        if (!$collation) {
+            // We want only utf8 compatible collations.
+            $collation = null;
+            $sql = "SHOW COLLATION WHERE Collation LIKE 'utf8\_%' AND Charset = 'utf8'";
+            $this->query_start($sql, NULL, SQL_QUERY_AUX);
+            $result = $this->mysqli->query($sql);
+            $this->query_end($result);
+            while ($res = $result->fetch_assoc()) {
+                $collation = $res['Collation'];
+                if (strtoupper($res['Default']) === 'YES') {
+                    $collation = $res['Collation'];
+                    break;
+                }
+            }
+            $result->close();
+        }
+
+        // Cache the result to improve performance.
+        $this->dboptions['dbcollation'] = $collation;
+        return $collation;
+    }
+
     /**
      * Returns localised database type name
      * Note: can be used before connect()
index 01a37e7..d8abda4 100644 (file)
@@ -1,3 +1,4 @@
 #dateselector-calendar-panel {background-color:#999;border-bottom:3px solid #999;border-right:3px solid #999;}
 #dateselector-calendar-content {border:1px solid #666;margin-top:-3px;margin-left:-3px;}
+#dateselector-calendar-content_t th.calweekdaycell {padding-left:3px; padding-right:3px;}
 body.ie #dateselector-calendar-panel.yui3-overlay-hidden table {display:none;}
\ No newline at end of file
index d403547..1109b22 100644 (file)
@@ -197,7 +197,30 @@ YUI.add('moodle-form-dateselector', function(Y) {
             this.calendar = new YAHOO.widget.Calendar(document.getElementById('dateselector-calendar-content'), {
                 iframe: false,
                 hide_blank_weeks: true,
-                start_weekday: config.firstdayofweek
+                start_weekday: config.firstdayofweek,
+                locale_weekdays: 'medium',
+                locale_months: 'long',
+                WEEKDAYS_MEDIUM: [
+                    config.sun,
+                    config.mon,
+                    config.tue,
+                    config.wed,
+                    config.thu,
+                    config.fri,
+                    config.sat ],
+                MONTHS_LONG: [
+                    config.january,
+                    config.february,
+                    config.march,
+                    config.april,
+                    config.may,
+                    config.june,
+                    config.july,
+                    config.august,
+                    config.september,
+                    config.october,
+                    config.november,
+                    config.december ],
             });
             this.calendar.changePageEvent.subscribe(function(){
                 this.fix_position();
index 306c4e5..d02d332 100644 (file)
@@ -79,7 +79,28 @@ function form_init_date_js() {
     if (!$done) {
         $module   = 'moodle-form-dateselector';
         $function = 'M.form.dateselector.init_date_selectors';
-        $config = array(array('firstdayofweek'=>get_string('firstdayofweek', 'langconfig')));
+        $config = array(array(
+            'firstdayofweek'    =>  get_string('firstdayofweek', 'langconfig'),
+            'mon'               => strftime('%a', 360000),      // 5th Jan 1970 at 12pm
+            'tue'               => strftime('%a', 446400),
+            'wed'               => strftime('%a', 532800),
+            'thu'               => strftime('%a', 619200),
+            'fri'               => strftime('%a', 705600),
+            'sat'               => strftime('%a', 792000),
+            'sun'               => strftime('%a', 878400),
+            'january'           => strftime('%B', 14400),       // 1st Jan 1970 at 12pm
+            'february'          => strftime('%B', 2692800),
+            'march'             => strftime('%B', 5112000),
+            'april'             => strftime('%B', 7790400),
+            'may'               => strftime('%B', 10382400),
+            'june'              => strftime('%B', 13060800),
+            'july'              => strftime('%B', 15652800),
+            'august'            => strftime('%B', 18331200),
+            'september'         => strftime('%B', 21009600),
+            'october'           => strftime('%B', 23601600),
+            'november'          => strftime('%B', 26280000),
+            'december'          => strftime('%B', 28872000)
+        ));
         $PAGE->requires->yui_module($module, $function, $config);
         $done = true;
     }
index 6bf2e28..5640b44 100644 (file)
@@ -1779,6 +1779,7 @@ M.util.load_flowplayer = function() {
                     // why??
                     continue;
                 }
+                if (!allrules) continue;
                 for(var i=0; i<allrules.length; i++) {
                     rule = '';
                     if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
index dc819aa..a4fa15b 100644 (file)
@@ -88,7 +88,11 @@ YUI.add('moodle-core-formchangechecker',
         /**
          * Set the form changed state to true
          */
-        M.core_formchangechecker.set_form_changed = function() {
+        M.core_formchangechecker.set_form_changed = function(e) {
+            if (e.target.hasClass('ignoredirty')) {
+                // Don't warn on elements with the ignoredirty class
+                return;
+            }
             M.core_formchangechecker.stateinformation.formchanged = 1;
 
             // Once the form has been marked as dirty, we no longer need to keep track of form elements
index 2fef79b..85ff77e 100644 (file)
@@ -1744,7 +1744,7 @@ class assign {
         if (!empty($CFG->enableplagiarism)) {
             /** Include plagiarismlib.php */
             require_once($CFG->libdir . '/plagiarismlib.php');
-            plagiarism_update_status($this->get_course(), $this->get_course_module());
+            $o .= plagiarism_update_status($this->get_course(), $this->get_course_module());
         }
 
         $actionformtext = $this->output->render($gradingactions);
@@ -1808,11 +1808,8 @@ class assign {
         if (!empty($CFG->enableplagiarism)) {
             /** Include plagiarismlib.php */
             require_once($CFG->libdir . '/plagiarismlib.php');
-            ob_start();
 
-            plagiarism_print_disclosure($this->get_course_module()->id);
-            $o = ob_get_contents();
-            ob_end_clean();
+            $o .= plagiarism_print_disclosure($this->get_course_module()->id);
         }
 
         return $o;
index 8f8dcf3..eea2714 100644 (file)
@@ -1591,7 +1591,7 @@ class assignment_base {
                     }
                     $currentposition++;
                 }
-                if ($hassubmission && ($this->assignment->assignmenttype=='upload' || $this->assignment->assignmenttype=='online' || $this->assignment->assignmenttype=='uploadsingle')) { //TODO: this is an ugly hack, where is the plugin spirit? (skodak)
+                if ($hassubmission && method_exists($this, 'download_submissions')) {
                     echo html_writer::start_tag('div', array('class' => 'mod-assignment-download-link'));
                     echo html_writer::link(new moodle_url('/mod/assignment/submissions.php', array('id' => $this->cm->id, 'download' => 'zip')), get_string('downloadall', 'assignment'));
                     echo html_writer::end_tag('div');
index 71c0241..fc48974 100644 (file)
@@ -247,11 +247,11 @@ M.mod_chat_ajax.init = function(Y, cfg) {
                     li.all('td').item(1).append(Y.Node.create('<strong><a target="_blank" href="'+users[i].url+'">'+ users[i].name+'</a></strong>'));
                 } else {
                     li.all('td').item(1).append(Y.Node.create('<div><a target="_blank" href="'+users[i].url+'">'+users[i].name+'</a></div>'));
-                    var talk = Y.Node.create('<a href="###">'+M.str.chat.talk+'</a>&nbsp;');
+                    var talk = Y.Node.create('<a href="###">'+M.str.chat.talk+'</a>');
                     talk.on('click', this.talkto, this, users[i].name);
                     var beep = Y.Node.create('<a href="###">'+M.str.chat.beep+'</a>');
                     beep.on('click', this.send, this, users[i].id);
-                    li.all('td').item(1).append(Y.Node.create('<div></div>').append(talk).append(beep));
+                    li.all('td').item(1).append(Y.Node.create('<div></div>').append(talk).append('&nbsp;').append(beep));
                 }
                 list.append(li);
             }
index 3040ef6..40fc894 100644 (file)
@@ -41,7 +41,7 @@ if (! $cm = get_coursemodule_from_instance('data', $data->id, $data->course)) {
 }
 
 if(! $course = $DB->get_record('course', array('id'=>$cm->course))) {
-    print_error('invalidcourseid', '', '', $cm->course);
+    print_error('invalidcourseid');
 }
 
 // fill in missing properties needed for updating of instance
index 812172a..4b7a8cc 100644 (file)
@@ -383,7 +383,7 @@ WHERE
  * @return string A unique message-id
  */
 function forum_get_email_message_id($postid, $usertoid, $hostname) {
-    return '<'.hash('sha256',$postid.'to'.$usertoid.'@'.$hostname).'>';
+    return '<'.hash('sha256',$postid.'to'.$usertoid).'@'.$hostname.'>';
 }
 
 /**
index 391fc90..820e711 100644 (file)
@@ -1149,6 +1149,24 @@ class mod_quiz_renderer extends plugin_renderer_base {
                 'id' => $cm->id, 'mode' => quiz_report_default_report($context)));
         return html_writer::link($url, $summary);
     }
+
+    /**
+     * Output a graph, or a message saying that GD is required.
+     * @param moodle_url $url the URL of the graph.
+     * @param string $title the title to display above the graph.
+     * @return string HTML fragment for the graph.
+     */
+    public function graph(moodle_url $url, $title) {
+        global $CFG;
+
+        if (empty($CFG->gdversion)) {
+            $graph = get_string('gdneed');
+        } else {
+            $graph = html_writer::empty_tag('img', array('src' => $url, 'alt' => $title));
+        }
+
+        return $this->heading($title) . html_writer::tag('div', $graph, array('class' => 'graph'));
+    }
 }
 
 class mod_quiz_links_to_other_attempts implements renderable {
index ae2e1f6..27b4df5 100644 (file)
@@ -40,7 +40,7 @@ require_once($CFG->dirroot . '/mod/quiz/report/overview/overview_table.php');
 class quiz_overview_report extends quiz_attempts_report {
 
     public function display($quiz, $cm, $course) {
-        global $CFG, $DB, $OUTPUT;
+        global $CFG, $DB, $OUTPUT, $PAGE;
 
         list($currentgroup, $students, $groupstudents, $allowed) =
                 $this->init('overview', 'quiz_overview_settings_form', $quiz, $cm, $course);
@@ -237,30 +237,25 @@ class quiz_overview_report extends quiz_attempts_report {
         }
 
         if (!$table->is_downloading() && $options->usercanseegrades) {
+            $output = $PAGE->get_renderer('mod_quiz');
             if ($currentgroup && $groupstudents) {
                 list($usql, $params) = $DB->get_in_or_equal($groupstudents);
                 $params[] = $quiz->id;
                 if ($DB->record_exists_select('quiz_grades', "userid $usql AND quiz = ?",
                         $params)) {
-                     $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
+                    $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
                             array('id' => $quiz->id, 'groupid' => $currentgroup));
-                     $graphname = get_string('overviewreportgraphgroup', 'quiz_overview',
+                    $graphname = get_string('overviewreportgraphgroup', 'quiz_overview',
                             groups_get_group_name($currentgroup));
-                     echo $OUTPUT->heading($graphname);
-                     echo html_writer::tag('div', html_writer::empty_tag('img',
-                            array('src' => $imageurl, 'alt' => $graphname)),
-                            array('class' => 'graph'));
+                    echo $output->graph($imageurl, $graphname);
                 }
             }
 
             if ($DB->record_exists('quiz_grades', array('quiz'=> $quiz->id))) {
-                 $graphname = get_string('overviewreportgraph', 'quiz_overview');
-                 $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
+                $imageurl = new moodle_url('/mod/quiz/report/overview/overviewgraph.php',
                         array('id' => $quiz->id));
-                 echo $OUTPUT->heading($graphname);
-                 echo html_writer::tag('div', html_writer::empty_tag('img',
-                        array('src' => $imageurl, 'alt' => $graphname)),
-                        array('class' => 'graph'));
+                $graphname = get_string('overviewreportgraph', 'quiz_overview');
+                echo $output->graph($imageurl, $graphname);
             }
         }
         return true;
index 99cbd31..48c91ee 100644 (file)
@@ -579,18 +579,17 @@ class quiz_statistics_report extends quiz_default_report {
      * @param int $quizstatsid the id of the statistics to show in the graph.
      */
     protected function output_statistics_graph($quizstatsid, $s) {
-        global $OUTPUT;
+        global $PAGE;
 
         if ($s == 0) {
             return;
         }
 
+        $output = $PAGE->get_renderer('mod_quiz');
         $imageurl = new moodle_url('/mod/quiz/report/statistics/statistics_graph.php',
                 array('id' => $quizstatsid));
-        $OUTPUT->heading(get_string('statisticsreportgraph', 'quiz_statistics'));
-        echo html_writer::tag('div', html_writer::empty_tag('img', array('src' => $imageurl,
-                'alt' => get_string('statisticsreportgraph', 'quiz_statistics'))),
-                array('class' => 'graph'));
+        $graphname = get_string('statisticsreportgraph', 'quiz_statistics');
+        echo $output->graph($imageurl, $graphname);
     }
 
     /**
index fc31e9b..f671042 100644 (file)
@@ -110,7 +110,7 @@ class core_notes_external extends external_api {
             //check the course exists
             if (empty($courses[$note['courseid']])) {
                 $success = false;
-                $errormessage = get_string('invalidcourseid', 'notes', $note['courseid']);
+                $errormessage = get_string('invalidcourseid', 'error');
             } else {
                 // Ensure the current user is allowed to run this function
                 $context = get_context_instance(CONTEXT_COURSE, $note['courseid']);
index 0405ba5..a7b87d0 100644 (file)
@@ -108,7 +108,9 @@ class portfolio_plugin_googledocs extends portfolio_plugin_push_base {
         $mform->addElement('static', null, '', get_string('oauthinfo', 'portfolio_googledocs', $a));
 
         $mform->addElement('text', 'clientid', get_string('clientid', 'portfolio_googledocs'));
+        $mform->setType('clientid', PARAM_RAW_TRIMMED);
         $mform->addElement('text', 'secret', get_string('secret', 'portfolio_googledocs'));
+        $mform->setType('secret', PARAM_RAW_TRIMMED);
 
         $strrequired = get_string('required');
         $mform->addRule('clientid', $strrequired, 'required', null, 'client');
index cbef42c..fff8760 100644 (file)
@@ -108,7 +108,9 @@ class portfolio_plugin_picasa extends portfolio_plugin_push_base {
         $mform->addElement('static', null, '', get_string('oauthinfo', 'portfolio_picasa', $a));
 
         $mform->addElement('text', 'clientid', get_string('clientid', 'portfolio_picasa'));
+        $mform->setType('clientid', PARAM_RAW_TRIMMED);
         $mform->addElement('text', 'secret', get_string('secret', 'portfolio_picasa'));
+        $mform->setType('secret', PARAM_RAW_TRIMMED);
 
         $strrequired = get_string('required');
         $mform->addRule('clientid', $strrequired, 'required', null, 'client');
index 5dd1e86..9f1b67b 100644 (file)
@@ -68,8 +68,8 @@ class restore_qtype_shortanswer_plugin extends restore_qtype_plugin {
         $questioncreated = $this->get_mappingid('question_created', $oldquestionid) ? true : false;
 
         // If the question has been created by restore, we need to create its
-        // question_shortanswer too
-        if ($questioncreated) {
+        // question_shortanswer too, if they are defined (the gui should ensure this).
+        if ($questioncreated && !empty($data->answers)) {
             // Adjust some columns
             $data->question = $newquestionid;
             // Map sequence of question_answer ids
index d23f361..7c4e1f4 100644 (file)
@@ -1737,7 +1737,7 @@ M.core_filepicker.init = function(Y, options) {
                 setAttrs({id:'fp-tb-help-'+client_id+'-link', target:'_blank'}).
                 setStyle('display', 'none');
             toolbar.append(helplnk);
-            toolbar.one('.fp-tb-manage').one('a,button').
+            toolbar.one('.fp-tb-help').one('a,button').
                 on('click', function(e) {
                     e.preventDefault();
                     helplnk.simulate('click')
index ee56c02..c3a00e1 100644 (file)
@@ -119,7 +119,9 @@ class repository_googledocs extends repository {
 
         parent::type_config_form($mform);
         $mform->addElement('text', 'clientid', get_string('clientid', 'repository_googledocs'));
+        $mform->setType('clientid', PARAM_RAW_TRIMMED);
         $mform->addElement('text', 'secret', get_string('secret', 'repository_googledocs'));
+        $mform->setType('secret', PARAM_RAW_TRIMMED);
 
         $strrequired = get_string('required');
         $mform->addRule('clientid', $strrequired, 'required', null, 'client');
index 642c7b3..11f8eb0 100644 (file)
@@ -118,7 +118,9 @@ class repository_picasa extends repository {
 
         parent::type_config_form($mform);
         $mform->addElement('text', 'clientid', get_string('clientid', 'repository_picasa'));
+        $mform->setType('clientid', PARAM_RAW_TRIMMED);
         $mform->addElement('text', 'secret', get_string('secret', 'repository_picasa'));
+        $mform->setType('secret', PARAM_RAW_TRIMMED);
 
         $strrequired = get_string('required');
         $mform->addRule('clientid', $strrequired, 'required', null, 'client');
index 09dc4b2..d073270 100644 (file)
@@ -167,7 +167,7 @@ input.titleeditor {
 
 /* Course drag and drop upload styles */
 #dndupload-status {width:40%;margin:0 30%;padding:6px;border:1px solid #ddd;text-align:center;background:#ffc;position:absolute;z-index:9999;box-shadow:2px 2px 5px 1px #ccc;border-radius:0px 0px 8px 8px;z-index: 0;}
-.dndupload-preview {color:#909090;border:1px dashed #909090;}
+.dndupload-preview {color:#909090;border:1px dashed #909090;list-style:none;}
 .dndupload-progress-outer {width:70px;border:1px solid black;height:10px;display:inline-block;margin:0;padding:0;overflow:hidden;position:relative;}
 .dndupload-progress-inner {width:0%;height:100%;background-color:green;display:inline-block;margin:0;padding:0;float:left;}
 .dndupload-hidden {display:none;}
index 836233a..5fbacc3 100644 (file)
@@ -290,7 +290,7 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 /*.filemanager-container ul{margin:0;padding:0;}
 .filemanager-container ul li{white-space:nowrap;list-style-type:none;}
 .filemanager-container ul li a{padding:0}*/
-.filemanager .fp-content{overflow: auto;max-height: 472px;}
+.filemanager .fp-content{overflow: auto;max-height: 472px;min-height: 157px;}
 .filemanager-container, .filepicker-filelist {overflow:hidden;}
 
 /*