Merge branch 'mdl34368' of https://bitbucket.org/ngmares/moodle into MOODLE_23_STABLE
authorDan Poltawski <dan@moodle.com>
Mon, 30 Jul 2012 07:49:12 +0000 (15:49 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 30 Jul 2012 07:49:12 +0000 (15:49 +0800)
132 files changed:
admin/cli/mysql_collation.php [new file with mode: 0644]
admin/roles/check.php
admin/tool/dbtransfer/cli/migrate.php [new file with mode: 0644]
admin/tool/dbtransfer/database_export_form.php
admin/tool/dbtransfer/database_transfer_form.php
admin/tool/dbtransfer/dbexport.php
admin/tool/dbtransfer/index.php
admin/tool/dbtransfer/lang/en/tool_dbtransfer.php
admin/tool/dbtransfer/locallib.php
admin/tool/dbtransfer/settings.php
admin/tool/dbtransfer/version.php
auth/shibboleth/login.php
blocks/blog_recent/block_blog_recent.php
blocks/navigation/renderer.php
blog/edit_form.php
blog/index.php
blog/lib.php
course/delete.php
course/dndupload.js
course/editsection.php
course/externallib.php
course/format/renderer.php
course/lib.php
course/modduplicate.php
course/reset.php
course/togglecompletion.php
enrol/authorize/index.php
enrol/database/tests/adodb_test.php
enrol/index.php
enrol/self/locallib.php
grade/edit/tree/lib.php
grade/grading/form/rubric/lib.php
install/lang/ca/install.php
install/lang/ga/langconfig.php
install/lang/is/admin.php
install/lang/is/langconfig.php
install/lang/pt/admin.php
install/lang/sw/langconfig.php [new file with mode: 0644]
lang/en/error.php
lang/en/form.php
lang/en/moodle.php
lang/en/question.php
lang/en/role.php
lib/accesslib.php
lib/completion/completion_criteria_completion.php
lib/completion/cron.php
lib/db/upgrade.php
lib/ddl/mysql_sql_generator.php
lib/dml/mysqli_native_moodle_database.php
lib/dtl/database_exporter.php
lib/dtl/database_importer.php
lib/dtl/database_mover.php
lib/dtl/file_xml_database_exporter.php
lib/dtl/file_xml_database_importer.php
lib/dtl/string_xml_database_exporter.php
lib/dtl/string_xml_database_importer.php
lib/dtl/xml_database_exporter.php
lib/dtl/xml_database_importer.php
lib/editor/tinymce/lib.php
lib/form/dndupload.js
lib/form/filemanager.js
lib/form/filepicker.js
lib/form/form.js
lib/form/yui/dateselector/assets/skins/sam/dateselector.css
lib/form/yui/dateselector/dateselector.js
lib/formslib.php
lib/grade/grade_category.php
lib/grade/tests/grade_category_test.php
lib/javascript-static.js
lib/moodlelib.php
lib/outputrenderers.php
lib/pagelib.php
lib/phpunit/classes/util.php
lib/tests/moodlelib_test.php
lib/upgradelib.php
lib/xmldb/xmldb_field.php
lib/yui/formchangechecker/formchangechecker.js
mod/assign/adminlib.php
mod/assign/gradingoptionsform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/renderable.php
mod/assign/renderer.php
mod/assign/settings.php
mod/assignment/lib.php
mod/assignment/type/upload/upload.php
mod/chat/gui_ajax/index.php
mod/chat/gui_ajax/module.js
mod/chat/gui_basic/index.php
mod/chat/lang/en/chat.php
mod/choice/renderer.php
mod/data/export.php
mod/forum/lib.php
mod/quiz/attempt.php
mod/quiz/cronlib.php
mod/quiz/lib.php
mod/quiz/module.js
mod/quiz/renderer.php
mod/quiz/report/attemptsreport_table.php
mod/quiz/report/overview/overviewgraph.php
mod/quiz/report/overview/report.php
mod/quiz/report/reportlib.php
mod/quiz/report/statistics/report.php
mod/quiz/version.php
mod/scorm/backup/moodle2/backup_scorm_stepslib.php
mod/scorm/locallib.php
mod/scorm/mod_form.php
mod/scorm/report/interactions/report.php
notes/externallib.php
portfolio/googledocs/lib.php
portfolio/picasa/lib.php
question/engine/questionattempt.php
question/engine/questionusage.php
question/engine/tests/questionusagebyactivity_test.php
question/type/multianswer/tests/helper.php
question/type/multianswer/tests/walkthrough_test.php
question/type/multichoice/question.php
question/type/shortanswer/backup/moodle2/restore_qtype_shortanswer_plugin.class.php
repository/filepicker.js
repository/googledocs/lib.php
repository/lib.php
repository/picasa/lib.php
repository/user/lib.php
theme/afterburner/style/afterburner_styles.css
theme/base/style/course.css
theme/base/style/filemanager.css
theme/fusion/style/core.css
theme/mymobile/config.php
theme/mymobile/style/jmobile11_rtl.css [new file with mode: 0644]
version.php

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();
diff --git a/admin/tool/dbtransfer/cli/migrate.php b/admin/tool/dbtransfer/cli/migrate.php
new file mode 100644 (file)
index 0000000..0d32e2a
--- /dev/null
@@ -0,0 +1,191 @@
+<?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/>.
+
+/**
+ * This script migrates data from current database to another
+ *
+ * This script is not intended for beginners!
+ * Potential problems:
+ * - su to apache account or sudo before execution
+ * - already broken DB scheme or invalid data
+ *
+ * @package    tool_dbtransfer
+ * @copyright  2012 Petr Skoda {@link http://skodak.org/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../../../config.php');
+require_once($CFG->libdir.'/clilib.php');
+require_once(__DIR__.'/../locallib.php');
+
+$help =
+    "Database migration script.
+
+It is strongly recommended to turn off the web server
+or enable CLI maintenance mode before starting the migration.
+
+Options:
+--dbtype=TYPE         Database type.
+--dblibrary=TYPE      Database library. Defaults to 'native'.
+--dbhost=HOST         Database host.
+--dbname=NAME         Database name.
+--dbuser=USERNAME     Database user.
+--dbpass=PASSWORD     Database password.
+--dbport=NUMBER       Database port.
+--prefix=STRING       Table prefix for above database tables.
+--dbsocket=PATH       Use database sockets. Available for some databases only.
+-h, --help            Print out this help.
+
+Example:
+\$ sudo -u www-data /usr/bin/php admin/tool/dbtransfer/cli/migrate.php
+";
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(
+    array(
+        'dbtype'            => null,
+        'dblibrary'         => 'native',
+        'dbhost'            => null,
+        'dbname'            => null,
+        'dbuser'            => null,
+        'dbpass'            => null,
+        'dbport'            => null,
+        'prefix'            => null,
+        'dbsocket'          => null,
+        'maintenance'       => null,
+        'list'              => false,
+        'help'              => false,
+    ),
+    array(
+        'm' => 'maintenance',
+        'l' => 'list',
+        'h' => 'help',
+    )
+);
+
+if ($options['help']) {
+    echo $help;
+    exit(0);
+}
+
+if (empty($CFG->version)) {
+    cli_error(get_string('missingconfigversion', 'debug'));
+}
+
+echo "\n".get_string('cliheading', 'tool_dbtransfer')."\n\n";
+
+$drivers = tool_dbtransfer_get_drivers();
+
+if (!isset($options['dbtype'])) {
+    $choose = array();
+    foreach ($drivers as $driver => $name) {
+        list($dbtype, $dblibrary) = explode('/', $driver);
+        $choose[$dbtype] = $dbtype;
+    }
+    $optionsstr = implode(', ', $choose);
+    cli_heading(get_string('databasetypehead', 'install')." ($optionsstr)");
+    $options['dbtype'] = cli_input(get_string('clitypevalue', 'admin'), '', $choose, true);
+}
+
+$choose = array();
+foreach ($drivers as $driver => $name) {
+    list($dbtype, $dblibrary) = explode('/', $driver);
+    if ($dbtype === $options['dbtype']) {
+        $choose[$dblibrary] = $dblibrary;
+    }
+}
+if (!isset($options['dblibrary']) or !isset($choose[$options['dblibrary']])) {
+    $optionsstr = implode(', ', $choose);
+    cli_heading('Database library'." ($optionsstr)"); // Note: no need to localise unless we add real PDO drivers.
+    $options['dblibrary'] = cli_input(get_string('clitypevalue', 'admin'), '', $choose, true);
+}
+
+if (!isset($options['dbhost'])) {
+    cli_heading(get_string('databasehost', 'install'));
+    $options['dbhost'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if (!isset($options['dbname'])) {
+    cli_heading(get_string('databasename', 'install'));
+    $options['dbname'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if (!isset($options['dbuser'])) {
+    cli_heading(get_string('databaseuser', 'install'));
+    $options['dbuser'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if (!isset($options['dbpass'])) {
+    cli_heading(get_string('databasepass', 'install'));
+    $options['dbpass'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if (!isset($options['prefix'])) {
+    cli_heading(get_string('dbprefix', 'install'));
+    $options['prefix'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if (!isset($options['dbport'])) {
+    cli_heading(get_string('dbport', 'install'));
+    $options['dbport'] = cli_input(get_string('clitypevalue', 'admin'));
+}
+
+if ($CFG->ostype !== 'WINDOWS') {
+    if (!isset($options['dbsocket'])) {
+        cli_heading(get_string('databasesocket', 'install'));
+        $options['dbsocket'] = cli_input(get_string('clitypevalue', 'admin'));
+    }
+}
+
+$a = (object)array('dbtypefrom' => $CFG->dbtype, 'dbtype' => $options['dbtype'],
+    'dbname' => $options['dbname'], 'dbhost' => $options['dbhost']);
+cli_heading(get_string('transferringdbto', 'tool_dbtransfer', $a));
+
+// Try target DB connection.
+$problem = '';
+
+$targetdb = moodle_database::get_driver_instance($options['dbtype'], $options['dblibrary']);
+$dboptions = array();
+if ($options['dbport']) {
+    $dboptions['dbport'] = $options['dbport'];
+}
+if ($options['dbsocket']) {
+    $dboptions['dbsocket'] = $options['dbsocket'];
+}
+try {
+    $targetdb->connect($options['dbhost'], $options['dbuser'], $options['dbpass'], $options['dbname'],
+        $options['prefix'], $dboptions);
+    if ($targetdb->get_tables()) {
+        $problem .= get_string('targetdatabasenotempty', 'tool_dbtransfer');
+    }
+} catch (moodle_exception $e) {
+    $problem .= $e->debuginfo."\n\n";
+    $problem .= get_string('notargetconectexception', 'tool_dbtransfer');
+}
+
+if ($problem !== '') {
+    echo $problem."\n\n";
+    exit(1);
+}
+
+$feedback = new text_progress_trace();
+tool_dbtransfer_transfer_database($DB, $targetdb, $feedback);
+$feedback->finished();
+
+cli_heading(get_string('success'));
+exit(0);
index 72b7c52..1fb8b9c 100644 (file)
 /**
  * Transfer form
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die;
 
-require_once $CFG->libdir.'/formslib.php';
+require_once($CFG->libdir.'/formslib.php');
 
-class database_export_form extends moodleform {
 
-    function definition() {
+/**
+ * Definition of db export settings form.
+ *
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class database_export_form extends moodleform {
+    /**
+     * Define the export form.
+     */
+    public function definition() {
         $mform = $this->_form;
 
         $mform->addElement('header', 'database', get_string('dbexport', 'tool_dbtransfer'));
index 9b699e9..d8a9923 100644 (file)
 /**
  * Transfer form
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die;
 
-require_once $CFG->libdir.'/formslib.php';
+require_once($CFG->libdir.'/formslib.php');
+require_once(__DIR__.'/locallib.php');
 
+
+/**
+ * Definition of db transfer settings form.
+ *
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
 class database_transfer_form extends moodleform {
 
-    function definition() {
+    /**
+     * Define transfer form.
+     */
+    protected function definition() {
+        global $CFG;
+
         $mform = $this->_form;
 
-        $mform->addElement('header', 'database', get_string('dbtransfer', 'tool_dbtransfer'));
+        $mform->addElement('header', 'database', get_string('targetdatabase', 'tool_dbtransfer'));
 
-        $supported = array (
-            'mysqli/native',
-            'pgsql/native',
-            'mssql/native',
-            'oci/native',
-            'sqlsrv/native',
-        );
-        $drivers = array();
-        foreach($supported as $driver) {
-            list($dbtype, $dblibrary) = explode('/', $driver);
-            $targetdb = moodle_database::get_driver_instance($dbtype, $dblibrary);
-            if ($targetdb->driver_installed() !== true) {
-                continue;
-            }
-            $drivers[$driver] = $driver;
-        }
+        $drivers = tool_dbtransfer_get_drivers();
+        $drivers = array_reverse($drivers, true);
+        $drivers[''] = get_string('choosedots');
+        $drivers = array_reverse($drivers, true);
 
         $mform->addElement('select', 'driver', get_string('dbtype', 'install'), $drivers);
-        $mform->addElement('text', 'dbhost', get_string('dbhost', 'install'));
-        $mform->addElement('text', 'dbname', get_string('database', 'install'));
-        $mform->addElement('text', 'dbuser', get_string('user'));
-        $mform->addElement('text', 'dbpass', get_string('password'));
+        $mform->addElement('text', 'dbhost', get_string('databasehost', 'install'));
+        $mform->addElement('text', 'dbname', get_string('databasename', 'install'));
+        $mform->addElement('text', 'dbuser', get_string('databaseuser', 'install'));
+        $mform->addElement('passwordunmask', 'dbpass', get_string('databasepass', 'install'));
         $mform->addElement('text', 'prefix', get_string('dbprefix', 'install'));
         $mform->addElement('text', 'dbport', get_string('dbport', 'install'));
-        $mform->addElement('text', 'dbsocket', get_string('databasesocket', 'install'));
+        if ($CFG->ostype !== 'WINDOWS') {
+            $mform->addElement('text', 'dbsocket', get_string('databasesocket', 'install'));
+        } else {
+            $mform->addElement('hidden', 'dbsocket');
+        }
 
+        $mform->addRule('driver', get_string('required'), 'required', null);
         $mform->addRule('dbhost', get_string('required'), 'required', null);
         $mform->addRule('dbname', get_string('required'), 'required', null);
         $mform->addRule('dbuser', get_string('required'), 'required', null);
         $mform->addRule('dbpass', get_string('required'), 'required', null);
-        $mform->addRule('prefix', get_string('required'), 'required', null);
+        if (!isset($drivers['mysqli/native'])) {
+            $mform->addRule('prefix', get_string('required'), 'required', null);
+        }
+
+        $mform->addElement('header', 'database', get_string('options', 'tool_dbtransfer'));
+
+        $mform->addElement('advcheckbox', 'enablemaintenance', get_string('enablemaintenance', 'tool_dbtransfer'));
+        $mform->addHelpButton('enablemaintenance', 'enablemaintenance', 'tool_dbtransfer');
 
         $this->add_action_buttons(false, get_string('transferdata', 'tool_dbtransfer'));
     }
+
+    /**
+     * Validate prefix is present for non-mysql drivers.
+     * @param array $data
+     * @param array $files
+     * @return array
+     */
+    public function validation($data, $files) {
+        $errors = parent::validation($data, $files);
+        if ($data['driver'] !== 'mysqli/native') {
+            // This is a bloody hack, let's pretend we do not need to look at db family...
+            if ($data['prefix'] === '') {
+                $errors['prefix'] = get_string('required');
+            }
+        }
+        return $errors;
+    }
 }
index 40f35ff..f55b29d 100644 (file)
@@ -17,9 +17,8 @@
 /**
  * Export
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
@@ -32,15 +31,15 @@ require_once('database_export_form.php');
 require_login();
 admin_externalpage_setup('tooldbexport');
 
-//create form
+// Create form.
 $form = new database_export_form();
 
 if ($data = $form->get_data()) {
-    dbtransfer_export_xml_database($data->description, $DB);
+    tool_dbtransfer_export_xml_database($data->description, $DB);
     die;
 }
 
 echo $OUTPUT->header();
-// TODO: add some more info here
+// TODO: add some more info here.
 $form->display();
 echo $OUTPUT->footer();
index 2b1a2c5..aa8b5b3 100644 (file)
@@ -17,9 +17,8 @@
 /**
  * Transfer tool
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
@@ -32,8 +31,9 @@ require_once('database_transfer_form.php');
 require_login();
 admin_externalpage_setup('tooldbtransfer');
 
-// Create the form
+// Create the form.
 $form = new database_transfer_form();
+$problem = '';
 
 // If we have valid input.
 if ($data = $form->get_data()) {
@@ -47,33 +47,63 @@ if ($data = $form->get_data()) {
     if ($data->dbsocket) {
         $dboptions['dbsocket'] = $data->dbsocket;
     }
-    if (!$targetdb->connect($data->dbhost, $data->dbuser, $data->dbpass, $data->dbname, $data->prefix, $dboptions)) {
-        throw new dbtransfer_exception('notargetconectexception', null, "$CFG->wwwroot/$CFG->admin/tool/dbtransfer/");
-    }
-    if ($targetdb->get_tables()) {
-        throw new dbtransfer_exception('targetdatabasenotempty', null, "$CFG->wwwroot/$CFG->admin/tool/dbtransfer/");
+    try {
+        $targetdb->connect($data->dbhost, $data->dbuser, $data->dbpass, $data->dbname, $data->prefix, $dboptions);
+        if ($targetdb->get_tables()) {
+            $problem .= get_string('targetdatabasenotempty', 'tool_dbtransfer');
+        }
+    } catch (moodle_exception $e) {
+        $problem .= get_string('notargetconectexception', 'tool_dbtransfer').'<br />'.$e->debuginfo;
     }
 
-    // Start output.
-    echo $OUTPUT->header();
-    $data->dbtype = $dbtype;
-    echo $OUTPUT->heading(get_string('transferringdbto', 'tool_dbtransfer', $data));
+    if ($problem === '') {
+        // Scroll down to the bottom when finished.
+        $PAGE->requires->js_init_code("window.scrollTo(0, 5000000);");
+
+        // Enable CLI maintenance mode if requested.
+        if ($data->enablemaintenance) {
+            $PAGE->set_pagelayout('maintenance');
+            tool_dbtransfer_create_maintenance_file();
+        }
 
-    // Do the transfer.
-    $feedback = new html_list_progress_trace();
-    dbtransfer_transfer_database($DB, $targetdb, $feedback);
-    $feedback->finished();
+        // Start output.
+        echo $OUTPUT->header();
+        $data->dbtype = $dbtype;
+        $data->dbtypefrom = $CFG->dbtype;
+        echo $OUTPUT->heading(get_string('transferringdbto', 'tool_dbtransfer', $data));
 
-    // Finish up.
-    echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
-    echo $OUTPUT->continue_button("$CFG->wwwroot/$CFG->admin/");
-    echo $OUTPUT->footer();
-    die;
+        // Do the transfer.
+        $CFG->tool_dbransfer_migration_running = true;
+        try {
+            $feedback = new html_list_progress_trace();
+            tool_dbtransfer_transfer_database($DB, $targetdb, $feedback);
+            $feedback->finished();
+        } catch (Exception $e) {
+            if ($data->enablemaintenance) {
+                tool_dbtransfer_maintenance_callback();
+            }
+            unset($CFG->tool_dbransfer_migration_running);
+            throw $e;
+        }
+        unset($CFG->tool_dbransfer_migration_running);
+
+        // Finish up.
+        echo $OUTPUT->notification(get_string('success'), 'notifysuccess');
+        echo $OUTPUT->continue_button("$CFG->wwwroot/$CFG->admin/");
+        echo $OUTPUT->footer();
+        die;
+    }
 }
 
 // Otherwise display the settings form.
 echo $OUTPUT->header();
 echo $OUTPUT->heading(get_string('transferdbtoserver', 'tool_dbtransfer'));
-echo '<p>', get_string('transferdbintro', 'tool_dbtransfer'), "</p>\n\n";
+
+$info = format_text(get_string('transferdbintro', 'tool_dbtransfer'), FORMAT_MARKDOWN);
+echo $OUTPUT->box($info);
+
 $form->display();
+if ($problem !== '') {
+    echo $OUTPUT->box($problem, 'generalbox error');
+}
 echo $OUTPUT->footer();
index 1279af5..a67a3e9 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Strings for component 'tool_generator', language 'en', branch 'MOODLE_22_STABLE'
+ * Strings for component 'tool_generator', language 'en'.
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2011 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2011 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$string['dbexport'] = 'Database transfer';
-$string['dbtransfer'] = 'Database export';
+$string['clidriverlist'] = 'Available database drivers for migration';
+$string['cliheading'] = 'Database migration - make sure nobody is accessing the server during migration!';
+$string['climigrationnotice'] = 'Database migration in progress, please wait until the migration completes and server administrator updates configuration and deletes the $CFG->dataroot/climaintenance.html file.';
+$string['convertinglogdisplay'] = 'Converting log display actions';
+$string['dbexport'] = 'Database export';
+$string['dbtransfer'] = 'Database migration';
+$string['enablemaintenance'] = 'Enable maintenance mode';
+$string['enablemaintenance_help'] = 'This option enables maintanance mode during and after the database migration, it prevents access of all users until the migration is completed. Please note that administrator has to manually delete $CFG->dataroot/climaintenance.html file after updating config.php settings to resume normal operation.';
 $string['exportdata'] = 'Export data';
 $string['notargetconectexception'] = 'Can not connect target database, sorry.';
+$string['options'] = 'Options';
 $string['pluginname'] = 'Database transfer';
+$string['targetdatabase'] = 'Target database';
+$string['targetdatabasenotempty'] = 'Target database must not contain any tables with given prefix!';
 $string['transferdata'] = 'Transfer data';
-$string['transferdbintro'] = 'This script will transfer the entire contents of this database to another database server.';
+$string['transferdbintro'] = 'This script will transfer the entire contents of this database to another database server. It is often used for migration of data to different database type.';
 $string['transferdbtoserver'] = 'Transfer this Moodle database to another server';
-$string['transferringdbto'] = 'Transferring this database to {$a->dbtype} database {$a->dbname} on {$a->dbhost}';
+$string['transferringdbto'] = 'Transferring this {$a->dbtypefrom} database to {$a->dbtype} database "{$a->dbname}" on "{$a->dbhost}"';
 
index f36ceef..8b0b062 100644 (file)
@@ -17,9 +17,8 @@
 /**
  * Export db content to file.
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda {@link http://skodak.org}
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
@@ -44,11 +43,16 @@ TODO:
 require_once($CFG->libdir.'/adminlib.php');
 require_once($CFG->libdir.'/dtllib.php');
 
-
-function dbtransfer_export_xml_database($description, $mdb) {
+/**
+ * Initiate database export.
+ * @param string $description
+ * @param moodle_database $mdb
+ * @return does not return, calls die()
+ */
+function tool_dbtransfer_export_xml_database($description, $mdb) {
     @set_time_limit(0);
 
-    session_get_instance()->write_close(); // release session
+    session_get_instance()->write_close(); // Release session.
 
     header('Content-Type: application/xhtml+xml; charset=utf-8');
     header('Content-Disposition: attachment; filename=database.xml');
@@ -61,16 +65,145 @@ function dbtransfer_export_xml_database($description, $mdb) {
     $var = new file_xml_database_exporter('php://output', $mdb);
     $var->export_database($description);
 
-    // no more output
+    // No more output.
     die;
 }
 
-
-function dbtransfer_transfer_database($sourcedb, $targetdb, $feedback = null) {
+/**
+ * Initiate database transfer.
+ * @param moodle_database $sourcedb
+ * @param moodle_database $targetdb
+ * @param progress_trace $feedback
+ * @return void
+ */
+function tool_dbtransfer_transfer_database(moodle_database $sourcedb, moodle_database $targetdb, progress_trace $feedback = null) {
     @set_time_limit(0);
 
-    session_get_instance()->write_close(); // release session
+    session_get_instance()->write_close(); // Release session.
 
     $var = new database_mover($sourcedb, $targetdb, true, $feedback);
     $var->export_database(null);
+
+    tool_dbtransfer_rebuild_target_log_actions($targetdb, $feedback);
+}
+
+/**
+ * Very hacky function for rebuilding of log actions in target database.
+ * @param moodle_database $target
+ * @param progress_trace $feedback
+ * @return void
+ * @throws Exception on conversion error
+ */
+function tool_dbtransfer_rebuild_target_log_actions(moodle_database $target, progress_trace $feedback = null) {
+    global $DB, $CFG;
+    require_once("$CFG->libdir/upgradelib.php");
+
+    $feedback->output(get_string('convertinglogdisplay', 'tool_dbtransfer'));
+
+    $olddb = $DB;
+    $DB = $target;
+    try {
+        $DB->delete_records('log_display', array('component'=>'moodle'));
+        log_update_descriptions('moodle');
+        $plugintypes = get_plugin_types();
+        foreach ($plugintypes as $type => $location) {
+            $plugs = get_plugin_list($type);
+            foreach ($plugs as $plug => $fullplug) {
+                $component = $type.'_'.$plug;
+                $DB->delete_records('log_display', array('component'=>$component));
+                log_update_descriptions($component);
+            }
+        }
+    } catch (Exception $e) {
+        $DB = $olddb;
+        throw $e;
+    }
+    $DB = $olddb;
+    $feedback->output(get_string('done', 'core_dbtransfer', null), 1);
+}
+
+/**
+ * Returns list of fully working database drivers present in system.
+ * @return array
+ */
+function tool_dbtransfer_get_drivers() {
+    global $CFG;
+
+    $files = new RegexIterator(new DirectoryIterator("$CFG->libdir/dml"), '|^.*_moodle_database\.php$|');
+    $drivers = array();
+
+    foreach ($files as $file) {
+        $matches = null;
+        preg_match('|^([a-z0-9]+)_([a-z]+)_moodle_database\.php$|', $file->getFilename(), $matches);
+        if (!$matches) {
+            continue;
+        }
+        $dbtype = $matches[1];
+        $dblibrary = $matches[2];
+
+        if ($dbtype === 'sqlite3') {
+            // Blacklist unfinished drivers.
+            continue;
+        }
+
+        $targetdb = moodle_database::get_driver_instance($dbtype, $dblibrary, false);
+        if ($targetdb->driver_installed() !== true) {
+            continue;
+        }
+
+        $driver = $dbtype.'/'.$dblibrary;
+
+        $drivers[$driver] = $targetdb->get_name();
+    };
+
+    return $drivers;
+}
+
+/**
+ * Create CLI maintenance file to prevent all access.
+ */
+function tool_dbtransfer_create_maintenance_file() {
+    global $CFG;
+
+    register_shutdown_function('tool_dbtransfer_maintenance_callback');
+
+    $options = new stdClass();
+    $options->trusted = false;
+    $options->noclean = false;
+    $options->smiley = false;
+    $options->filter = false;
+    $options->para = true;
+    $options->newlines = false;
+
+    $message = format_text(get_string('climigrationnotice', 'tool_dbtransfer'), FORMAT_MARKDOWN, $options);
+    $message = bootstrap_renderer::early_error_content($message, '', '', array());
+    $html = <<<OET
+<!DOCTYPE html>
+<html>
+<header><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /><header/>
+<body>$message</body>
+</html>
+OET;
+
+    file_put_contents("$CFG->dataroot/climaintenance.html", $html);
+    @chmod("$CFG->dataroot/climaintenance.html", $CFG->filepermissions);
+}
+
+/**
+ * This callback is responsible for unsetting maintenance mode
+ * if the migration is interrupted.
+ */
+function tool_dbtransfer_maintenance_callback() {
+    global $CFG;
+
+    if (empty($CFG->tool_dbransfer_migration_running)) {
+        // Migration was finished properly - keep the maintenance file in place.
+        return;
+    }
+
+    if (file_exists("$CFG->dataroot/climaintenance.html")) {
+        // Failed migration, revert to normal site operation.
+        unlink("$CFG->dataroot/climaintenance.html");
+        error_log('tool_dbtransfer: Interrupted database migration detected, switching off CLI maintenance mode.');
+    }
 }
index 13c45b8..4717542 100644 (file)
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Link to InnoDB conversion tool
+ * Add hidden links db transfer tool
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2011 Petr Skoda {@link http://skodak.org}
+ * @package    tool_dbtransfer
+ * @copyright  2011 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die;
 
 if ($hassiteconfig) {
-    // DB transfer related pages
-    $ADMIN->add('experimental', new admin_externalpage('tooldbtransfer', get_string('dbtransfer', 'tool_dbtransfer'), $CFG->wwwroot.'/'.$CFG->admin.'/tool/dbtransfer/index.php', 'moodle/site:config', true));
-    $ADMIN->add('experimental', new admin_externalpage('tooldbexport', get_string('dbexport', 'tool_dbtransfer'), $CFG->wwwroot.'/'.$CFG->admin.'/tool/dbtransfer/dbexport.php', 'moodle/site:config', true));
-}
\ No newline at end of file
+    $ADMIN->add('experimental', new admin_externalpage('tooldbtransfer', get_string('dbtransfer', 'tool_dbtransfer'),
+        $CFG->wwwroot.'/'.$CFG->admin.'/tool/dbtransfer/index.php', 'moodle/site:config', false));
+    // DB export/import is not ready yet - keep it hidden for now.
+    $ADMIN->add('experimental', new admin_externalpage('tooldbexport', get_string('dbexport', 'tool_dbtransfer'),
+        $CFG->wwwroot.'/'.$CFG->admin.'/tool/dbtransfer/dbexport.php', 'moodle/site:config', true));
+}
index 61aa6ec..59bafe5 100644 (file)
 /**
  * Version details.
  *
- * @package    tool
- * @subpackage dbtransfer
- * @copyright  2008 Petr Skoda
+ * @package    tool_dbtransfer
+ * @copyright  2008 Petr Skoda {@link http://skodak.org/}
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012061700; // The current plugin version (Date: YYYYMMDDXX)
-$plugin->requires  = 2012061700; // Requires this Moodle version
-$plugin->component = 'tool_dbtransfer'; // Full name of the plugin (used for diagnostics)
+$plugin->version   = 2012062200; // The current plugin version (Date: YYYYMMDDXX).
+$plugin->requires  = 2012061700; // Requires this Moodle version.
+$plugin->component = 'tool_dbtransfer'; // Full name of the plugin (used for diagnostics).
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 fe91aed..4c3d28c 100644 (file)
@@ -77,6 +77,11 @@ class block_navigation_renderer extends plugin_renderer_base {
             $isexpandable = (empty($expansionlimit) || ($item->type > navigation_node::TYPE_ACTIVITY || $item->type < $expansionlimit) || ($item->contains_active_node() && $item->children->count() > 0));
             $isbranch = $isexpandable && ($item->children->count() > 0 || ($item->has_children() && (isloggedin() || $item->type <= navigation_node::TYPE_CATEGORY)));
 
+            // Skip elements which have no content and no action - no point in showing them
+            if (!$isexpandable && empty($item->action)) {
+                continue;
+            }
+
             $hasicon = ((!$isbranch || $item->type == navigation_node::TYPE_ACTIVITY || $item->type == navigation_node::TYPE_RESOURCE) && $item->icon instanceof renderable);
 
             if ($hasicon) {
index 292d55c..55ce91d 100644 (file)
@@ -101,6 +101,7 @@ class blog_edit_form extends moodleform {
                     $a = new stdClass();
                     $a->modtype = $DB->get_field('modules', 'name', array('id' => $cm->module));
                     $a->modname = $DB->get_field($a->modtype, 'name', array('id' => $cm->instance));
+                    $modid = $context->instanceid;
                 }
 
                 $mform->addElement('header', 'assochdr', get_string('associations', 'blog'));
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 ab9fd52..8a3b687 100644 (file)
@@ -608,11 +608,14 @@ function blog_get_options_for_module($module, $user=null) {
     $canparticipate = (is_enrolled($modcontext) or is_viewing($modcontext));
 
     if (has_capability('moodle/blog:view', $modcontext)) {
+        // Save correct module name for later usage.
+        $modulename = get_string('modulename', $module->modname);
+
         // We can view!
         if ($CFG->bloglevel >= BLOG_SITE_LEVEL) {
             // View all entries about this module
             $a = new stdClass;
-            $a->type = $module->modname;
+            $a->type = $modulename;
             $options['moduleview'] = array(
                 'string' => get_string('viewallmodentries', 'blog', $a),
                 'link' => new moodle_url('/blog/index.php', array('modid'=>$module->id))
@@ -620,13 +623,13 @@ function blog_get_options_for_module($module, $user=null) {
         }
         // View MY entries about this module
         $options['moduleviewmine'] = array(
-            'string' => get_string('viewmyentriesaboutmodule', 'blog', $module->modname),
+            'string' => get_string('viewmyentriesaboutmodule', 'blog', $modulename),
             'link' => new moodle_url('/blog/index.php', array('modid'=>$module->id, 'userid'=>$USER->id))
         );
         if (!empty($user) && ($CFG->bloglevel >= BLOG_SITE_LEVEL)) {
             // View the given users entries about this module
             $a = new stdClass;
-            $a->mod = $module->modname;
+            $a->mod = $modulename;
             $a->user = fullname($user);
             $options['moduleviewuser'] = array(
                 'string' => get_string('blogentriesbyuseraboutmodule', 'blog', $a),
@@ -638,7 +641,7 @@ function blog_get_options_for_module($module, $user=null) {
     if (has_capability('moodle/blog:create', $sitecontext) and $canparticipate) {
         // The user can blog about this module
         $options['moduleadd'] = array(
-            'string' => get_string('blogaboutthismodule', 'blog', $module->modname),
+            'string' => get_string('blogaboutthismodule', 'blog', $modulename),
             'link' => new moodle_url('/blog/edit.php', array('action'=>'add', 'modid'=>$module->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 ecd905b..e4d6bea 100644 (file)
@@ -33,9 +33,9 @@ require_once($CFG->libdir . '/conditionlib.php');
 require_once('editsection_form.php');
 
 $id = required_param('id',PARAM_INT);    // Week/topic ID
-$sectionreturn = optional_param('sectionreturn', 0, PARAM_BOOL);
+$sectionreturn = optional_param('sr', 0, PARAM_INT);
 
-$PAGE->set_url('/course/editsection.php', array('id'=>$id, 'sectionreturn'=> $sectionreturn));
+$PAGE->set_url('/course/editsection.php', array('id'=>$id, 'sr'=> $sectionreturn));
 
 $section = $DB->get_record('course_sections', array('id' => $id), '*', MUST_EXIST);
 $course = $DB->get_record('course', array('id' => $section->course), '*', MUST_EXIST);
@@ -60,11 +60,7 @@ $mform = new editsection_form($PAGE->url, array('course' => $course, 'editoropti
         'cs' => $section, 'showavailability' => $section->showavailability));
 $mform->set_data($section); // set current value
 
-if ($sectionreturn) {
-    $returnurl = course_get_url($course, $section->section);
-} else {
-    $returnurl = course_get_url($course);
-}
+$returnurl = course_get_url($course, $sectionreturn);
 
 /// If data submitted, then process and store.
 if ($mform->is_cancelled()){
index 41c5ab4..b18c0a4 100644 (file)
@@ -668,13 +668,12 @@ class core_course_external extends external_api {
                 'options' => new external_multiple_structure(
                     new external_single_structure(
                         array(
-                                'name' => new external_value(PARAM_ALPHA, 'The backup option name:
+                                'name' => new external_value(PARAM_ALPHAEXT, 'The backup option name:
                                             "activities" (int) Include course activites (default to 1 that is equal to yes),
                                             "blocks" (int) Include course blocks (default to 1 that is equal to yes),
                                             "filters" (int) Include course filters  (default to 1 that is equal to yes),
                                             "users" (int) Include users (default to 0 that is equal to no),
                                             "role_assignments" (int) Include role assignments  (default to 0 that is equal to no),
-                                            "user_files" (int) Include user files  (default to 0 that is equal to no),
                                             "comments" (int) Include user comments  (default to 0 that is equal to no),
                                             "completion_information" (int) Include user course completion information  (default to 0 that is equal to no),
                                             "logs" (int) Include course logs  (default to 0 that is equal to no),
@@ -722,7 +721,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.
@@ -739,7 +738,6 @@ class core_course_external extends external_api {
             'filters' => 1,
             'users' => 0,
             'role_assignments' => 0,
-            'user_files' => 0,
             'comments' => 0,
             'completion_information' => 0,
             'logs' => 0,
index b7c0e21..1309025 100644 (file)
@@ -122,9 +122,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
      * @param stdClass $section The course_section entry from DB
      * @param stdClass $course The course entry from DB
      * @param bool $onsectionpage true if being printed on a single-section page
+     * @param int $sectionreturn The section to return to after an action
      * @return string HTML to output.
      */
-    protected function section_header($section, $course, $onsectionpage) {
+    protected function section_header($section, $course, $onsectionpage, $sectionreturn=0) {
         global $PAGE;
 
         $o = '';
@@ -150,7 +151,13 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o.= html_writer::tag('div', $rightcontent, array('class' => 'right side'));
         $o.= html_writer::start_tag('div', array('class' => 'content'));
 
-        if (!$onsectionpage) {
+        // When not on a section page, we display the section titles except the general section if null
+        $hasnamenotsecpg = (!$onsectionpage && ($section->section != 0 || !is_null($section->name)));
+
+        // When on a section page, we only display the general section title, if title is not the default one
+        $hasnamesecpg = ($onsectionpage && ($section->section == 0 && !is_null($section->name)));
+
+        if ($hasnamenotsecpg || $hasnamesecpg) {
             $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
         }
 
@@ -159,12 +166,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
 
         $context = context_course::instance($course->id);
         if ($PAGE->user_is_editing() && has_capability('moodle/course:update', $context)) {
-            $url = new moodle_url('/course/editsection.php', array('id'=>$section->id));
-
-            if ($onsectionpage) {
-                $url->param('sectionreturn', 1);
-            }
-
+            $url = new moodle_url('/course/editsection.php', array('id'=>$section->id, 'sr'=>$sectionreturn));
             $o.= html_writer::link($url,
                 html_writer::empty_tag('img', array('src' => $this->output->pix_url('t/edit'), 'class' => 'iconsmall edit')),
                 array('title' => get_string('editsummary')));
@@ -560,10 +562,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $thissection = $sections[0];
         if ($thissection->summary or $thissection->sequence or $PAGE->user_is_editing()) {
             echo $this->start_section_list();
-            echo $this->section_header($thissection, $course, true);
-            print_section($course, $thissection, $mods, $modnamesused, true);
+            echo $this->section_header($thissection, $course, true, $displaysection);
+            print_section($course, $thissection, $mods, $modnamesused, true, "100%", false, $displaysection);
             if ($PAGE->user_is_editing()) {
-                print_section_add_menus($course, 0, $modnames, false, false, true);
+                print_section_add_menus($course, 0, $modnames, false, false, $displaysection);
             }
             echo $this->section_footer();
             echo $this->end_section_list();
@@ -592,14 +594,14 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
 
         // The requested section page.
         $thissection = $sections[$displaysection];
-        echo $this->section_header($thissection, $course, true);
+        echo $this->section_header($thissection, $course, true, $displaysection);
         // Show completion help icon.
         $completioninfo = new completion_info($course);
         echo $completioninfo->display_help_icon();
 
-        print_section($course, $thissection, $mods, $modnamesused, true, '100%', false, true);
+        print_section($course, $thissection, $mods, $modnamesused, true, '100%', false, $displaysection);
         if ($PAGE->user_is_editing()) {
-            print_section_add_menus($course, $displaysection, $modnames, false, false, true);
+            print_section_add_menus($course, $displaysection, $modnames, false, false, $displaysection);
         }
         echo $this->section_footer();
         echo $this->end_section_list();
@@ -646,7 +648,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $thissection = $sections[0];
         unset($sections[0]);
         if ($thissection->summary or $thissection->sequence or $PAGE->user_is_editing()) {
-            echo $this->section_header($thissection, $course, true);
+            echo $this->section_header($thissection, $course, false);
             print_section($course, $thissection, $mods, $modnamesused, true);
             if ($PAGE->user_is_editing()) {
                 print_section_add_menus($course, 0, $modnames);
index 374b309..36ef842 100644 (file)
@@ -1376,8 +1376,18 @@ function get_print_section_cm_text(cm_info $cm, $course) {
 
 /**
  * Prints a section full of activity modules
+ *
+ * @param stdClass $course The course
+ * @param stdClass $section The section
+ * @param array $mods The modules in the section
+ * @param array $modnamesused An array containing the list of modules and their names
+ * @param bool $absolute All links are absolute
+ * @param string $width Width of the container
+ * @param bool $hidecompletion Hide completion status
+ * @param int $sectionreturn The section to return to
+ * @return void
  */
-function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn = false) {
+function print_section($course, $section, $mods, $modnamesused, $absolute=false, $width="100%", $hidecompletion=false, $sectionreturn=0) {
     global $CFG, $USER, $DB, $PAGE, $OUTPUT;
 
     static $initialised;
@@ -1637,12 +1647,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
                     $mod->groupmode = false;
                 }
                 echo '&nbsp;&nbsp;';
-
-                if ($sectionreturn) {
-                    echo make_editing_buttons($mod, $absolute, true, $mod->indent, $section->section);
-                } else {
-                    echo make_editing_buttons($mod, $absolute, true, $mod->indent, 0);
-                }
+                echo make_editing_buttons($mod, $absolute, true, $mod->indent, $sectionreturn);
                 echo $mod->get_after_edit_icons();
             }
 
@@ -1761,8 +1766,16 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
 
 /**
  * Prints the menus to add activities and resources.
+ *
+ * @param stdClass $course The course
+ * @param stdClass $section The section
+ * @param array $modnames An array containing the list of modules and their names
+ * @param bool $vertical Vertical orientation
+ * @param bool $return Return the menus or send them to output
+ * @param int $sectionreturn The section to link back to
+ * @return void|string depending on $return
  */
-function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false, $sectionreturn = false) {
+function print_section_add_menus($course, $section, $modnames, $vertical=false, $return=false, $sectionreturn=0) {
     global $CFG, $OUTPUT;
 
     // check to see if user can add menus
@@ -1778,14 +1791,7 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
     $activities = array();
 
     // We need to add the section section to the link for each module
-    $sectionlink = '&section=' . $section;
-
-    // We need to add the section to return to
-    if ($sectionreturn) {
-        $sectionreturnlink = '&sr=' . $section;
-    } else {
-        $sectionreturnlink = '&sr=0';
-    }
+    $sectionlink = '&section=' . $section . '&sr=' . $sectionreturn;
 
     foreach ($modules as $module) {
         if (isset($module->types)) {
@@ -1793,7 +1799,7 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
             // NOTE: this is legacy stuff, module subtypes are very strongly discouraged!!
             $subtypes = array();
             foreach ($module->types as $subtype) {
-                $subtypes[$subtype->link . $sectionlink . $sectionreturnlink] = $subtype->title;
+                $subtypes[$subtype->link . $sectionlink] = $subtype->title;
             }
 
             // Sort module subtypes into the list
@@ -1815,11 +1821,11 @@ function print_section_add_menus($course, $section, $modnames, $vertical=false,
         } else {
             // This module has no subtypes
             if ($module->archetype == MOD_ARCHETYPE_RESOURCE) {
-                $resources[$module->link . $sectionlink . $sectionreturnlink] = $module->title;
+                $resources[$module->link . $sectionlink] = $module->title;
             } else if ($module->archetype === MOD_ARCHETYPE_SYSTEM) {
                 // System modules cannot be added by user, do not add to dropdown
             } else {
-                $activities[$module->link . $sectionlink . $sectionreturnlink] = $module->title;
+                $activities[$module->link . $sectionlink] = $module->title;
             }
         }
     }
@@ -3286,8 +3292,8 @@ function make_editing_buttons(stdClass $mod, $absolute_ignored = true, $movesele
         );
     }
 
-    // Duplicate (require both target import caps to be able to duplicate, see modduplicate.php)
-    if (has_all_capabilities($dupecaps, $coursecontext)) {
+    // Duplicate (require both target import caps to be able to duplicate and backup2 support, see modduplicate.php)
+    if (has_all_capabilities($dupecaps, $coursecontext) && plugin_supports('mod', $mod->modname, FEATURE_BACKUP_MOODLE2)) {
         $actions[] = new action_link(
             new moodle_url($baseurl, array('duplicate' => $mod->id)),
             new pix_icon('t/copy', $str->duplicate, 'moodle', array('class' => 'iconsmall')),
@@ -4033,7 +4039,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 +4072,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 e08ea35..30c9aaf 100644 (file)
@@ -56,6 +56,15 @@ $PAGE->set_pagelayout('incourse');
 
 $output = $PAGE->get_renderer('core', 'backup');
 
+$a          = new stdClass();
+$a->modtype = get_string('modulename', $cm->modname);
+$a->modname = format_string($cm->name);
+
+if (!plugin_supports('mod', $cm->modname, FEATURE_BACKUP_MOODLE2)) {
+    $url = new moodle_url('/course/view.php#section-' . $cm->sectionnum, array('id' => $course->id));
+    print_error('duplicatenosupport', 'core', $url, $a);
+}
+
 // backup the activity
 
 $bc = new backup_controller(backup::TYPE_1ACTIVITY, $cm->id, backup::FORMAT_MOODLE,
@@ -118,10 +127,6 @@ if (empty($CFG->keeptempdirectoriesonbackup)) {
     fulldelete($backupbasepath);
 }
 
-$a          = new stdClass();
-$a->modtype = get_string('modulename', $cm->modname);
-$a->modname = format_string($cm->name);
-
 echo $output->header();
 
 if ($newcmid) {
index cd0a66b..088c203 100644 (file)
@@ -39,7 +39,7 @@ if (!$course = $DB->get_record('course', array('id'=>$id))) {
 $PAGE->set_url('/course/reset.php', array('id'=>$id));
 
 require_login($course);
-require_capability('moodle/course:update', get_context_instance(CONTEXT_COURSE, $course->id));
+require_capability('moodle/course:reset', get_context_instance(CONTEXT_COURSE, $course->id));
 
 $strreset       = get_string('reset');
 $strresetcourse = get_string('resetcourse');
index d720144..19a584b 100644 (file)
@@ -53,7 +53,7 @@ if ($courseid) {
     if ($user && $rolec) {
         require_sesskey();
 
-        completion_criteria::factory((object) array('id'=>$rolec, 'criteriatype'=>COMPLETION_CRITERIA_TYPE_ROLE)); //TODO: this is dumb, because it does not fetch the data?!?!
+        completion_criteria::factory(array('id'=>$rolec, 'criteriatype'=>COMPLETION_CRITERIA_TYPE_ROLE)); //TODO: this is dumb, because it does not fetch the data?!?!
         $criteria = completion_criteria_role::fetch(array('id'=>$rolec));
 
         if ($criteria and user_has_role_assignment($USER->id, $criteria->role, $context->id)) {
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 5c3caa8..14e7da2 100644 (file)
@@ -84,6 +84,34 @@ $PAGE->navbar->add(get_string('enrolmentoptions','enrol'));
 echo $OUTPUT->header();
 echo $OUTPUT->heading(get_string('enrolmentoptions','enrol'));
 
+echo $OUTPUT->box_start('generalbox info');
+
+$summary = file_rewrite_pluginfile_urls($course->summary, 'pluginfile.php', $context->id, 'course', 'summary', null);
+echo format_text($summary, $course->summaryformat, array('overflowdiv'=>true), $course->id);
+if (!empty($CFG->coursecontact)) {
+    $coursecontactroles = explode(',', $CFG->coursecontact);
+    foreach ($coursecontactroles as $roleid) {
+        $role = $DB->get_record('role', array('id'=>$roleid));
+        $roleid = (int) $roleid;
+        if ($users = get_role_users($roleid, $context, true)) {
+            foreach ($users as $teacher) {
+                $fullname = fullname($teacher, has_capability('moodle/site:viewfullnames', $context));
+                $namesarray[] = format_string(role_get_name($role, $context)).': <a href="'.$CFG->wwwroot.'/user/view.php?id='.
+                    $teacher->id.'&amp;course='.SITEID.'">'.$fullname.'</a>';
+            }
+        }
+    }
+
+    if (!empty($namesarray)) {
+        echo "<ul class=\"teachers\">\n<li>";
+        echo implode('</li><li>', $namesarray);
+        echo "</li></ul>";
+    }
+}
+
+echo $OUTPUT->box_end();
+
+
 //TODO: find if future enrolments present and display some info
 
 foreach ($forms as $form) {
index 86adfbf..a189e7d 100644 (file)
@@ -67,7 +67,7 @@ class enrol_self_enrol_form extends moodleform {
         if ($instance->password) {
             //change the id of self enrolment key input as there can be multiple self enrolment methods
             $mform->addElement('passwordunmask', 'enrolpassword', get_string('password', 'enrol_self'),
-                    array('id' => $instance->id."_enrolpassword"));
+                    array('id' => 'enrolpassword_'.$instance->id));
         } else {
             $mform->addElement('static', 'nokey', '', get_string('nopassword', 'enrol_self'));
         }
@@ -127,4 +127,4 @@ class enrol_self_enrol_form extends moodleform {
 
         return $errors;
     }
-}
\ No newline at end of file
+}
index 5a268b4..eee8664 100644 (file)
@@ -389,7 +389,7 @@ class grade_edit_tree {
     //Grader report has its own decimal place settings so they are handled elsewhere
     static function format_number($number) {
         $formatted = rtrim(format_float($number, 4),'0');
-        if (substr($formatted, -1)=='.') { //if last char is the decimal point
+        if (substr($formatted, -1)==get_string('decsep', 'langconfig')) { //if last char is the decimal point
             $formatted .= '0';
         }
         return $formatted;
index 71b38ec..ae97eee 100644 (file)
@@ -556,7 +556,8 @@ class gradingform_rubric_controller extends gradingform_controller {
             return $this->get_instance($instance);
         }
         if ($itemid && $raterid) {
-            if ($rs = $DB->get_records('grading_instances', array('raterid' => $raterid, 'itemid' => $itemid), 'timemodified DESC', '*', 0, 1)) {
+            $params = array('definitionid' => $this->definition->id, 'raterid' => $raterid, 'itemid' => $itemid);
+            if ($rs = $DB->get_records('grading_instances', $params, 'timemodified DESC', '*', 0, 1)) {
                 $record = reset($rs);
                 $currentinstance = $this->get_current_instance($raterid, $itemid);
                 if ($record->status == gradingform_rubric_instance::INSTANCE_STATUS_INCOMPLETE &&
index 940d880..fb62037 100644 (file)
@@ -70,6 +70,11 @@ $string['pathsroparentdataroot'] = 'No es pot escriure en el directori pare ({$a
 $string['pathssubadmindir'] = 'Alguns serveis d\'allotjament web (pocs) utilitzen un URL especial /admin p. ex. per a accedir a un tauler de control o quelcom semblant. Malauradament això entra en conflicte amb la ubicació estàndard de les pàgines d\'administració de Moodle. Podeu arreglar aquest problema canviant el nom del directori d\'administració de Moodle en la vostra instal·lació i posant el nou nom aquí. Per exemple <em>moodleadmin</em>. Això modificarà els enllaços d\'administració de Moodle.';
 $string['pathssubdataroot'] = 'Necessiteu un espai on Moodle pugui desar els fitxers penjats. Aquest directori hauria de tenir permisos de lectura I ESCRIPTURA per a l\'usuari del servidor web (normalment \'nobody\' o \'apache\'), però no cal que sigui accessible directament via web. L\'instal·lador provarà de crear-lo si no existeix.';
 $string['pathssubdirroot'] = 'Camí complet del directori d\'instal·lació de Moodle.';
+$string['pathssubwwwroot'] = 'L\'adreça web completa on s\'accedirà a Moodle.
+No és possible accedir a Moodle en diferents adreces.
+Si el vostre lloc té múltiples adreces públiques haureu de configurar redireccions permanents per a totes excepte aquesta.
+Si el vostre lloc és accessible tant des d\'Internet com des d\'una intranet, utilitzeu aquí l\'adreça pública i configureu el DNS de manera que els usuaris de la intranet puguin utilitzar també l\'adreça pública.
+Si l\'adreça no és correcta, canvieu l\'URL en el vostre navegador per reiniciar la instal·lació amb un altre valor.';
 $string['pathsunsecuredataroot'] = 'La ubicació del dataroot no és segura.';
 $string['pathswrongadmindir'] = 'No existeix el directori d\'administració';
 $string['phpextension'] = 'Extensió PHP {$a}';
index cd9ab38..9cea86c 100644 (file)
@@ -32,4 +32,4 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['parentlanguage'] = '';
 $string['thisdirection'] = 'ltr';
-$string['thislanguage'] = 'Béarla';
+$string['thislanguage'] = 'Gaeilge';
index 1b5f34a..9bae23d 100644 (file)
@@ -32,6 +32,7 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['clianswerno'] = 'n';
 $string['cliansweryes'] = 'j';
+$string['cliincorrectvalueerror'] = 'Villa, ótækt gildi "{$a->value}" fyrir "{$a->option}"';
 $string['cliincorrectvalueretry'] = 'Rangt gildi, vinsamlegast reyndu aftur';
 $string['clitypevalue'] = 'Sláðu inn gildi';
 $string['clitypevaluedefault'] = 'Sláðu inn gildi, sláðu á Enter hnappinn á lyklaborðinu til að nota sjálfgefið gildi ({$a})';
index 4de4bef..bcf067a 100644 (file)
@@ -31,4 +31,4 @@
 defined('MOODLE_INTERNAL') || die();
 
 $string['thisdirection'] = 'ltr';
-$string['thislanguage'] = '&Iacute;slenska';
+$string['thislanguage'] = 'Íslenska';
index 09bd68f..beeead1 100644 (file)
@@ -32,11 +32,13 @@ defined('MOODLE_INTERNAL') || die();
 
 $string['clianswerno'] = 'n';
 $string['cliansweryes'] = 's';
-$string['cliincorrectvalueerror'] = 'Erro: o valor "{$a->value}" não é permitido para a opção "{$a->option}"';
+$string['cliincorrectvalueerror'] = 'Erro. Valor "{$a->value}" incorreto para "{$a->option}"';
 $string['cliincorrectvalueretry'] = 'Valor incorreto, por favor tente novamente';
 $string['clitypevalue'] = 'valor do tipo';
 $string['clitypevaluedefault'] = 'valor do tipo, pressione a tecla Enter para usar o valor predefinido ({$a})';
-$string['cliunknowoption'] = 'Opções desconhecidas: {$a} Por favor use a opção --help';
+$string['cliunknowoption'] = 'Opções desconhecidas:
+{$a}
+Por favor use a opção --help';
 $string['cliyesnoprompt'] = 'digite s (para sim) ou n (para não)';
 $string['environmentrequireinstall'] = 'deve estar instalada e ativa';
 $string['environmentrequireversion'] = 'é requerida a versão {$a->needed} e está a correr a versão {$a->current}';
diff --git a/install/lang/sw/langconfig.php b/install/lang/sw/langconfig.php
new file mode 100644 (file)
index 0000000..2c93731
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle installer
+ *
+ * Do not edit this file manually! It contains just a subset of strings
+ * needed during the very first steps of installation. This file was
+ * generated automatically by export-installer.php (which is part of AMOS
+ * {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
+ * list of strings defined in /install/stringnames.txt.
+ *
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+$string['thisdirection'] = 'ltr';
+$string['thislanguage'] = 'Kiswahili';
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 fbd8c9d..d55667d 100644 (file)
@@ -30,6 +30,7 @@ $string['display'] = 'Display';
 $string['err_alphanumeric'] = 'You must enter only letters or numbers here.';
 $string['err_email'] = 'You must enter a valid email address here.';
 $string['err_lettersonly'] = 'You must enter only letters here.';
+$string['err_maxfiles'] = 'You must not attach more than {$a} files here.';
 $string['err_maxlength'] = 'You must enter not more than {$a->format} characters here.';
 $string['err_minlength'] = 'You must enter at least {$a->format} characters here.';
 $string['err_nonzero'] = 'You must enter a number not starting with a 0 here.';
index af23c90..1aae903 100644 (file)
@@ -487,6 +487,7 @@ $string['duplicate'] = 'Duplicate';
 $string['duplicateconfirm'] = 'Are you sure you want to duplicate {$a->modtype} \'{$a->modname}\' ?';
 $string['duplicatecontcourse'] = 'Return to the course';
 $string['duplicatecontedit'] = 'Edit the new copy';
+$string['duplicatenosupport'] = '\'{$a->modname}\' activity could not be duplicated because the {$a->modtype} module does not support backup and restore.';
 $string['duplicatesuccess'] = '{$a->modtype} \'{$a->modname}\' has been duplicated successfully';
 $string['duplicatinga'] = 'Duplicating: {$a}';
 $string['edhelpaspellpath'] = 'To use spell-checking within the editor, you MUST have <strong>aspell 0.50</strong> or later installed on your server, and you must specify the correct path to access the aspell binary.  On Unix/Linux systems, this path is usually <strong>/usr/bin/aspell</strong>, but it might be something else.';
index d0cf1a4..dfa1aeb 100644 (file)
@@ -230,6 +230,8 @@ $string['novirtualquestiontype'] = 'No virtual question type for question type {
 $string['numqas'] = 'No. question attempts';
 $string['numquestions'] = 'No. questions';
 $string['numquestionsandhidden'] = '{$a->numquestions} (+{$a->numhidden} hidden)';
+$string['orphanedquestionscategory'] = 'Questions saved from deleted categories';
+$string['orphanedquestionscategoryinfo'] = 'Occasionally, typically due to old software bugs, questions can remain in the database even though the corresponding question category has been deleted. Of course, this should not happen, it has happened in the past on this site. This category has been created automatically, and the orphaned questions moved here so that you can manage them. Note that any images or media files used by these questions have probably been lost.';
 $string['page-question-x'] = 'Any question page';
 $string['page-question-edit'] = 'Question editing page';
 $string['page-question-category'] = 'Question category page';
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 7f44800..dabe552 100644 (file)
@@ -153,7 +153,7 @@ class completion_criteria_completion extends data_object {
 
             $record = $DB->get_record('course_completion_criteria', $params);
 
-            $this->attach_criteria(completion_criteria::factory($record));
+            $this->attach_criteria(completion_criteria::factory((array) $record));
         }
 
         return $this->_criteria;
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 75e165f..9dd53d2 100644 (file)
@@ -914,6 +914,14 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012062501.01);
     }
 
+    if ($oldversion < 2012062501.04) {
+
+        // Saves orphaned questions from the Dark Side
+        upgrade_save_orphaned_questions();
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012062501.04);
+    }
 
     return true;
 }
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..1a238ba 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()
@@ -300,6 +380,7 @@ class mysqli_native_moodle_database extends moodle_database {
         $errorno = @$this->mysqli->connect_errno;
 
         if ($errorno !== 0) {
+            $this->mysqli = null;
             throw new dml_connection_exception($dberr);
         }
 
index bac7394..7051c82 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * General database export class
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -41,16 +38,17 @@ defined('MOODLE_INTERNAL') || die();
  * @see export_table_data for the same table.
  */
 abstract class database_exporter {
-    /** Connection to the source database (a @see moodle_database object). */
+    /** @var moodle_database Connection to the source database (a @see moodle_database object). */
     protected $mdb;
-    /** Database manager of the source database (a @see database_manager object). */
+    /** @var database_manager Database manager of the source database (a @see database_manager object). */
     protected $manager;
-    /** Source database schema in XMLDB format (a @see xmldb_structure object). */
+    /** @var xmldb_structure Source database schema in XMLDB format (a @see xmldb_structure object). */
     protected $schema;
     /**
      * Boolean flag - whether or not to check that XML database schema matches
      * the RDBMS database schema before exporting (used by
      * @see export_database).
+     * @var bool
      */
     protected $check_schema;
 
@@ -158,5 +156,4 @@ abstract class database_exporter {
         }
         $this->finish_database_export();
     }
-
 }
index b7f6a2d..92b9ff2 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * General database importer class
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -43,23 +40,22 @@ defined('MOODLE_INTERNAL') || die();
  * is respected.
  */
 class database_importer {
-    /** Connection to the target database (a @see moodle_database object). */
+    /** @var moodle_database Connection to the target database (a @see moodle_database object). */
     protected $mdb;
-    /** Database manager of the target database (a @see database_manager object). */
+    /** @var database_manager Database manager of the target database (a @see database_manager object). */
     protected $manager;
-    /** Target database schema in XMLDB format (a @see xmldb_structure object). */
+    /** @var xmldb_structure Target database schema in XMLDB format (a @see xmldb_structure object). */
     protected $schema;
     /**
      * Boolean flag - whether or not to check that XML database schema matches
      * the RDBMS database schema before importing (used by
      * @see begin_database_import).
+     * @var bool
      */
     protected $check_schema;
-    /**
-     * How to use transactions.
-     */
+    /** @var string How to use transactions. */
     protected $transactionmode = 'allinone';
-    /** Transaction object */
+    /** @var moodle_transaction Transaction object */
     protected $transaction;
 
     /**
@@ -105,7 +101,7 @@ class database_importer {
         global $CFG;
 
         if (!$this->mdb->get_tables()) {
-            // not tables yet, time to create all tables
+            // No tables present yet, time to create all tables.
             $this->manager->install_from_xmldb_structure($this->schema);
         }
 
index d61d416..419080e 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * General database mover class
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 defined('MOODLE_INTERNAL') || die();
 
 class database_mover extends database_exporter {
-    /** Importer object used to transfer data. */
+    /** @var database_importer Importer object used to transfer data. */
     protected $importer;
-    protected $feeback;
+    /** @var progress_trace Progress tracing object */
+    protected $feedback;
 
     /**
      * Object constructor.
      *
-     * @param moodle_database $mdb Connection to the source database (a
+     * @param moodle_database $mdb_source Connection to the source database (a
      * @see moodle_database object).
      * @param moodle_database $mdb_target Connection to the target database (a
      * @see moodle_database object).
      * @param boolean $check_schema - whether or not to check that XML database
      * schema matches the RDBMS database schema before exporting (used by
+     * @param progress_trace $feedback Progress tracing object
      * @see export_database).
      */
     public function __construct(moodle_database $mdb_source, moodle_database $mdb_target,
-            $check_schema = true, progress_trace $feeback = null) {
-        if (empty($feeback)) {
-            $this->feeback = new null_progress_trace();
+            $check_schema = true, progress_trace $feedback = null) {
+        if (empty($feedback)) {
+            $this->feedback = new null_progress_trace();
         } else {
-            $this->feeback = $feeback;
+            $this->feedback = $feedback;
         }
         if ($check_schema) {
-            $this->feeback->output(get_string('checkingsourcetables', 'core_dbtransfer'));
+            $this->feedback->output(get_string('checkingsourcetables', 'core_dbtransfer'));
         }
         parent::__construct($mdb_source, $check_schema);
-        $this->feeback->output(get_string('creatingtargettables', 'core_dbtransfer'));
+        $this->feedback->output(get_string('creatingtargettables', 'core_dbtransfer'));
         $this->importer = new database_importer($mdb_target, $check_schema);
     }
 
@@ -70,12 +69,13 @@ class database_mover extends database_exporter {
      * Callback function. Calls importer's begin_database_import callback method.
      *
      * @param float $version the version of the system which generating the data
+     * @param string $release moodle release info
      * @param string $timestamp the timestamp of the data (in ISO 8601) format.
      * @param string $description a user description of the data.
      * @return void
      */
     public function begin_database_export($version, $release, $timestamp, $description) {
-        $this->feeback->output(get_string('copyingtables', 'core_dbtransfer'));
+        $this->feedback->output(get_string('copyingtables', 'core_dbtransfer'));
         $this->importer->begin_database_import($version, $timestamp, $description);
     }
 
@@ -86,7 +86,7 @@ class database_mover extends database_exporter {
      * @return void
      */
     public function begin_table_export(xmldb_table $table) {
-        $this->feeback->output(get_string('copyingtable', 'core_dbtransfer', $table->getName()), 1);
+        $this->feedback->output(get_string('copyingtable', 'core_dbtransfer', $table->getName()), 1);
         $this->importer->begin_table_import($table->getName(), $table->getHash());
     }
 
@@ -108,7 +108,7 @@ class database_mover extends database_exporter {
      * @return void
      */
     public function finish_table_export(xmldb_table $table) {
-        $this->feeback->output(get_string('done', 'core_dbtransfer', $table->getName()), 2);
+        $this->feedback->output(get_string('done', 'core_dbtransfer', $table->getName()), 2);
         $this->importer->finish_table_import($table->getName());
     }
 
index 9f93cb3..498609f 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format exporter class to file storage
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -31,9 +28,9 @@ defined('MOODLE_INTERNAL') || die();
  * XML format exporter class to file storage.
  */
 class file_xml_database_exporter extends xml_database_exporter {
-    /** Path to the XML data file. */
+    /** @var string Path to the XML data file. */
     protected $filepath;
-    /** File descriptor for the output file. */
+    /** @var resource File descriptor for the output file. */
     protected $file;
 
     /**
@@ -53,6 +50,7 @@ class file_xml_database_exporter extends xml_database_exporter {
 
     /**
      * Specific output method for the file XML sink.
+     * @param string $text
      */
     protected function output($text) {
         fwrite($this->file, $text);
index a4362f3..cf34f3a 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format importer class from file storage
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -31,7 +28,7 @@ defined('MOODLE_INTERNAL') || die();
  * XML format importer class from file storage.
  */
 class file_xml_database_importer extends xml_database_importer {
-    /** Path to the XML data file. */
+    /** @var string Path to the XML data file. */
     protected $filepath;
 
     /**
index 85ce030..cc2dc58 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format exporter class to memory storage
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -31,11 +28,12 @@ defined('MOODLE_INTERNAL') || die();
  * XML format exporter class to memory storage (i.e. a string).
  */
 class string_xml_database_exporter extends xml_database_exporter {
-    /** String with XML data. */
+    /** @var string String with XML data. */
     protected $data;
 
     /**
      * Specific output method for the memory XML sink.
+     * @param string $text
      */
     protected function output($text) {
         $this->data .= $text;
index fc80593..94095e9 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format importer class from memory storage
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -31,13 +28,13 @@ defined('MOODLE_INTERNAL') || die();
  * XML format importer class from memory storage (i.e. string).
  */
 class string_xml_database_importer extends xml_database_importer {
-    /** String with XML data. */
+    /** @var string String with XML data. */
     protected $data;
 
     /**
      * Object constructor.
      *
-     * @param string data - string with XML data
+     * @param string $data - string with XML data
      * @param moodle_database $mdb Connection to the target database
      * @see xml_database_importer::__construct()
      * @param boolean $check_schema - whether or not to check that XML database
index ae76a7a..374a735 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format exporter class
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -51,7 +48,7 @@ abstract class xml_database_exporter extends database_exporter {
     public function begin_database_export($version, $release, $timestamp, $description) {
         $this->output('<?xml version="1.0" encoding="utf-8"?>');
         //TODO add xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" and schema information
-        $this->output('<moodle_database version="'.$version.'" release="'.$release.'" timestamp="'.$timestamp.'"'.(empty ($description) ? '' : ' comment="'.htmlspecialchars($description, ENT_QUOTES).'"').'>');
+        $this->output('<moodle_database version="'.$version.'" release="'.$release.'" timestamp="'.$timestamp.'"'.(empty ($description) ? '' : ' comment="'.htmlspecialchars($description, ENT_QUOTES, 'UTF-8').'"').'>');
     }
 
     /**
@@ -93,7 +90,7 @@ abstract class xml_database_exporter extends database_exporter {
             if (is_null($value)) {
                 $this->output('<field name="'.$key.'" value="null" />');
             } else {
-                $this->output('<field name="'.$key.'">'.htmlspecialchars($value, ENT_NOQUOTES).'</field>');
+                $this->output('<field name="'.$key.'">'.htmlspecialchars($value, ENT_NOQUOTES, 'UTF-8').'</field>');
             }
         }
         $this->output('</record>');
index 24ca87a..da522dd 100644 (file)
@@ -1,5 +1,4 @@
 <?php
-
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // You should have received a copy of the GNU General Public License
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
-
 /**
  * XML format importer class
  *
- * @package    core
- * @subpackage dtl
+ * @package    core_dtl
  * @copyright  2008 Andrei Bautu
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
@@ -150,9 +147,9 @@ abstract class xml_database_importer extends database_importer {
      * @param string $data character data to be processed
      * @return void
      */
-    protected function cdata($parser, $cdata) {
+    protected function cdata($parser, $data) {
         if (isset($this->current_field)) {
-            $this->current_data .= $cdata;
+            $this->current_data .= $data;
         }
     }
 }
index 10753a9..9e53fc7 100644 (file)
@@ -155,7 +155,7 @@ class tinymce_texteditor extends texteditor {
                     'theme_advanced_resizing_min_height' => 30,
                     'theme_advanced_toolbar_location' => "top",
                     'theme_advanced_statusbar_location' => "bottom",
-                    'spellchecker_rpc_url' => $CFG->wwwroot."/lib/editor/tinymce/tiny_mce/$this->version/plugins/spellchecker/rpc.php",
+                    'spellchecker_rpc_url' => $CFG->httpswwwroot."/lib/editor/tinymce/tiny_mce/$this->version/plugins/spellchecker/rpc.php",
                     'spellchecker_languages' => $spelllanguagelist
                   );
 
index 53018b9..bbadd5c 100644 (file)
@@ -165,11 +165,22 @@ M.form_dndupload.init = function(Y, options) {
             this.Y.on('dragleave', this.drag_leave_page, 'body', this);
         },
 
+        /**
+         * Check if the filemanager / filepicker is disabled
+         * @return bool - true if disabled
+         */
+        is_disabled: function() {
+            return (this.container.ancestor('.fitem.disabled') != null);
+        },
+
         /**
          * Show the 'drop files here' message when file(s) are dragged
          * onto the page
          */
         drag_enter_page: function(e) {
+            if (this.is_disabled()) {
+                return false;
+            }
             if (!this.has_files(e)) {
                 return false;
             }
@@ -210,6 +221,9 @@ M.form_dndupload.init = function(Y, options) {
          * @return boolean true if a valid file drag event
          */
         check_drag: function(e) {
+            if (this.is_disabled()) {
+                return false;
+            }
             if (!this.has_files(e)) {
                 return false;
             }
index 164c683..cbd528a 100644 (file)
@@ -255,6 +255,9 @@ M.form_filemanager.init = function(Y, options) {
             this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
             this.msg_dlg.show();
         },
+        is_disabled: function() {
+            return this.filemanager.ancestor('.fitem.disabled') != null;
+        },
         setup_buttons: function() {
             var button_download = this.filemanager.one('.fp-btn-download');
             var button_create   = this.filemanager.one('.fp-btn-mkdir');
@@ -272,6 +275,9 @@ M.form_filemanager.init = function(Y, options) {
             if (this.options.subdirs) {
                 button_create.on('click',function(e) {
                     e.preventDefault();
+                    if (this.is_disabled()) {
+                        return;
+                    }
                     var scope = this;
                     // a function used to perform an ajax request
                     var perform_action = function(e) {
@@ -325,6 +331,9 @@ M.form_filemanager.init = function(Y, options) {
             // setup 'download this folder' button
             button_download.on('click',function(e) {
                 e.preventDefault();
+                if (this.is_disabled()) {
+                    return;
+                }
                 var scope = this;
                 // perform downloaddir ajax request
                 this.request({
@@ -351,7 +360,7 @@ M.form_filemanager.init = function(Y, options) {
                 on('click', function(e) {
                     e.preventDefault();
                     var viewbar = this.filemanager.one('.fp-viewbar')
-                    if (!viewbar || !viewbar.hasClass('disabled')) {
+                    if (!this.is_disabled() && (!viewbar || !viewbar.hasClass('disabled'))) {
                         this.filemanager.all('.fp-vb-icons,.fp-vb-tree,.fp-vb-details').removeClass('checked')
                         if (e.currentTarget.hasClass('fp-vb-tree')) {
                             this.viewmode = 2;
@@ -369,6 +378,9 @@ M.form_filemanager.init = function(Y, options) {
         show_filepicker: function (e) {
             // if maxfiles == -1, the no limit
             e.preventDefault();
+            if (this.is_disabled()) {
+                return;
+            }
             var options = this.filepicker_options;
             options.formcallback = this.filepicker_callback;
             // XXX: magic here, to let filepicker use filemanager scope
@@ -400,7 +412,9 @@ M.form_filemanager.init = function(Y, options) {
                     el.one('.fp-path-folder-name').setContent(p[i].name).
                         on('click', function(e, path) {
                             e.preventDefault();
-                            this.refresh(path);
+                            if (!this.is_disabled()) {
+                                this.refresh(path);
+                            }
                         }, this, p[i].path);
                 }
                 this.pathbar.removeClass('empty');
@@ -873,6 +887,9 @@ M.form_filemanager.init = function(Y, options) {
             return node.filepath;
         },
         select_file: function(node) {
+            if (this.is_disabled()) {
+                return;
+            }
             var selectnode = this.selectnode;
             selectnode.removeClass('loading').removeClass('fp-folder').
                 removeClass('fp-file').removeClass('fp-zip').removeClass('fp-cansetmain');
index e372a23..b2811a2 100644 (file)
@@ -32,7 +32,9 @@ M.form_filepicker.init = function(Y, options) {
     }
     Y.on('click', function(e, client_id) {
         e.preventDefault();
-        M.core_filepicker.instances[client_id].show();
+        if (this.ancestor('.fitem.disabled') == null) {
+            M.core_filepicker.instances[client_id].show();
+        }
     }, '#filepicker-button-'+options.client_id, null, options.client_id);
 
     var item = document.getElementById('nonjs-filepicker-'+options.client_id);
index 938fab6..b49d76a 100644 (file)
@@ -207,16 +207,14 @@ M.form.initFormDependencies = function(Y, formid, dependencies) {
                         this.removeAttribute('disabled');
                     }
 
-                    // Extra code to disable a filepicker
-                    if (this.getAttribute('class') == 'filepickerhidden'){
-                        var pickerbuttons = form.elementsByName(name + 'choose');
-                        pickerbuttons.each(function(){
-                            if (disabled){
-                                this.setAttribute('disabled','disabled');
-                            } else {
-                                this.removeAttribute('disabled');
-                            }
-                        });
+                    // Extra code to disable filepicker or filemanager form elements
+                    var fitem = this.ancestor('.fitem');
+                    if (fitem && (fitem.hasClass('fitem_ffilemanager') || fitem.hasClass('fitem_ffilepicker'))) {
+                        if (disabled){
+                            fitem.addClass('disabled');
+                        } else {
+                            fitem.removeClass('disabled');
+                        }
                     }
                 })
             },
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..77b960e 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;
     }
@@ -385,6 +406,22 @@ abstract class moodleform {
                 }
             }
         }
+        // Check all the filemanager elements to make sure they do not have too many
+        // files in them.
+        foreach ($mform->_elements as $element) {
+            if ($element->_type == 'filemanager') {
+                $maxfiles = $element->getMaxfiles();
+                if ($maxfiles > 0) {
+                    $draftid = (int)$element->getValue();
+                    $fs = get_file_storage();
+                    $context = context_user::instance($USER->id);
+                    $files = $fs->get_area_files($context->id, 'user', 'draft', $draftid, '', false);
+                    if (count($files) > $maxfiles) {
+                        $errors[$element->getName()] = get_string('err_maxfiles', 'form', $maxfiles);
+                    }
+                }
+            }
+        }
         if (empty($errors)) {
             return true;
         } else {
index ca67450..95b99f8 100644 (file)
@@ -878,22 +878,64 @@ class grade_category extends grade_object {
             asort($grade_values, SORT_NUMERIC);
             $dropped = 0;
 
-            foreach ($grade_values as $itemid=>$value) {
+            // If we have fewer grade items available to drop than $this->droplow, use this flag to escape the loop
+            // May occur because of "extra credit" or if droplow is higher than the number of grade items
+            $droppedsomething = true;
 
-                if ($dropped < $this->droplow) {
+            while ($dropped < $this->droplow && $droppedsomething) {
+                $droppedsomething = false;
 
-                    if ($extraused and $items[$itemid]->aggregationcoef > 0) {
-                        // no drop low for extra credits
+                $grade_keys = array_keys($grade_values);
+                if (count($grade_keys) === 0) {
+                    //We've dropped all grade items
+                    break;
+                }
 
-                    } else {
-                        unset($grade_values[$itemid]);
-                        $dropped++;
+                $originalindex = $founditemid = $foundmax = null;
+
+                // Find the first remaining grade item that is available to be dropped
+                foreach ($grade_keys as $gradekeyindex=>$gradekey) {
+                    if (!$extraused || $items[$gradekey]->aggregationcoef <= 0) {
+                        // Found a non-extra credit grade item that is eligible to be dropped
+                        $originalindex = $gradekeyindex;
+                        $founditemid = $grade_keys[$originalindex];
+                        $foundmax = $items[$founditemid]->grademax;
+                        break;
                     }
+                }
 
-                } else {
-                    // we have dropped enough
+                if (empty($founditemid)) {
+                    // No grade items available to drop
                     break;
                 }
+
+                $i = 1;
+                while ($originalindex+$i < count($grade_keys)) {
+                    $possibleitemid = $grade_keys[$originalindex+$i];
+                    if ($grade_values[$founditemid] != $grade_values[$possibleitemid]) {
+                        // The next grade item has a different grade. Stop looking.
+                        break;
+                    }
+
+                    if ($extraused && $items[$possibleitemid]->aggregationcoef > 0) {
+                        // Don't drop extra credit grade items. Continue the search.
+                        continue;
+                    }
+
+                    if ($foundmax < $items[$possibleitemid]->grademax) {
+                        // Found a grade item with the same grade and a higher grademax
+                        $foundmax = $items[$possibleitemid]->grademax;
+                        $founditemid = $possibleitemid;
+                        // Continue searching to see if there is an even higher grademax...
+                    }
+
+                    $i++;
+                }
+
+                // Now drop whatever grade item we have found
+                unset($grade_values[$founditemid]);
+                $dropped++;
+                $droppedsomething = true;
             }
 
         } else if (!empty($this->keephigh)) {
index 102ef86..727e698 100644 (file)
@@ -406,6 +406,7 @@ class grade_category_testcase extends grade_base_testcase {
         $items[$this->grade_items[2]->id] = new grade_item($this->grade_items[2], false);
         $items[$this->grade_items[4]->id] = new grade_item($this->grade_items[4], false);
 
+        // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades
         $category = new grade_category();
         $category->droplow = 2;
         $grades = array($this->grade_items[0]->id=>5.374,
@@ -417,6 +418,7 @@ class grade_category_testcase extends grade_base_testcase {
         $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
         $this->assertEquals($grades[$this->grade_items[4]->id], 7.3754);
 
+        // Test aggregating only the highest 1 out of 4 grades
         $category = new grade_category();
         $category->keephigh = 1;
         $category->droplow = 0;
@@ -429,25 +431,28 @@ class grade_category_testcase extends grade_base_testcase {
         $grade = reset($grades);
         $this->assertEquals(9.4743, $grade);
 
+        // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades
+        // An extra credit grade item should be kept even if droplow means it would otherwise be excluded
         $category = new grade_category();
         $category->droplow     = 2;
         $category->aggregation = GRADE_AGGREGATE_SUM;
-        $items[$this->grade_items[2]->id]->aggregationcoef = 1;
+        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit"
         $grades = array($this->grade_items[0]->id=>5.374,
                         $this->grade_items[1]->id=>9.4743,
                         $this->grade_items[2]->id=>2.5474,
                         $this->grade_items[4]->id=>7.3754);
-
         $category->apply_limit_rules($grades, $items);
         $this->assertEquals(count($grades), 2);
         $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
         $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
 
+        // Test only aggregating the highest 1 out of 4 grades
+        // An extra credit grade item is retained in addition to the highest grade
         $category = new grade_category();
         $category->keephigh = 1;
         $category->droplow = 0;
         $category->aggregation = GRADE_AGGREGATE_SUM;
-        $items[$this->grade_items[2]->id]->aggregationcoef = 1;
+        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit"
         $grades = array($this->grade_items[0]->id=>5.374,
                         $this->grade_items[1]->id=>9.4743,
                         $this->grade_items[2]->id=>2.5474,
@@ -456,6 +461,61 @@ class grade_category_testcase extends grade_base_testcase {
         $this->assertEquals(count($grades), 2);
         $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743);
         $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474);
+
+
+        // Test excluding the lowest 1 out of 4 grades from aggregation with two 0 grades
+        $items[$this->grade_items[2]->id]->aggregationcoef = 0; // Undo marking grade item 2 as "extra credit"
+        $category = new grade_category();
+        $category->droplow     = 1;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>5, // 5 out of 100
+                        $this->grade_items[2]->id=>2, // 0 out of 6
+                        $this->grade_items[4]->id=>0); // 0 out of 100
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 3);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 5);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 2);
+        $this->assertEquals($grades[$this->grade_items[4]->id], 0);
+
+        // Test excluding the lowest 2 out of 4 grades from aggregation with three 0 grades
+        $category = new grade_category();
+        $category->droplow     = 2;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>5, // 5 out of 100
+                        $this->grade_items[2]->id=>0, // 0 out of 6
+                        $this->grade_items[4]->id=>0); // 0 out of 100. Should be excluded from aggregation.
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 2);
+        $this->assertEquals($grades[$this->grade_items[1]->id], 5);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 0);
+
+        // Test excluding the lowest 5 out of 4 grades from aggregation
+        // Just to check we handle this sensibly
+        $category = new grade_category();
+        $category->droplow     = 5;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>5, // 5 out of 100
+                        $this->grade_items[2]->id=>6, // 6 out of 6
+                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 0);
+
+        // Test excluding the lowest 4 out of 4 grades from aggregation with one marked as extra credit
+        $category = new grade_category();
+        $category->droplow     = 4;
+        $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // simple weighted mean
+        $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit"
+        $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation.
+                        $this->grade_items[1]->id=>5, // 5 out of 100. Should be excluded from aggregation.
+                        $this->grade_items[2]->id=>6, // 6 out of 6. Extra credit. Should be retained.
+                        $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation.
+        $category->apply_limit_rules($grades, $items);
+        $this->assertEquals(count($grades), 1);
+        $this->assertEquals($grades[$this->grade_items[2]->id], 6);
+
     }
 
     /**
index 6bf2e28..463f824 100644 (file)
@@ -716,7 +716,13 @@ M.util.get_string = function(identifier, component, a) {
         // creating new instance if YUI is not optimal but it seems to be better way then
         // require the instance via the function API - note that it is used in rare cases
         // for debugging only anyway
-        var Y = new YUI({ debug : true });
+        // To ensure we don't kill browser performance if hundreds of get_string requests
+        // are made we cache the instance we generate within the M.util namespace.
+        // We don't publicly define the variable so that it doesn't get abused.
+        if (typeof M.util.get_string_yui_instance === 'undefined') {
+            M.util.get_string_yui_instance = new YUI({ debug : true });
+        }
+        var Y = M.util.get_string_yui_instance;
     }
 
     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
@@ -1771,14 +1777,27 @@ M.util.load_flowplayer = function() {
 
             var rule;
             for (var j=0; j < document.styleSheets.length; j++) {
-                if (typeof (document.styleSheets[j].rules) != 'undefined') {
-                    var allrules = document.styleSheets[j].rules;
-                } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
-                    var allrules = document.styleSheets[j].cssRules;
-                } else {
-                    // why??
+
+                // To avoid javascript security violation accessing cross domain stylesheets
+                var allrules = false;
+                try {
+                    if (typeof (document.styleSheets[j].rules) != 'undefined') {
+                        allrules = document.styleSheets[j].rules;
+                    } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
+                        allrules = document.styleSheets[j].cssRules;
+                    } else {
+                        // why??
+                        continue;
+                    }
+                } catch (e) {
                     continue;
                 }
+
+                // On cross domain style sheets Chrome V8 allows access to rules but returns null
+                if (!allrules) {
+                    continue;
+                }
+
                 for(var i=0; i<allrules.length; i++) {
                     rule = '';
                     if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
index b5b82c8..6591795 100644 (file)
@@ -661,7 +661,8 @@ function optional_param_array($parname, $default, $type) {
  * @param string $type PARAM_ constant
  * @param bool $allownull are nulls valid value?
  * @param string $debuginfo optional debug information
- * @return mixed the $param value converted to PHP type or invalid_parameter_exception
+ * @return mixed the $param value converted to PHP type
+ * @throws invalid_parameter_exception if $param is not of given type
  */
 function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='') {
     if (is_null($param)) {
@@ -676,7 +677,15 @@ function validate_param($param, $type, $allownull=NULL_NOT_ALLOWED, $debuginfo='
     }
 
     $cleaned = clean_param($param, $type);
-    if ((string)$param !== (string)$cleaned) {
+
+    if ($type == PARAM_FLOAT) {
+        // Do not detect precision loss here.
+        if (is_float($param) or is_int($param)) {
+            // These always fit.
+        } else if (!is_numeric($param) or !preg_match('/^[\+-]?[0-9]*\.?[0-9]*(e[-+]?[0-9]+)?$/i', (string)$param)) {
+            throw new invalid_parameter_exception($debuginfo);
+        }
+    } else if ((string)$param !== (string)$cleaned) {
         // conversion to string is usually lossless
         throw new invalid_parameter_exception($debuginfo);
     }
index b2d0e49..e800b8f 100644 (file)
@@ -2050,7 +2050,7 @@ $icon_progress
 </div>
 <div id="filepicker-wrapper-{$client_id}" class="mdl-left" style="display:none">
     <div>
-        <input type="button" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
+        <input type="button" class="fp-btn-choose" id="filepicker-button-{$client_id}" value="{$straddfile}"{$buttonname}/>
         <span> $maxsize </span>
     </div>
 EOD;
index ddea039..36172c7 100644 (file)
@@ -186,7 +186,7 @@ class moodle_page {
      * @var array List of theme layout options, these are ignored by core.
      * To be used in individual theme layout files only.
      */
-    protected $_layout_options = array();
+    protected $_layout_options = null;
 
     /**
      * @var string An optional arbitrary parameter that can be set on pages where the context
@@ -492,10 +492,13 @@ class moodle_page {
     }
 
     /**
-     * Please do not call this method directly, use the ->layout_tions syntax. {@link moodle_page::__get()}.
-     * @return array returns arrys with options for layout file
+     * Please do not call this method directly, use the ->layout_options syntax. {@link moodle_page::__get()}.
+     * @return array returns arrays with options for layout file
      */
     protected function magic_get_layout_options() {
+        if (!is_array($this->_layout_options)) {
+            $this->_layout_options = $this->_theme->pagelayout_options($this->pagelayout);
+        }
         return $this->_layout_options;
     }
 
@@ -1433,7 +1436,6 @@ class moodle_page {
         if (is_null($this->_theme)) {
             $themename = $this->resolve_theme();
             $this->_theme = theme_config::load($themename);
-            $this->_layout_options = $this->_theme->pagelayout_options($this->pagelayout);
         }
 
         $this->_theme->setup_blocks($this->pagelayout, $this->blocks);
index 93aecf7..3380549 100644 (file)
@@ -925,7 +925,7 @@ class phpunit_util {
         global $CFG;
 
         $template = '
-        <testsuite name="@component@">
+        <testsuite name="@component@ test suite">
             <directory suffix="_test.php">@dir@</directory>
         </testsuite>';
         $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
index 5ce3a2c..2d03ffc 100644 (file)
@@ -912,6 +912,56 @@ class moodlelib_testcase extends advanced_testcase {
         } catch (invalid_parameter_exception $ex) {
             $this->assertTrue(true);
         }
+        try {
+            $param = validate_param('1.0', PARAM_FLOAT);
+            $this->assertSame(1.0, $param);
+
+            // Make sure valid floats do not cause exception.
+            validate_param(1.0, PARAM_FLOAT);
+            validate_param(10, PARAM_FLOAT);
+            validate_param('0', PARAM_FLOAT);
+            validate_param('119813454.545464564564546564545646556564465465456465465465645645465645645645', PARAM_FLOAT);
+            validate_param('011.1', PARAM_FLOAT);
+            validate_param('11', PARAM_FLOAT);
+            validate_param('+.1', PARAM_FLOAT);
+            validate_param('-.1', PARAM_FLOAT);
+            validate_param('1e10', PARAM_FLOAT);
+            validate_param('.1e+10', PARAM_FLOAT);
+            validate_param('1E-1', PARAM_FLOAT);
+            $this->assertTrue(true);
+        } catch (invalid_parameter_exception $ex) {
+            $this->fail('Valid float notation not accepted');
+        }
+        try {
+            $param = validate_param('1,2', PARAM_FLOAT);
+            $this->fail('invalid_parameter_exception expected');
+        } catch (invalid_parameter_exception $ex) {
+            $this->assertTrue(true);
+        }
+        try {
+            $param = validate_param('', PARAM_FLOAT);
+            $this->fail('invalid_parameter_exception expected');
+        } catch (invalid_parameter_exception $ex) {
+            $this->assertTrue(true);
+        }
+        try {
+            $param = validate_param('.', PARAM_FLOAT);
+            $this->fail('invalid_parameter_exception expected');
+        } catch (invalid_parameter_exception $ex) {
+            $this->assertTrue(true);
+        }
+        try {
+            $param = validate_param('e10', PARAM_FLOAT);
+            $this->fail('invalid_parameter_exception expected');
+        } catch (invalid_parameter_exception $ex) {
+            $this->assertTrue(true);
+        }
+        try {
+            $param = validate_param('abc', PARAM_FLOAT);
+            $this->fail('invalid_parameter_exception expected');
+        } catch (invalid_parameter_exception $ex) {
+            $this->assertTrue(true);
+        }
     }
 
     function test_shorten_text() {
index aa6ee8f..c299ffe 100644 (file)
@@ -1839,3 +1839,44 @@ function upgrade_course_completion_remove_duplicates($table, $uniques, $fieldsto
         }
     }
 }
+
+/**
+ * Find questions missing an existing category and associate them with
+ * a category which purpose is to gather them.
+ *
+ * @return void
+ */
+function upgrade_save_orphaned_questions() {
+    global $DB;
+
+    // Looking for orphaned questions
+    $orphans = $DB->record_exists_select('question',
+            'NOT EXISTS (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)');
+    if (!$orphans) {
+        return;
+    }
+
+    // Generate a unique stamp for the orphaned questions category, easier to identify it later on
+    $uniquestamp = "unknownhost+120719170400+orphan";
+    $systemcontext = context_system::instance();
+
+    // Create the orphaned category at system level
+    $cat = $DB->get_record('question_categories', array('stamp' => $uniquestamp,
+            'contextid' => $systemcontext->id));
+    if (!$cat) {
+        $cat = new stdClass();
+        $cat->parent = 0;
+        $cat->contextid = $systemcontext->id;
+        $cat->name = get_string('orphanedquestionscategory', 'question');
+        $cat->info = get_string('orphanedquestionscategoryinfo', 'question');
+        $cat->sortorder = 999;
+        $cat->stamp = $uniquestamp;
+        $cat->id = $DB->insert_record("question_categories", $cat);
+    }
+
+    // Set a category to those orphans
+    $params = array('catid' => $cat->id);
+    $DB->execute('UPDATE {question} SET category = :catid WHERE NOT EXISTS
+            (SELECT 1 FROM {question_categories} WHERE {question_categories}.id = {question}.category)', $params);
+}
+
index ce919d9..7c69407 100644 (file)
@@ -493,9 +493,10 @@ class xmldb_field extends xmldb_object {
         if (!$this->loaded) {
             $this->hash = null;
         } else {
+            $defaulthash = is_null($this->default) ? '' : sha1($this->default);
             $key = $this->name . $this->type . $this->length .
                    $this->notnull . $this->sequence .
-                   $this->decimals . $this->comment;
+                   $this->decimals . $this->comment . $defaulthash;
             $this->hash = md5($key);
         }
     }
index dc819aa..ea5164d 100644 (file)
@@ -43,6 +43,10 @@ YUI.add('moodle-core-formchangechecker',
                  * get_form_dirty_state function later.
                  */
                 store_initial_value : function(e) {
+                    if (e.target.hasClass('ignoredirty')) {
+                        // Don't warn on elements with the ignoredirty class
+                        return;
+                    }
                     if (M.core_formchangechecker.get_form_dirty_state()) {
                         // Clear the store_initial_value listeners as the form is already dirty so
                         // we no longer need to call this function
@@ -88,7 +92,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 && e.target && 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 a6b2eb1..43c1e7d 100644 (file)
@@ -269,7 +269,7 @@ class assign_plugin_manager {
 
         if ($confirm) {
             // Delete any configuration records.
-            if (!unset_all_config_for_plugin('assignsubmission_' . $plugin)) {
+            if (!unset_all_config_for_plugin($this->subtype . '_' . $plugin)) {
                 $this->error = $OUTPUT->notification(get_string('errordeletingconfig', 'admin', $this->subtype . '_' . $plugin));
             }
 
@@ -282,7 +282,8 @@ class assign_plugin_manager {
             $DB->delete_records('assign_plugin_config', array('plugin'=>$plugin, 'subtype'=>$this->subtype));
 
             // Then the tables themselves
-            drop_plugin_tables($this->subtype . '_' . $plugin, $CFG->dirroot . '/mod/assign/' . $this->subtype . '/' .$plugin. '/db/install.xml', false);
+            $shortsubtype = substr($this->subtype, strlen('assign'));
+            drop_plugin_tables($this->subtype . '_' . $plugin, $CFG->dirroot . '/mod/assign/' . $shortsubtype . '/' .$plugin. '/db/install.xml', false);
 
             // Remove event handlers and dequeue pending events
             events_uninstall($this->subtype . '_' . $plugin);
index cf90c86..3e6e54b 100644 (file)
@@ -52,7 +52,9 @@ class mod_assign_grading_options_form extends moodleform {
         $options = array('' => get_string('filternone', 'assign'),
                          ASSIGN_FILTER_SUBMITTED => get_string('filtersubmitted', 'assign'),
                          ASSIGN_FILTER_REQUIRE_GRADING => get_string('filterrequiregrading', 'assign'));
-        $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options);
+        if ($instance['submissionsenabled']) {
+            $mform->addElement('select', 'filter', get_string('filter', 'assign'), $options);
+        }
 
         // quickgrading
         if ($instance['showquickgrading']) {
index abd5b72..be0eb3d 100644 (file)
@@ -105,7 +105,7 @@ class assign_grading_table extends table_sql implements renderable {
             $where .= ' AND s.timecreated > 0 ';
         }
         if ($filter == ASSIGN_FILTER_REQUIRE_GRADING) {
-            $where .= ' AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)';
+            $where .= ' AND (s.timemodified > g.timemodified OR (s.timemodified IS NOT NULL AND g.timemodified IS NULL))';
         }
         if (strpos($filter, ASSIGN_FILTER_SINGLE_USER) === 0) {
             $userfilter = (int) array_pop(explode('=', $filter));
index 251cff5..5b2438b 100644 (file)
@@ -173,6 +173,7 @@ $string['nousersselected'] = 'No users selected';
 $string['numberofdraftsubmissions'] = 'Drafts';
 $string['numberofparticipants'] = 'Participants';
 $string['numberofsubmittedassignments'] = 'Submitted';
+$string['numberofsubmissionsneedgrading'] = 'Needs grading';
 $string['offline'] = 'No online submissions required';
 $string['overdue'] = '<font color="red">Assignment is overdue by: {$a}</font>';
 $string['outlinegrade'] = 'Grade: {$a}';
@@ -206,7 +207,7 @@ $string['sendsubmissionreceipts_help'] = 'This switch will enable submission rec
 $string['settings'] = 'Assignment settings';
 $string['showrecentsubmissions'] = 'Show recent submissions';
 $string['submissiondrafts'] = 'Require students click submit button';
-$string['submissiondrafts_help'] = 'If enabled, students will have to click a Submit button to declare their submission as final. This allows students to keep a draft version of the submission on the system.';
+$string['submissiondrafts_help'] = 'If enabled, students will have to click a Submit button to declare their submission as final. This allows students to keep a draft version of the submission on the system. If this setting is changed from "No" to "Yes" after students have already submitted those submissions will be regarded as final.';
 $string['submissionnotready'] = 'This assignment is not ready to submit:';
 $string['submissionplugins'] = 'Submission plugins';
 $string['submissionreceipts'] = 'Send submission receipts';
index caccf10..a6cdba7 100644 (file)
@@ -212,16 +212,13 @@ function assign_print_overview($courses, &$htmlarray) {
     // Do assignment_base::isopen() here without loading the whole thing for speed
     foreach ($assignments as $key => $assignment) {
         $time = time();
+        $isopen = $assignment->allowsubmissionsfromdate <= $time;
         if ($assignment->duedate) {
             if ($assignment->preventlatesubmissions) {
-                $isopen = ($assignment->allowsubmissionsfromdate <= $time && $time <= $assignment->duedate);
-            } else {
-                $isopen = ($assignment->allowsubmissionsfromdate <= $time);
+                $isopen = ($isopen && $time <= $assignment->duedate);
             }
         }
-        if (empty($isopen) || empty($assignment->duedate)) {
-            $assignmentids[] = $assignment->id;
-        } else {
+        if ($isopen) {
             $assignmentids[] = $assignment->id;
         }
     }
@@ -265,6 +262,10 @@ function assign_print_overview($courses, &$htmlarray) {
                             AND a.id $sqlassignmentids", array_merge(array($USER->id, $USER->id), $assignmentidparams));
 
     foreach ($assignments as $assignment) {
+        // Do not show assignments that are not open
+        if (!in_array($assignment->id, $assignmentids)) {
+            continue;
+        }
         $str = '<div class="assign overview"><div class="name">'.$strassignment. ': '.
                '<a '.($assignment->visible ? '':' class="dimmed"').
                'title="'.$strassignment.'" href="'.$CFG->wwwroot.
index 2fef79b..8c538fd 100644 (file)
@@ -967,6 +967,27 @@ class assign {
         return count_enrolled_users($this->context, "mod/assign:submit", $currentgroup);
     }
 
+    /**
+     * Load a count of users submissions in the current module that require grading
+     * This means the submission modification time is more recent than the
+     * grading modification time.
+     *
+     * @return int number of matching submissions
+     */
+    public function count_submissions_need_grading() {
+        global $DB;
+
+        $params = array($this->get_course_module()->instance);
+
+        return $DB->count_records_sql("SELECT COUNT('x')
+                                       FROM {assign_submission} s
+                                       LEFT JOIN {assign_grades} g ON s.assignment = g.assignment AND s.userid = g.userid
+                                       WHERE s.assignment = ?
+                                           AND s.timemodified IS NOT NULL
+                                           AND (s.timemodified > g.timemodified OR g.timemodified IS NULL)",
+                                       $params);
+    }
+
     /**
      * Load a count of users enrolled in the current course with the specified permission and group (optional)
      *
@@ -1724,6 +1745,7 @@ class assign {
                                                                   array('cm'=>$this->get_course_module()->id,
                                                                         'contextid'=>$this->context->id,
                                                                         'userid'=>$USER->id,
+                                                                        'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
                                                                         'showquickgrading'=>$showquickgrading,
                                                                         'quickgrading'=>$quickgrading),
                                                                   'post', '',
@@ -1744,7 +1766,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 +1830,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;
@@ -2094,7 +2113,8 @@ class assign {
                                                             $this->is_any_submission_plugin_enabled(),
                                                             $this->count_submissions_with_status(ASSIGN_SUBMISSION_STATUS_SUBMITTED),
                                                             $this->get_instance()->duedate,
-                                                            $this->get_course_module()->id
+                                                            $this->get_course_module()->id,
+                                                            $this->count_submissions_need_grading()
                                                             ));
         }
         $grade = $this->get_user_grade($USER->id, false);
@@ -2654,7 +2674,11 @@ class assign {
         // Need submit permission to submit an assignment
         require_capability('mod/assign:grade', $this->context);
 
-        $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id, 'contextid'=>$this->context->id, 'userid'=>$USER->id, 'showquickgrading'=>false));
+        $mform = new mod_assign_grading_options_form(null, array('cm'=>$this->get_course_module()->id,
+                                                                 'contextid'=>$this->context->id,
+                                                                 'userid'=>$USER->id,
+                                                                 'submissionsenabled'=>$this->is_any_submission_plugin_enabled(),
+                                                                 'showquickgrading'=>false));
         if ($formdata = $mform->get_data()) {
             set_user_preference('assign_perpage', $formdata->perpage);
             set_user_preference('assign_filter', $formdata->filter);
index cb1daf9..5e710a4 100644 (file)
@@ -405,6 +405,8 @@ class assign_grading_summary implements renderable {
     var $submissionsenabled = false;
     /** @var int submissionssubmittedcount - The number of submissions in submitted status */
     var $submissionssubmittedcount = 0;
+    /** @var int submissionsneedgradingcount - The number of submissions that need grading */
+    var $submissionsneedgradingcount = 0;
     /** @var int duedate - The assignment due date (if one is set) */
     var $duedate = 0;
     /** @var int coursemoduleid - The assignment course module id */
@@ -421,7 +423,9 @@ class assign_grading_summary implements renderable {
      * @param int $duedate
      * @param int $coursemoduleid
      */
-    public function __construct($participantcount, $submissiondraftsenabled, $submissiondraftscount, $submissionsenabled, $submissionssubmittedcount, $duedate, $coursemoduleid) {
+    public function __construct($participantcount, $submissiondraftsenabled, $submissiondraftscount,
+                                $submissionsenabled, $submissionssubmittedcount,
+                                $duedate, $coursemoduleid, $submissionsneedgradingcount) {
         $this->participantcount = $participantcount;
         $this->submissiondraftsenabled = $submissiondraftsenabled;
         $this->submissiondraftscount = $submissiondraftscount;
@@ -429,6 +433,7 @@ class assign_grading_summary implements renderable {
         $this->submissionssubmittedcount = $submissionssubmittedcount;
         $this->duedate = $duedate;
         $this->coursemoduleid = $coursemoduleid;
+        $this->submissionsneedgradingcount = $submissionsneedgradingcount;
     }
 
 
index ab3af8a..8ffe18f 100644 (file)
@@ -248,6 +248,8 @@ class mod_assign_renderer extends plugin_renderer_base {
         if ($summary->submissionsenabled) {
             $this->add_table_row_tuple($t, get_string('numberofsubmittedassignments', 'assign'),
                                        $summary->submissionssubmittedcount);
+            $this->add_table_row_tuple($t, get_string('numberofsubmissionsneedgrading', 'assign'),
+                                       $summary->submissionsneedgradingcount);
         }
 
         $time = time();
index 187bc9a..95e2cfb 100644 (file)
@@ -50,8 +50,8 @@ if ($ADMIN->fulltree) {
 
     // The default here is feedback_comments (if it exists)
     $settings->add(new admin_setting_configselect('assign/feedback_plugin_for_gradebook',
-                   new lang_string('feedbackpluginforgradebook', 'mod_assign'),
-                   new lang_string('feedbackplugin', 'mod_assign'), 'assignfeedback_comments', $menu));
+                   new lang_string('feedbackplugin', 'mod_assign'),
+                   new lang_string('feedbackpluginforgradebook', 'mod_assign'), 'assignfeedback_comments', $menu));
     $settings->add(new admin_setting_configcheckbox('assign/showrecentsubmissions',
                    new lang_string('showrecentsubmissions', 'assign'),
                    new lang_string('configshowrecentsubmissions', 'assign'), 0));
index 8f8dcf3..99d29c8 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');
@@ -1820,10 +1820,10 @@ class assignment_base {
      *
      * @global object
      * @global object
-     * @param $userid int The id of the user whose submission we want or 0 in which case USER->id is used
-     * @param $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
+     * @param int $userid int The id of the user whose submission we want or 0 in which case USER->id is used
+     * @param bool $createnew boolean optional Defaults to false. If set to true a new submission object will be created in the database
      * @param bool $teachermodified student submission set if false
-     * @return object The submission
+     * @return object|bool The submission or false (if $createnew is false and there is no existing submission).
      */
     function get_submission($userid=0, $createnew=false, $teachermodified=false) {
         global $USER, $DB;
@@ -1834,8 +1834,10 @@ class assignment_base {
 
         $submission = $DB->get_record('assignment_submissions', array('assignment'=>$this->assignment->id, 'userid'=>$userid));
 
-        if ($submission || !$createnew) {
+        if ($submission) {
             return $submission;
+        } else if (!$createnew) {
+            return false;
         }
         $newsubmission = $this->prepare_new_submission($userid, $teachermodified);
         $DB->insert_record("assignment_submissions", $newsubmission);
index 36bba5a..e0f79f4 100644 (file)
@@ -59,7 +59,7 @@ $PAGE->set_title($title);
 $PAGE->set_heading($title);
 
 $instance = new assignment_upload($cm->id, $assignment, $cm, $course);
-$submission = $instance->get_submission($formdata->userid, true);
+$submission = $instance->get_submission($formdata->userid, false);
 
 $filemanager_options = array('subdirs'=>1, 'maxbytes'=>$assignment->maxbytes, 'maxfiles'=>$assignment->var1, 'accepted_types'=>'*', 'return_types'=>FILE_INTERNAL);
 
@@ -77,8 +77,12 @@ echo $OUTPUT->header();
 echo $OUTPUT->box_start('generalbox');
 if ($instance->can_upload_file($submission) && ($id==null)) {
     $data = new stdClass();
+    $submissionid = null;
+    if (is_object($submission) && isset($submission->id)) {
+        $submissionid = $submission->id;
+    }
     // move submission files to user draft area
-    $data = file_prepare_standard_filemanager($data, 'files', $filemanager_options, $context, 'mod_assignment', 'submission', $submission->id);
+    $data = file_prepare_standard_filemanager($data, 'files', $filemanager_options, $context, 'mod_assignment', 'submission', $submissionid);
     // set file manager itemid, so it will find the files in draft area
     $mform->set_data($data);
     $mform->display();
index 6955693..db7f340 100644 (file)
@@ -11,6 +11,7 @@ if ($groupid !== 0) {
     $url->param('groupid', $groupid);
 }
 $PAGE->set_url($url);
+$PAGE->set_popup_notification_allowed(false); // No popup notifications in the chat window
 
 $chat = $DB->get_record('chat', array('id'=>$id), '*', MUST_EXIST);
 $course = $DB->get_record('course', array('id'=>$chat->course), '*', MUST_EXIST);
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 82e02cb..aa79bd5 100644 (file)
@@ -44,6 +44,7 @@ $context = get_context_instance(CONTEXT_MODULE, $cm->id);
 require_login($course, false, $cm);
 require_capability('mod/chat:chat', $context);
 $PAGE->set_pagelayout('base');
+$PAGE->set_popup_notification_allowed(false); // No popup notifications in the chat window
 
 /// Check to see if groups are being used here
  if ($groupmode = groups_get_activity_groupmode($cm)) {   // Groups are being used
index 25b7b6b..662aadc 100644 (file)
@@ -98,7 +98,7 @@ $string['nomessages'] = 'No messages yet';
 $string['normalkeepalive'] = 'KeepAlive';
 $string['normalstream'] = 'Stream';
 $string['noscheduledsession'] = 'No scheduled session';
-$string['notallowenter'] = 'You are not allow to enter the chat room.';
+$string['notallowenter'] = 'You are not allowed to enter the chat room.';
 $string['notlogged'] = 'You are not logged in!';
 $string['nopermissiontoseethechatlog'] = 'You don\'t have permission to see the chat logs.';
 $string['oldping'] = 'Disconnect timeout';
index 07bbd19..ccbb949 100644 (file)
@@ -213,15 +213,17 @@ class mod_choice_renderer extends plugin_renderer_base {
                             $user->imagealt = '';
                         }
 
+                        $userfullname = fullname($user, $choices->fullnamecapability);
                         if ($choices->viewresponsecapability && $choices->deleterepsonsecapability  && $optionid > 0) {
-                            $attemptaction = html_writer::checkbox('attemptid[]', $user->id,'');
+                            $attemptaction = html_writer::label($userfullname, 'attempt-user'.$user->id, false, array('class' => 'accesshide'));
+                            $attemptaction .= html_writer::checkbox('attemptid[]', $user->id,'', null, array('id' => 'attempt-user'.$user->id));
                             $data .= html_writer::tag('div', $attemptaction, array('class'=>'attemptaction'));
                         }
                         $userimage = $this->output->user_picture($user, array('courseid'=>$choices->courseid));
                         $data .= html_writer::tag('div', $userimage, array('class'=>'image'));
 
                         $userlink = new moodle_url('/user/view.php', array('id'=>$user->id,'course'=>$choices->courseid));
-                        $name = html_writer::tag('a', fullname($user, $choices->fullnamecapability), array('href'=>$userlink, 'class'=>'username'));
+                        $name = html_writer::tag('a', $userfullname, array('href'=>$userlink, 'class'=>'username'));
                         $data .= html_writer::tag('div', $name, array('class'=>'fullname'));
                         $data .= html_writer::tag('div','', array('class'=>'clearfloat'));
                         $optionusers .= html_writer::tag('div', $data, array('class'=>'user'));
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 bb8c01f..cdff54e 100644 (file)
@@ -103,7 +103,7 @@ if ($attemptobj->get_currentpage() != $page) {
         // Prevent out of sequence access.
         redirect($attemptobj->start_attempt_url(null, $attemptobj->get_currentpage()));
     }
-    $DB->set_field('quiz_attempts', 'currentpage', $page);
+    $DB->set_field('quiz_attempts', 'currentpage', $page, array('id' => $attemptid));
 }
 
 // Initialise the JavaScript.
index 111419c..7913b63 100644 (file)
@@ -57,27 +57,36 @@ class mod_quiz_overdue_attempt_updater {
         $count = 0;
         $quizcount = 0;
         foreach ($attemptstoprocess as $attempt) {
-            // If we have moved on to a different quiz, fetch the new data.
-            if (!$quiz || $attempt->quiz != $quiz->id) {
-                $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz), '*', MUST_EXIST);
-                $cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
-                $quizcount += 1;
+            try {
+
+                // If we have moved on to a different quiz, fetch the new data.
+                if (!$quiz || $attempt->quiz != $quiz->id) {
+                    $quiz = $DB->get_record('quiz', array('id' => $attempt->quiz), '*', MUST_EXIST);
+                    $cm = get_coursemodule_from_instance('quiz', $attempt->quiz);
+                    $quizcount += 1;
+                }
+
+                // If we have moved on to a different course, fetch the new data.
+                if (!$course || $course->id != $quiz->course) {
+                    $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
+                }
+
+                // Make a specialised version of the quiz settings, with the relevant overrides.
+                $quizforuser = clone($quiz);
+                $quizforuser->timeclose = $attempt->usertimeclose;
+                $quizforuser->timelimit = $attempt->usertimelimit;
+
+                // Trigger any transitions that are required.
+                $attemptobj = new quiz_attempt($attempt, $quizforuser, $cm, $course);
+                $attemptobj->handle_if_time_expired($timenow, false);
+                $count += 1;
+
+            } catch (moodle_exception $e) {
+                // If an error occurs while processing one attempt, don't let that kill cron.
+                mtrace("Error while processing attempt {$attempt->id} at {$attempt->quiz} quiz:");
+                mtrace($e->getMessage());
+                mtrace($e->getTraceAsString());
             }
-
-            // If we have moved on to a different course, fetch the new data.
-            if (!$course || $course->id != $quiz->course) {
-                $course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
-            }
-
-            // Make a specialised version of the quiz settings, with the relevant overrides.
-            $quizforuser = clone($quiz);
-            $quizforuser->timeclose = $attempt->usertimeclose;
-            $quizforuser->timelimit = $attempt->usertimelimit;
-
-            // Trigger any transitions that are required.
-            $attemptobj = new quiz_attempt($attempt, $quizforuser, $cm, $course);
-            $attemptobj->handle_if_time_expired($timenow, false);
-            $count += 1;
         }
 
         $attemptstoprocess->close();
index 6e28cbf..397869d 100644 (file)
@@ -1556,7 +1556,7 @@ function quiz_supports($feature) {
         case FEATURE_MOD_INTRO:                 return true;
         case FEATURE_COMPLETION_TRACKS_VIEWS:   return true;
         case FEATURE_GRADE_HAS_GRADE:           return true;
-        case FEATURE_GRADE_OUTCOMES:            return false;
+        case FEATURE_GRADE_OUTCOMES:            return true;
         case FEATURE_BACKUP_MOODLE2:            return true;
         case FEATURE_SHOW_DESCRIPTION:          return true;
         case FEATURE_CONTROLS_GRADE_VISIBILITY: return true;
index c1bec7f..89fa16a 100644 (file)
@@ -275,7 +275,7 @@ M.mod_quiz.secure_window = {
         }, '#secureclosebutton');
     },
 
-    close: function(url, delay) {
+    close: function(Y, url, delay) {
         setTimeout(function() {
             if (window.opener) {
                 window.opener.document.location.reload();
index 165ec10..820e711 100644 (file)
@@ -509,8 +509,8 @@ class mod_quiz_renderer extends plugin_renderer_base {
             $output .= html_writer::tag('p', get_string('pleaseclose', 'quiz'));
             $delay = 0;
         }
-        $this->page->requires->js_function_call('M.mod_quiz.secure_window.close',
-                array($url, $delay));
+        $this->page->requires->js_init_call('M.mod_quiz.secure_window.close',
+                array($url, $delay), false, quiz_get_js_module());
 
         $output .= $this->box_end();
         $output .= $this->footer();
@@ -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 03f8959..4334256 100644 (file)
@@ -189,8 +189,6 @@ abstract class quiz_attempts_report_table extends table_sql {
     public function col_duration($attempt) {
         if ($attempt->timefinish) {
             return format_time($attempt->timefinish - $attempt->timestart);
-        } else if ($attempt->timestart) {
-            return get_string('unfinished', 'quiz');
         } else {
             return '-';
         }
index c450615..6583ef9 100644 (file)
@@ -89,7 +89,9 @@ while ($bands > 20 || $bands <= 10) {
     }
 }
 
-$bands = ceil($bands);
+// See MDL-34589. Using doubles as array keys causes problems in PHP 5.4,
+// hence the explicit cast to int.
+$bands = (int) ceil($bands);
 $bandwidth = $quiz->grade / $bands;
 $bandlabels = array();
 for ($i = 1; $i <= $bands; $i++) {
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 d3e4e99..344513e 100644 (file)
@@ -191,6 +191,11 @@ function quiz_report_qm_filter_select($quiz, $quizattemptsalias = 'quiza') {
  */
 function quiz_report_grade_bands($bandwidth, $bands, $quizid, $userids = array()) {
     global $DB;
+    if (!is_int($bands)) {
+        debugging('$bands passed to quiz_report_grade_bands must be an integer. (' .
+                gettype($bands) . ' passed.)', DEBUG_DEVELOPER);
+        $bands = (int) $bands;
+    }
 
     if ($userids) {
         list($usql, $params) = $DB->get_in_or_equal($userids, SQL_PARAMS_NAMED, 'u');
@@ -220,7 +225,7 @@ ORDER BY
     $data = $DB->get_records_sql_menu($sql, $params);
 
     // We need to create array elements with values 0 at indexes where there is no element.
-    $data =  $data + array_fill(0, $bands+1, 0);
+    $data =  $data + array_fill(0, $bands + 1, 0);
     ksort($data);
 
     // Place the maximum (prefect grade) into the last band i.e. make last
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 aa9e48d..03f42f1 100644 (file)
@@ -25,7 +25,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$module->version   = 2012061700;       // The current module version (Date: YYYYMMDDXX).
+$module->version   = 2012061701;       // The current module version (Date: YYYYMMDDXX).
 $module->requires  = 2012061700;    // Requires this Moodle version.
 $module->component = 'mod_quiz';       // Full name of the plugin (used for diagnostics).
 $module->cron      = 60;
index 27862e4..5a1bf0b 100644 (file)
@@ -127,25 +127,72 @@ class backup_scorm_activity_structure_step extends backup_activity_structure_ste
         // Define sources
         $scorm->set_source_table('scorm', array('id' => backup::VAR_ACTIVITYID));
 
-        $sco->set_source_table('scorm_scoes', array('scorm' => backup::VAR_PARENTID));
-
-        $scodata->set_source_table('scorm_scoes_data', array('scoid' => backup::VAR_PARENTID));
-
-        $seqrulecond->set_source_table('scorm_seq_ruleconds', array('scoid' => backup::VAR_PARENTID));
-
-        $seqrulecondsdata->set_source_table('scorm_seq_rulecond', array('ruleconditionsid' => backup::VAR_PARENTID));
-
-        $seqrolluprule->set_source_table('scorm_seq_rolluprule', array('scoid' => backup::VAR_PARENTID));
-
-        $seqrolluprulecond->set_source_table('scorm_seq_rolluprulecond', array('rollupruleid' => backup::VAR_PARENTID));
-
-        $seqobjective->set_source_table('scorm_seq_objective', array('scoid' => backup::VAR_PARENTID));
-
-        $seqmapinfo->set_source_table('scorm_seq_mapinfo', array('objectiveid' => backup::VAR_PARENTID));
+        // Use set_source_sql for other calls as set_source_table returns records in reverse order
+        // and order is important for several SCORM fields - esp scorm_scoes.
+        $sco->set_source_sql('
+                SELECT *
+                FROM {scorm_scoes}
+                WHERE scorm = :scorm
+                ORDER BY id',
+            array('scorm' => backup::VAR_PARENTID));
+
+        $scodata->set_source_sql('
+                SELECT *
+                FROM {scorm_scoes_data}
+                WHERE scoid = :scoid
+                ORDER BY id',
+            array('scoid' => backup::VAR_PARENTID));
+
+        $seqrulecond->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_ruleconds}
+                WHERE scoid = :scoid
+                ORDER BY id',
+            array('scoid' => backup::VAR_PARENTID));
+
+        $seqrulecondsdata->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_rulecond}
+                WHERE ruleconditionsid = :ruleconditionsid
+                ORDER BY id',
+            array('ruleconditionsid' => backup::VAR_PARENTID));
+
+        $seqrolluprule->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_rolluprule}
+                WHERE scoid = :scoid
+                ORDER BY id',
+            array('scoid' => backup::VAR_PARENTID));
+
+        $seqrolluprulecond->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_rolluprulecond}
+                WHERE rollupruleid = :rollupruleid
+                ORDER BY id',
+            array('rollupruleid' => backup::VAR_PARENTID));
+
+        $seqobjective->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_objective}
+                WHERE scoid = :scoid
+                ORDER BY id',
+            array('scoid' => backup::VAR_PARENTID));
+
+        $seqmapinfo->set_source_sql('
+                SELECT *
+                FROM {scorm_seq_mapinfo}
+                WHERE objectiveid = :objectiveid
+                ORDER BY id',
+            array('objectiveid' => backup::VAR_PARENTID));
 
         // All the rest of elements only happen if we are including user info
         if ($userinfo) {
-            $scotrack->set_source_table('scorm_scoes_track', array('scoid' => backup::VAR_PARENTID));
+            $scotrack->set_source_sql('
+                SELECT *
+                FROM {scorm_scoes_track}
+                WHERE scoid = :scoid
+                ORDER BY id',
+                array('scoid' => backup::VAR_PARENTID));
         }
 
         // Define id annotations
index c48c467..6021912 100644 (file)
@@ -22,6 +22,10 @@ define('SCORM_UPDATE_NEVER', '0');
 define('SCORM_UPDATE_EVERYDAY', '2');
 define('SCORM_UPDATE_EVERYTIME', '3');
 
+define('SCORM_SKIPVIEW_NEVER', '0');
+define('SCORM_SKIPVIEW_FIRST', '1');
+define('SCORM_SKIPVIEW_ALWAYS', '2');
+
 define('SCO_ALL', 0);
 define('SCO_DATA', 1);
 define('SCO_ONLY', 2);
@@ -109,9 +113,9 @@ function scorm_get_what_grade_array() {
  * @return array an array of skip view options
  */
 function scorm_get_skip_view_array() {
-    return array(0 => get_string('never'),
-                 1 => get_string('firstaccess', 'scorm'),
-                 2 => get_string('always'));
+    return array(SCORM_SKIPVIEW_NEVER => get_string('never'),
+                 SCORM_SKIPVIEW_FIRST => get_string('firstaccess', 'scorm'),
+                 SCORM_SKIPVIEW_ALWAYS => get_string('always'));
 }
 
 /**
index d558c6b..4b0837c 100644 (file)
@@ -124,7 +124,14 @@ class mod_scorm_mod_form extends moodleform_mod {
         $mform->setAdvanced('winoptgrp', $cfg_scorm->winoptgrp_adv);
 
         // Skip view page
-        $mform->addElement('select', 'skipview', get_string('skipview', 'scorm'), scorm_get_skip_view_array());
+        $skipviewoptions = scorm_get_skip_view_array();
+        if ($COURSE->format == 'scorm') { // Remove option that would cause a constant redirect.
+            unset($skipviewoptions[SCORM_SKIPVIEW_ALWAYS]);
+            if ($cfg_scorm->skipview == SCORM_SKIPVIEW_ALWAYS) {
+                $cfg_scorm->skipview = SCORM_SKIPVIEW_FIRST;
+            }
+        }
+        $mform->addElement('select', 'skipview', get_string('skipview', 'scorm'), $skipviewoptions);
         $mform->addHelpButton('skipview', 'skipview', 'scorm');
         $mform->setDefault('skipview', $cfg_scorm->skipview);
         $mform->setAdvanced('skipview', $cfg_scorm->skipview_adv);
index b251127..43c5395 100644 (file)
@@ -221,6 +221,18 @@ class scorm_interactions_report extends scorm_default_report {
                 $table->no_sorting('finish');
                 $table->no_sorting('score');
 
+                for($id = 0; $id < $questioncount; $id++) {
+                    if ($displayoptions['qtext']) {
+                        $table->no_sorting('question'.$id);
+                    }
+                    if ($displayoptions['resp']) {
+                        $table->no_sorting('response'.$id);
+                    }
+                    if ($displayoptions['right']) {
+                        $table->no_sorting('right'.$id);
+                    }
+                }
+
                 foreach ($scoes as $sco) {
                     if ($sco->launch != '') {
                         $table->no_sorting('scograde'.$sco->id);
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 54c4d9f..1be7df2 100644 (file)
@@ -1213,6 +1213,15 @@ class question_attempt {
         $qa->behaviour = question_engine::make_behaviour(
                 $record->behaviour, $qa, $preferredbehaviour);
 
+        // If attemptstepid is null (which should not happen, but has happened
+        // due to corrupt data, see MDL-34251) then the current pointer in $records
+        // will not be advanced in the while loop below, and we get stuck in an
+        // infinite loop, since this method is supposed to always consume at
+        // least one record. Therefore, in this case, advance the record here.
+        if (is_null($record->attemptstepid)) {
+            $records->next();
+        }
+
         $i = 0;
         while ($record && $record->questionattemptid == $questionattemptid && !is_null($record->attemptstepid)) {
             $qa->steps[$i] = question_attempt_step::load_from_records($records, $record->attemptstepid);
index 8d7ffa9..f9470c0 100644 (file)
@@ -714,6 +714,14 @@ class question_usage_by_activity {
 
         $quba->observer = new question_engine_unit_of_work($quba);
 
+        // If slot is null then the current pointer in $records will not be
+        // advanced in the while loop below, and we get stuck in an infinite loop,
+        // since this method is supposed to always consume at least one record.
+        // Therefore, in this case, advance the record here.
+        if (is_null($record->slot)) {
+            $records->next();
+        }
+
         while ($record && $record->qubaid == $qubaid && !is_null($record->slot)) {
             $quba->questionattempts[$record->slot] =
                     question_attempt::load_from_records($records,
index 73ff8b3..71434ee 100644 (file)
@@ -169,16 +169,17 @@ class question_usage_by_activity_test extends advanced_testcase {
  */
 class question_usage_db_test extends data_loading_method_test_base {
     public function test_load() {
+        $scid = context_system::instance()->id;
         $records = new question_test_recordset(array(
         array('qubaid', 'contextid', 'component', 'preferredbehaviour',
-                                               'questionattemptid', 'contextid', 'questionusageid', 'slot',
+                                               'questionattemptid', 'questionusageid', 'slot',
                                                               'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
                                                                                                              'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
                                                                                                                                      'attemptstepid', 'sequencenumber', 'state', 'fraction',
                                                                                                                                                                      'timecreated', 'userid', 'name', 'value'),
-        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo',             null, 1256233700, 1,       null, null),
-        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233705, 1,   'answer',  '1'),
-        array(1, 1, 'unit_test', 'interactive', 1, 123, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 2, 'gradedright', 1.0000000, 1256233720, 1,  '-finish',  '1'),
+        array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 1, 0, 'todo',             null, 1256233700, 1,       null, null),
+        array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 2, 1, 'todo',             null, 1256233705, 1,   'answer',  '1'),
+        array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', -1, 1, 2.0000000, 0.0000000, 0, '', '', '', 1256233790, 5, 2, 'gradedright', 1.0000000, 1256233720, 1,  '-finish',  '1'),
         ));
 
         $question = test_question_maker::make_question('truefalse', 'true');
@@ -221,4 +222,63 @@ class question_usage_db_test extends data_loading_method_test_base {
         $this->assertEquals(1, $step->get_user_id());
         $this->assertEquals(array('-finish' => '1'), $step->get_all_data());
     }
+
+    public function test_load_data_no_steps() {
+        // The code had a bug where if one question_attempt had no steps,
+        // load_from_records got stuck in an infinite loop. This test is to
+        // verify that no longer happens.
+        $scid = context_system::instance()->id;
+        $records = new question_test_recordset(array(
+        array('qubaid', 'contextid', 'component', 'preferredbehaviour',
+                                                   'questionattemptid', 'questionusageid', 'slot',
+                                                             'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+                                                                                                            'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
+                                                                                                                                                                               'attemptstepid', 'sequencenumber', 'state', 'fraction',
+                                                                                                                                                                                                         'timecreated', 'userid', 'name', 'value'),
+        array(1, $scid, 'unit_test', 'interactive', 1, 1, 1, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+        array(1, $scid, 'unit_test', 'interactive', 2, 1, 2, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+        array(1, $scid, 'unit_test', 'interactive', 3, 1, 3, 'interactive', 0, 1, 1.0000000, 0.0000000, 0, 'This question is missing. Unable to display anything.', '', '', 0, null, null, null, null, null, null, null, null),
+        ));
+
+        question_bank::start_unit_test();
+        $quba = question_usage_by_activity::load_from_records($records, 1);
+        question_bank::end_unit_test();
+
+        $this->assertEquals('unit_test', $quba->get_owning_component());
+        $this->assertEquals(1, $quba->get_id());
+        $this->assertInstanceOf('question_engine_unit_of_work', $quba->get_observer());
+        $this->assertEquals('interactive', $quba->get_preferred_behaviour());
+
+        $this->assertEquals(array(1, 2, 3), $quba->get_slots());
+
+        $qa = $quba->get_question_attempt(1);
+        $this->assertEquals(0, $qa->get_num_steps());
+    }
+
+    public function test_load_data_no_qas() {
+        // The code had a bug where if a question_usage had no question_attempts,
+        // load_from_records got stuck in an infinite loop. This test is to
+        // verify that no longer happens.
+        $scid = context_system::instance()->id;
+        $records = new question_test_recordset(array(
+        array('qubaid', 'contextid', 'component', 'preferredbehaviour',
+                                                   'questionattemptid', 'questionusageid', 'slot',
+                                                                        'behaviour', 'questionid', 'variant', 'maxmark', 'minfraction', 'flagged',
+                                                                                                               'questionsummary', 'rightanswer', 'responsesummary', 'timemodified',
+                                                                                                                                         'attemptstepid', 'sequencenumber', 'state', 'fraction',
+                                                                                                                                                                   'timecreated', 'userid', 'name', 'value'),
+        array(1, $scid, 'unit_test', 'interactive', null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null),
+        ));
+
+        question_bank::start_unit_test();
+        $quba = question_usage_by_activity::load_from_records($records, 1);
+        question_bank::end_unit_test();
+
+        $this->assertEquals('unit_test', $quba->get_owning_component());
+        $this->assertEquals(1, $quba->get_id());
+        $this->assertInstanceOf('question_engine_unit_of_work', $quba->get_observer());
+        $this->assertEquals('interactive', $quba->get_preferred_behaviour());
+
+        $this->assertEquals(array(), $quba->get_slots());
+    }
 }
index 90f78ef..75a3d7d 100644 (file)
@@ -38,7 +38,7 @@ require_once($CFG->dirroot . '/question/type/multianswer/question.php');
  */
 class qtype_multianswer_test_helper extends question_test_helper {
     public function get_test_questions() {
-        return array('twosubq');
+        return array('twosubq', 'fourmc');
     }
 
     /**
@@ -54,6 +54,14 @@ class qtype_multianswer_test_helper extends question_test_helper {
                 'Complete this opening line of verse: "The {#1} and the {#2} went to sea".';
         $q->generalfeedback = 'General feedback: It\'s from "The Owl and the Pussy-cat" by Lear: ' .
                 '"The owl and the pussycat went to sea';
+        $q->qtype = question_bank::get_qtype('multianswer');
+
+        $q->textfragments = array(
+            'Complete this opening line of verse: "The ',
+            ' and the ',
+            ' went to sea".',
+        );
+        $q->places = array('1' => '1', '2' => '2');
 
         // Shortanswer subquestion.
         question_bank::load_question_definition_classes('shortanswer');
@@ -214,4 +222,69 @@ class qtype_multianswer_test_helper extends question_test_helper {
 
         return $formdata;
     }
+
+    /**
+     * Makes a multianswer question about completing two blanks in some text.
+     * @return qtype_multianswer_question
+     */
+    public function make_multianswer_question_fourmc() {
+        question_bank::load_question_definition_classes('multianswer');
+        $q = new qtype_multianswer_question();
+        test_question_maker::initialise_a_question($q);
+        $q->name = 'Multianswer four multi-choice';
+        $q->questiontext = '<p>Match the following cities with the correct state:</p>
+                <ul>
+                <li>San Francisco: {#1}</li>
+                <li>Tucson: {#2}</li>
+                <li>Los Angeles: {#3}</li>
+                <li>Phoenix: {#4}</li>
+                </ul>';
+        $q->questiontextformat = FORMAT_HTML;
+        $q->generalfeedback = '';
+        $q->qtype = question_bank::get_qtype('multianswer');
+
+        $q->textfragments = array('<p>Match the following cities with the correct state:</p>
+                <ul>
+                <li>San Francisco: ', '</li>
+                <li>Tucson: ', '</li>
+                <li>Los Angeles: ', '</li>
+                <li>Phoenix: ', '</li>
+                </ul>');
+        $q->places = array('1' => '1', '2' => '2', '3' => '3', '4' => '4');
+
+        $subqdata = array(
+            1 => array('qt' => '{1:MULTICHOICE:=California#OK~Arizona#Wrong}', 'California' => 'OK', 'Arizona' => 'Wrong'),
+            2 => array('qt' => '{1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}', 'California' => 'Wrong', 'Arizona' => 'OK'),
+            3 => array('qt' => '{1:MULTICHOICE:=California#OK~Arizona#Wrong}', 'California' => 'OK', 'Arizona' => 'Wrong'),
+            4 => array('qt' => '{1:MULTICHOICE:%0%California#Wrong~=Arizona#OK}', 'California' => 'Wrong', 'Arizona' => 'OK'),
+        );
+
+        foreach ($subqdata as $i => $data) {
+            // Multiple-choice subquestion.
+            question_bank::load_question_definition_classes('multichoice');
+            $mc = new qtype_multichoice_single_question();
+            test_question_maker::initialise_a_question($mc);
+            $mc->name = 'Multianswer four multi-choice';
+            $mc->questiontext = $data['qt'];
+            $mc->questiontextformat = FORMAT_HTML;
+            $mc->generalfeedback = '';
+            $mc->generalfeedbackformat = FORMAT_HTML;
+
+            $mc->shuffleanswers = 0; // TODO this is a cheat to make the unit tests easier to write.
+            // In reality, multianswer questions always shuffle.
+            $mc->answernumbering = 'none';
+            $mc->layout = qtype_multichoice_base::LAYOUT_DROPDOWN;
+
+            $mc->answers = array(
+                10 * $i     => new question_answer(13, 'California', $data['California'] == 'OK', $data['California'], FORMAT_HTML),
+                10 * $i + 1 => new question_answer(14, 'Arizona', $data['Arizona'] == 'OK', $data['Arizona'], FORMAT_HTML),
+            );
+            $mc->qtype = question_bank::get_qtype('multichoice');
+            $mc->maxmark = 1;
+
+            $q->subquestions[$i] = $mc;
+        }
+
+        return $q;
+    }
 }
index 65dff59..19af3c7 100644 (file)
@@ -37,70 +37,53 @@ require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class qtype_multianswer_walkthrough_test extends qbehaviour_walkthrough_test_base {
-    public function test_interactive() {
-        return; // TODO
+    public function test_deferred_feedback() {
 
         // Create a gapselect question.
-        $q = test_question_maker::make_question('calculated');
-        $q->hints = array(
-            new question_hint(1, 'This is the first hint.', FORMAT_HTML),
-            new question_hint(2, 'This is the second hint.', FORMAT_HTML),
-        );
-        $this->start_attempt_at_question($q, 'interactive', 3);
-        $values = $q->vs->get_values();
+        $q = test_question_maker::make_question('multianswer', 'fourmc');
+        $this->start_attempt_at_question($q, 'deferredfeedback', 4);
 
         // Check the initial state.
         $this->check_current_state(question_state::$todo);
         $this->check_current_mark(null);
         $this->check_current_output(
                 $this->get_contains_marked_out_of_summary(),
-                $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_feedback_expectation(),
-                $this->get_does_not_contain_validation_error_expectation(),
-                $this->get_does_not_contain_try_again_button_expectation(),
-                $this->get_no_hint_visible_expectation());
+                $this->get_does_not_contain_validation_error_expectation());
 
-        // Submit blank.
-        $this->process_submission(array('-submit' => 1, 'answer' => ''));
+        // Save in incomplete answer.
+        $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '',
+                'sub3_answer' => '', 'sub4_answer' => ''));
 
         // Verify.
         $this->check_current_state(question_state::$invalid);
         $this->check_current_mark(null);
         $this->check_current_output(
                 $this->get_contains_marked_out_of_summary(),
-                $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_feedback_expectation(),
-                $this->get_contains_validation_error_expectation(),
-                $this->get_does_not_contain_try_again_button_expectation(),
-                $this->get_no_hint_visible_expectation());
+                $this->get_does_not_contain_validation_error_expectation()); // TODO, really, it should. See MDL-32049.
 
-        // Sumit something that does not look like a number.
-        $this->process_submission(array('-submit' => 1, 'answer' => 'newt'));
+        // Save a partially correct answer.
+        $this->process_submission(array('sub1_answer' => '1', 'sub2_answer' => '1',
+                'sub3_answer' => '1', 'sub4_answer' => '1'));
 
         // Verify.
-        $this->check_current_state(question_state::$invalid);
+        $this->check_current_state(question_state::$complete);
         $this->check_current_mark(null);
         $this->check_current_output(
                 $this->get_contains_marked_out_of_summary(),
-                $this->get_contains_submit_button_expectation(true),
                 $this->get_does_not_contain_feedback_expectation(),
-                $this->get_contains_validation_error_expectation(),
-                new question_pattern_expectation('/' .
-                        preg_quote(get_string('invalidnumber', 'qtype_numerical') . '/')),
-                $this->get_does_not_contain_try_again_button_expectation(),
-                $this->get_no_hint_visible_expectation());
+                $this->get_does_not_contain_validation_error_expectation());
 
-        // Now get it right.
-        $this->process_submission(array('-submit' => 1, 'answer' => $values['a'] + $values['b']));
+        // Now submit all and finish.
+        $this->process_submission(array('-finish' => 1));
 
         // Verify.
-        $this->check_current_state(question_state::$gradedright);
-        $this->check_current_mark(3);
+        $this->check_current_state(question_state::$gradedpartial);
+        $this->check_current_mark(2);
         $this->check_current_output(
-                $this->get_contains_mark_summary(3),
-                $this->get_contains_submit_button_expectation(false),
-                $this->get_contains_correct_expectation(),
-                $this->get_does_not_contain_validation_error_expectation(),
-                $this->get_no_hint_visible_expectation());
+                $this->get_contains_mark_summary(2),
+                $this->get_contains_partcorrect_expectation(),
+                $this->get_does_not_contain_validation_error_expectation());
     }
 }
index f31ceb6..afca6a4 100644 (file)
@@ -199,7 +199,7 @@ class qtype_multichoice_single_question extends qtype_multichoice_base {
     }
 
     public function is_complete_response(array $response) {
-        return array_key_exists('answer', $response);
+        return array_key_exists('answer', $response) && $response['answer'] !== '';
     }
 
     public function is_gradable_response(array $response) {
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 c34dec7..44e4fe4 100644 (file)
@@ -899,6 +899,9 @@ abstract class repository {
         $repositories = array();
         if (isset($args['accepted_types'])) {
             $accepted_types = $args['accepted_types'];
+            if (is_array($accepted_types) && in_array('*', $accepted_types)) {
+                $accepted_types = '*';
+            }
         } else {
             $accepted_types = '*';
         }
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 98c7384..c7d7f3c 100644 (file)
@@ -56,6 +56,8 @@ class repository_user extends repository {
         $ret['dynload'] = true;
         $ret['nosearch'] = true;
         $ret['nologin'] = true;
+        $manageurl = new moodle_url('/user/files.php');
+        $ret['manage'] = $manageurl->out();
         $list = array();
 
         if (!empty($encodedpath)) {
index 3b51251..eb9260e 100644 (file)
@@ -453,4 +453,16 @@ tab styles for ie6 & ie7
 }
 body#page-course-view-topics.path-course div.moodle-dialogue-base div.yui3-widget{
     z-index: 600!important;
+}
+/* Filemanager
+-------------------------*/
+.filemanager select,
+.filemanager input,
+.filemanager button,
+.filemanager textarea,
+.file-picker select,
+.file-picker input,
+.file-picker button,
+.file-picker textarea {
+    background-color: #EEE;
 }
\ No newline at end of file
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..ed52934 100644 (file)
@@ -275,6 +275,10 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .filemanager.fm-updating .filemanager-updating {display:block;margin-top: 37px;}
 .filemanager.fm-updating .fm-content-wrapper {display:none;}
 .filemanager.fm-nomkdir .fp-btn-mkdir {display:none;}
+.fitem.disabled .filemanager .filemanager-toolbar,
+.fitem.disabled .filemanager .fp-pathbar,
+.fitem.disabled .filemanager .fp-restrictions,
+.fitem.disabled .filemanager .fm-content-wrapper {display:none;}
 
  /*
  * File Manager layout
@@ -290,8 +294,11 @@ 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;}
+.fitem.disabled .filepicker-filelist, .fitem.disabled .filemanager-container {background-color:#EBEBE4;}
+.fitem.disabled .fp-btn-choose {color:graytext;}
+.fitem.disabled .filepicker-filelist .filepicker-filename {display:none;}
 
 /*
  * Icon view (File Manager only)
@@ -324,7 +331,7 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .filemanager .fp-tableview .fp-folder.fp-hascontextmenu .fp-contextmenu {display: inline;position: absolute;left: 14px;margin-right: -20px;top: 6px;}
 
 /*
- * Drag and drop support (File Manager only)
+ * Drag and drop support (filemanager and filepicker form elements)
  */
 .filepicker-filelist .filepicker-container,
 .filemanager.fm-noitems .fm-empty-container {display:block;position:absolute;top:10px;bottom:10px;left:10px;right:10px;border: 2px dashed #BBBBBB;padding-top:85px;text-align:center;z-index: 3000;}
@@ -339,6 +346,7 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .dndupload-uploadinprogress {display:none;text-align:center;}
 .dndupload-uploading .dndupload-uploadinprogress {display:block;}
 .dndupload-arrow {background:url([[pix:theme|fp/dnd_arrow]]) center no-repeat;width:60px;height:80px;position:absolute;margin-left: -28px;top: 5px;}
+.fitem.disabled .filepicker-container, .fitem.disabled .fm-empty-container {display:none;}
 
 /*
  * Select Dialogue (File Manager only)
index cfdb62b..bc31d35 100644 (file)
@@ -58,7 +58,7 @@ blockquote {
     font-size: 1.2em;
     border: 1px solid #eee;
     padding: 2px 5px;
-    background: #fff;
+    background-color: #fff;
 }
 
 img{
@@ -276,12 +276,20 @@ h2.headingblock {
     margin-left: 0;
 }
 
+.editing .course-content .weeks .section.main .content {
+    margin-left: 40px;
+}
+
 .course-content .weeks .section.main .left {
     display: none;
 }
 
+.editing .course-content .weeks .section.main .left {
+    display: block;
+}
+
 .course-content .section.main.current {
-    background:#fffcdc;
+    background-color: #fffcdc;
 }
 
 .course-content .weeks .section.main h3.weekdates {
@@ -289,9 +297,10 @@ h2.headingblock {
 }
 
 .course-content .current .left,
-.course-content .current h3.weekdates {
-    color: #2d83d5 !important;
+.course-content .current h3.sectionname {
+    color: #2d83d5;
 }
+
 /* Forum
 --------------------------*/
 
@@ -318,12 +327,11 @@ h2.headingblock {
     padding: 5px 10px 10px;
 }
 
-
 /* Dock
 ----------------------*/
 
 #dock {
-    background: #eee;
+    background-color: #eee;
     border: none;
 }
 
@@ -354,7 +362,7 @@ h2.headingblock {
 #dockeditempanel .dockeditempanel_hd {
     border-bottom: none;
     padding: 3px 5px;
-    background: #eee;
+    background-color: #eee;
     text-align: left;
 }
 
index 2e05c99..544c792 100644 (file)
@@ -35,6 +35,7 @@ $THEME->parents = array(
 // Set the stylesheets that we want to include for this theme
 $THEME->sheets = array(
     'jmobile11',
+    'jmobile11_rtl',
     'core',
     'media'
 );
diff --git a/theme/mymobile/style/jmobile11_rtl.css b/theme/mymobile/style/jmobile11_rtl.css
new file mode 100644 (file)
index 0000000..7d2ba26
--- /dev/null
@@ -0,0 +1,54 @@
+.dir-rtl .ui-collapsible-heading a {text-align: right}
+
+.dir-rtl .ui-li, .ui-li.ui-field-contain {text-align: right;}
+
+.dir-rtl .ui-li-thumb, .dir-rtl .ui-listview .ui-li-icon, .dir-rtl .ui-li-content {
+    float: right;
+    margin-left: 27px;
+}
+.dir-rtl .ui-li, .dir-rtl .ui-li.ui-field-contain {
+    text-align: right;
+}
+.dir-rtl .ui-listview .ui-li-icon {right:27px; left:auto;}
+
+.dir-rtl .mod-indent-1 { margin-right: 0px;}
+
+.dir-rtl .ui-icon-arrow-r {
+    background-position: -108px 0%;
+}
+
+.dir-rtl .ui-field-contain div.ui-slider-switch {
+    width: 10.5em;
+}
+
+.dir-rtl .coursebox h3 a .ui-btn-inner, .dir-rtl .category a .ui-btn-inner {
+    padding-right: .8em;
+    padding-left: 8px;
+}
+
+.dir-rtl .mform .fitem .felement {
+    margin-right: auto;
+    margin-left: 16%;
+    text-align: right;
+}
+
+.dir-rtl .has-myblocks .content-secondary {
+    text-align: right;
+}
+
+.dir-rtl .forumheaderlist thead .ui-btn-inner, .dir-rtl .topic .ui-btn {
+    text-align: right !important;
+    padding-right: 7px;
+}
+
+.dir-rtl .ui-li-count { left:38px; right:auto;}
+
+.dir-rtl .ui-checkbox .ui-btn, .dir-rtl .ui-radio .ui-btn {
+    text-align: right;
+}
+
+.dir-rtl .mform .fitemtitle { text-align: right !important; }
+
+.path-mod-quiz.dir-rtl .que .control {
+    width: 75%;
+}
\ No newline at end of file
index 246fae2..4d57f57 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012062501.02;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012062501.05;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.3.1+ (Build: 20120712)';   // Human-friendly version name
+$release  = '2.3.1+ (Build: 20120726)';   // Human-friendly version name
 
 $branch   = '23';                       // this version's branch
 $maturity = MATURITY_STABLE;            // this version's maturity level