Merge branch 'MDL-33056_dnd_status_bar' of git://github.com/davosmith/moodle
authorSam Hemelryk <sam@moodle.com>
Tue, 5 Jun 2012 20:30:02 +0000 (08:30 +1200)
committerSam Hemelryk <sam@moodle.com>
Tue, 5 Jun 2012 20:30:02 +0000 (08:30 +1200)
198 files changed:
admin/cli/install.php
admin/cli/install_database.php
admin/cli/mysql_engine.php
admin/cli/upgrade.php
admin/index.php
admin/oauth2callback.php [new file with mode: 0644]
admin/registration/confirmregistration.php
admin/registration/forms.php
admin/registration/hubselector.php [deleted file]
admin/registration/index.php
admin/registration/register.php
admin/registration/renderer.php
admin/registration/renewregistration.php
admin/renderer.php
admin/settings/server.php
admin/settings/top.php
admin/tool/bloglevelupgrade/index.php
auth/shibboleth/index.php
backup/moodle2/backup_stepslib.php
backup/moodle2/backup_xml_transformer.class.php
backup/moodle2/restore_final_task.class.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/backup_controller_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/plan/base_plan.class.php
backup/util/ui/backup_ui_stage.class.php
backup/util/ui/base_moodleform.class.php
calendar/lib.php
course/changenumsections.php
course/externallib.php
course/format/renderer.php
course/format/topics/format.js
course/format/topics/lib.php
course/format/weeks/format.js
course/format/weeks/lib.php
course/lib.php
course/recent.php
course/rest.php
course/view.php
course/yui/coursebase/coursebase.js
course/yui/dragdrop/dragdrop.js
enrol/externallib.php
files/renderer.php
grade/edit/tree/grade.php
grade/lib.php
group/externallib.php
install.php
install/lang/es_mx/admin.php [moved from portfolio/googledocs/db/events.php with 53% similarity]
install/lang/es_mx/install.php [new file with mode: 0644]
lang/en/access.php
lang/en/admin.php
lang/en/hub.php
lang/en/moodle.php
lang/en/plugin.php
lang/en/repository.php
lang/en/webservice.php
lib/completion/completion_criteria_activity.php
lib/completion/completion_criteria_course.php
lib/completion/completion_criteria_date.php
lib/completion/completion_criteria_duration.php
lib/completion/completion_criteria_grade.php
lib/completionlib.php
lib/conditionlib.php
lib/configonlylib.php
lib/cronlib.php
lib/db/access.php
lib/db/install.xml
lib/db/log.php
lib/db/upgrade.php
lib/dml/mssql_native_moodle_database.php
lib/dml/tests/dml_test.php
lib/evalmath/evalmath.class.php
lib/externallib.php
lib/filebrowser/file_info.php
lib/filebrowser/file_info_stored.php
lib/filelib.php
lib/filestorage/file_storage.php
lib/filestorage/stored_file.php
lib/filestorage/tests/file_storage_test.php
lib/form/dndupload.js
lib/form/filemanager.js
lib/form/filepicker.js
lib/gdlib.php
lib/googleapi.php
lib/installlib.php
lib/mathslib.php
lib/messagelib.php
lib/moodlelib.php
lib/oauthlib.php
lib/outputcomponents.php
lib/outputrenderers.php
lib/phpunit/lib.php
lib/pluginlib.php
lib/portfolio/exporter.php
lib/portfolio/forms.php
lib/setup.php
lib/tablelib.php
lib/tests/conditionlib_test.php
lib/tests/configonlylib_test.php [new file with mode: 0644]
lib/tests/mathslib_test.php
lib/tests/moodlelib_test.php
lib/upgradelib.php
lib/yui/blocks/blocks.js
lib/yui/dragdrop/dragdrop.js
message/defaultoutputs.php
message/externallib.php
message/lib.php
mod/assign/assignmentplugin.php
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/locallib.php
mod/assign/feedbackplugin.php
mod/assign/gradingtable.php
mod/assign/locallib.php
mod/assign/settings.php
mod/assign/submission/comments/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/book/lang/en/book.php
mod/book/locallib.php
mod/book/mod_form.php
mod/book/settings.php
mod/book/styles.css
mod/book/tool/exportimscp/lang/en/booktool_exportimscp.php
mod/book/tool/importhtml/lang/en/booktool_importhtml.php
mod/book/tool/print/index.php
mod/book/version.php
mod/folder/edit.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/rsslib.php
mod/forum/subscribe.php
mod/forum/unsubscribeall.php
mod/quiz/module.js
mod/quiz/report/grading/report.php
mod/resource/lib.php
mod/scorm/datamodels/aicclib.php
notes/externallib.php
phpunit.xml.dist
pix/u/f3.png [new file with mode: 0644]
portfolio/add.php
portfolio/googledocs/db/upgrade.php [new file with mode: 0644]
portfolio/googledocs/lang/en/portfolio_googledocs.php
portfolio/googledocs/lib.php
portfolio/googledocs/version.php
portfolio/picasa/db/events.php [deleted file]
portfolio/picasa/db/upgrade.php [new file with mode: 0644]
portfolio/picasa/lang/en/portfolio_picasa.php
portfolio/picasa/lib.php
portfolio/picasa/version.php
question/import_form.php
question/type/essay/db/upgrade.php
question/type/essay/renderer.php
question/type/essay/version.php
rating/index.php
report/log/index.php
repository/coursefiles/lib.php
repository/dropbox/lang/en/repository_dropbox.php
repository/filepicker.js
repository/googledocs/db/upgrade.php [new file with mode: 0644]
repository/googledocs/lang/en/repository_googledocs.php
repository/googledocs/lib.php
repository/googledocs/version.php
repository/lib.php
repository/picasa/db/upgrade.php [new file with mode: 0644]
repository/picasa/lang/en/repository_picasa.php
repository/picasa/lib.php
repository/picasa/version.php
repository/recent/lib.php
repository/repository_ajax.php
repository/upload/lib.php
theme/afterburner/config.php
theme/anomaly/config.php
theme/arialist/config.php
theme/base/config.php
theme/base/style/filemanager.css
theme/boxxie/config.php
theme/boxxie/style/boilerplate.css [deleted file]
theme/canvas/config.php
theme/formal_white/config.php
theme/formal_white/style/formal_white.css
theme/formfactor/config.php
theme/leatherbound/config.php
theme/magazine/config.php
theme/serenity/layout/embedded.php [deleted file]
theme/serenity/layout/frontpage.php [deleted file]
theme/serenity/layout/general.php [deleted file]
theme/splash/config.php
theme/standardold/config.php
theme/upgrade.txt
user/externallib.php
user/files.php
user/files_form.php
user/lib.php
version.php
webservice/lib.php
webservice/rest/locallib.php
webservice/rest/server.php

index dc65e31..79ff0b7 100644 (file)
@@ -425,7 +425,8 @@ if (isset($maturity)) {
                 exit(1);
             }
         } else {
-            cli_error(get_string('maturitycorewarning', 'admin'));
+            cli_problem(get_string('maturitycorewarning', 'admin', $maturitylevel));
+            cli_error(get_string('maturityallowunstable', 'admin'));
         }
     }
 }
@@ -677,7 +678,9 @@ if (!$envstatus) {
 
 // Test plugin dependencies.
 require_once($CFG->libdir . '/pluginlib.php');
-if (!plugin_manager::instance()->all_plugins_ok($version)) {
+$failed = array();
+if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+    cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed)))));
     cli_error(get_string('pluginschecktodo', 'admin'));
 }
 
index 2524c41..313f352 100644 (file)
@@ -161,7 +161,9 @@ if (!$envstatus) {
 
 // Test plugin dependencies.
 require_once($CFG->libdir . '/pluginlib.php');
-if (!plugin_manager::instance()->all_plugins_ok($version)) {
+$failed = array();
+if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+    cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed)))));
     cli_error(get_string('pluginschecktodo', 'admin'));
 }
 
index 3b51f41..6bc3642 100644 (file)
@@ -34,8 +34,8 @@ if ($DB->get_dbfamily() !== 'mysql') {
 }
 
 // now get cli options
-list($options, $unrecognized) = cli_get_params(array('help'=>false, 'list'=>false, 'engine'=>false),
-                                               array('h'=>'help', 'l'=>'list'));
+list($options, $unrecognized) = cli_get_params(array('help'=>false, 'list'=>false, 'engine'=>false, 'available'=>false),
+                                               array('h'=>'help', 'l'=>'list', 'a'=>'available'));
 
 if ($unrecognized) {
     $unrecognized = implode("\n  ", $unrecognized);
@@ -52,6 +52,7 @@ and does not support transactions.
 Options:
 --engine=ENGINE       Convert MySQL tables to different engine
 -l, --list            Show table information
+-a, --available       Show list of available engines
 -h, --help            Print out this help
 
 Example:
@@ -59,7 +60,11 @@ Example:
 ";
 
 if (!empty($options['engine'])) {
+    $engines = mysql_get_engines();
     $engine = clean_param($options['engine'], PARAM_ALPHA);
+    if (!isset($engines[strtoupper($engine)])) {
+        cli_error("Error: engine '$engine' is not available on this server!");
+    }
 
     echo "Converting tables to '$engine' for $CFG->wwwroot:\n";
     $prefix = $DB->get_prefix();
@@ -68,9 +73,11 @@ if (!empty($options['engine'])) {
     $rs = $DB->get_recordset_sql($sql);
     $converted = 0;
     $skipped   = 0;
+    $errors    = 0;
     foreach ($rs as $table) {
-        if ($table->engine === $engine) {
-            echo str_pad($table->name, 40). " - NO CONVERSION NEEDED\n";
+        if (strtoupper($table->engine) === strtoupper($engine)) {
+            $newengine = mysql_get_table_engine($table->name);
+            echo str_pad($table->name, 40). " - NO CONVERSION NEEDED ($newengine)\n";
             $skipped++;
             continue;
         }
@@ -78,16 +85,22 @@ if (!empty($options['engine'])) {
 
         try {
             $DB->change_database_structure("ALTER TABLE {$table->name} ENGINE = $engine");
+            $newengine = mysql_get_table_engine($table->name);
+            if (strtoupper($newengine) !== strtoupper($engine)) {
+                echo "ERROR ($newengine)\n";
+                $errors++;
+                continue;
+            }
+            echo "DONE ($newengine)\n";
+            $converted++;
         } catch (moodle_exception $e) {
             echo $e->getMessage()."\n";
-            $skipped++;
+            $errors++;
             continue;
         }
-        echo "DONE\n";
-        $converted++;
     }
     $rs->close();
-    echo "Converted: $converted, skipped: $skipped\n";
+    echo "Converted: $converted, skipped: $skipped, errors: $errors\n";
     exit(0); // success
 
 } else if (!empty($options['list'])) {
@@ -115,7 +128,53 @@ if (!empty($options['engine'])) {
     }
     exit(0); // success
 
+} else if (!empty($options['available'])) {
+    echo "List of available MySQL engines for $CFG->wwwroot:\n";
+    $engines = mysql_get_engines();
+    foreach ($engines as $engine) {
+        echo " $engine\n";
+    }
+    die;
+
 } else {
     echo $help;
     die;
 }
+
+
+
+// ========== Some functions ==============
+
+function mysql_get_engines() {
+    global $DB;
+
+    $sql = "SHOW Engines";
+    $rs = $DB->get_recordset_sql($sql);
+    $engines = array();
+    foreach ($rs as $engine) {
+        if (strtoupper($engine->support) !== 'YES' and strtoupper($engine->support) !== 'DEFAULT') {
+            continue;
+        }
+        $engines[strtoupper($engine->engine)] = $engine->engine;
+        if (strtoupper($engine->support) === 'DEFAULT') {
+            $engines[strtoupper($engine->engine)] .= ' (default)';
+        }
+    }
+    $rs->close();
+
+    return $engines;
+}
+
+function mysql_get_table_engine($tablename) {
+    global $DB;
+
+    $engine = null;
+    $sql = "SHOW TABLE STATUS WHERE Name = '$tablename'"; // no special chars expected here
+    $rs = $DB->get_recordset_sql($sql);
+    if ($rs->valid()) {
+        $record = $rs->current();
+        $engine = $record->engine;
+    }
+    $rs->close();
+    return $engine;
+}
index 33e66cc..1fae897 100644 (file)
@@ -109,7 +109,9 @@ if (!$envstatus) {
 }
 
 // Test plugin dependencies.
-if (!plugin_manager::instance()->all_plugins_ok($version)) {
+$failed = array();
+if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+    cli_problem(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed)))));
     cli_error(get_string('pluginschecktodo', 'admin'));
 }
 
@@ -132,7 +134,8 @@ if (isset($maturity)) {
             echo get_string('morehelp') . ': ' . get_docs_url('admin/versions') . PHP_EOL;
             cli_separator();
         } else {
-            cli_error(get_string('maturitycorewarning', 'admin', $maturitylevel));
+            cli_problem(get_string('maturitycorewarning', 'admin', $maturitylevel));
+            cli_error(get_string('maturityallowunstable', 'admin'));
         }
     }
 }
index 5b01be2..d4a7d4e 100644 (file)
@@ -44,11 +44,6 @@ if (!function_exists('iconv')) {
     echo 'Moodle requires the iconv PHP extension. Please install or enable the iconv extension.';
     die();
 }
-if (iconv('UTF-8', 'UTF-8//IGNORE', 'abc') !== 'abc') {
-    // known to be broken in mid-2011 MAMP installations
-    echo 'Broken iconv PHP extension detected, installation/upgrade can not continue.';
-    die();
-}
 
 define('NO_OUTPUT_BUFFERING', true);
 
@@ -152,6 +147,20 @@ if (!core_tables_exist()) {
         die();
     }
 
+    // check plugin dependencies
+    $failed = array();
+    if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+        $PAGE->navbar->add(get_string('pluginscheck', 'admin'));
+        $PAGE->set_title($strinstallation);
+        $PAGE->set_heading($strinstallation . ' - Moodle ' . $CFG->target_release);
+
+        $output = $PAGE->get_renderer('core', 'admin');
+        $url = new moodle_url('/admin/index.php', array('agreelicense' => 1, 'confirmrelease' => 1, 'lang' => $CFG->lang));
+        echo $output->unsatisfied_dependencies_page($version, $failed, $url);
+        die();
+    }
+    unset($failed);
+
     //TODO: add a page with list of non-standard plugins here
 
     $strdatabasesetup = get_string('databasesetup');
@@ -238,6 +247,15 @@ if ($version > $CFG->version) {  // upgrade
 
         $reloadurl = new moodle_url('/admin/index.php', array('confirmupgrade' => 1, 'confirmrelease' => 1));
 
+        // check plugin dependencies first
+        $failed = array();
+        if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+            $output = $PAGE->get_renderer('core', 'admin');
+            echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
+            die();
+        }
+        unset($failed);
+
         if ($fetchupdates) {
             // no sesskey support guaranteed here
             if (empty($CFG->disableupdatenotifications)) {
@@ -290,6 +308,16 @@ if (moodle_needs_upgrading()) {
             }
 
             $output = $PAGE->get_renderer('core', 'admin');
+
+            // check plugin dependencies first
+            $failed = array();
+            if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
+                echo $output->unsatisfied_dependencies_page($version, $failed, $PAGE->url);
+                die();
+            }
+            unset($failed);
+
+            // dependencies check passed, let's rock!
             echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(),
                     $version, $showallplugins,
                     new moodle_url($PAGE->url),
@@ -401,6 +429,10 @@ $availableupdates = $updateschecker->get_update_info('core',
     array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
 $availableupdatesfetch = $updateschecker->get_last_timefetched();
 
+$buggyiconvnomb = (!function_exists('mb_convert_encoding') and @iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
+//check if the site is registered on Moodle.org
+$registered = $DB->count_records('registration_hubs', array('huburl' => HUB_MOODLEORGHUBURL, 'confirmed' => 1));
+
 admin_externalpage_setup('adminnotifications');
 
 if ($fetchupdates) {
@@ -411,4 +443,5 @@ if ($fetchupdates) {
 
 $output = $PAGE->get_renderer('core', 'admin');
 echo $output->admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
-        $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch);
+        $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch, $buggyiconvnomb,
+        $registered);
diff --git a/admin/oauth2callback.php b/admin/oauth2callback.php
new file mode 100644 (file)
index 0000000..c032a9c
--- /dev/null
@@ -0,0 +1,46 @@
+<?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/>.
+
+/**
+ * An oauth2 redirection endpoint which can be used for an application:
+ * http://tools.ietf.org/html/draft-ietf-oauth-v2-26#section-3.1.2
+ *
+ * This is used because some oauth servers will not allow a redirect urls
+ * with get params (like repository callback) and that needs to be called
+ * using the state param.
+ *
+ * @package    core
+ * @copyright  2012 Dan Poltawski
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(dirname(dirname(__FILE__)).'/config.php');
+
+// The authorization code generated by the authorization server.
+$code = required_param('code', PARAM_RAW);
+// The state parameter we've given (used in moodle as a redirect url).
+$state = required_param('state', PARAM_LOCALURL);
+
+$redirecturl = new moodle_url($state);
+$params = $redirecturl->params();
+
+if (isset($params['sesskey']) and confirm_sesskey($params['sesskey'])) {
+    $redirecturl->param('oauth2code', $code);
+    redirect($redirecturl);
+} else {
+    print_error('invalidsesskey');
+}
index 12afb50..e7c8288 100644 (file)
@@ -44,7 +44,7 @@ $hubname = optional_param('hubname', '', PARAM_TEXT);
 $token = optional_param('token', '', PARAM_TEXT);
 $error = optional_param('error', '', PARAM_ALPHANUM);
 
-admin_externalpage_setup('registrationindex');
+admin_externalpage_setup('registrationhubs');
 
 if (!empty($error) and $error == 'urlalreadyexist') {
     throw new moodle_exception('urlalreadyregistered', 'hub',
index 48c2cd9..0126722 100644 (file)
@@ -236,6 +236,7 @@ class site_registration_form extends moodleform {
         $geolocation = get_config('hub', 'site_geolocation_' . $cleanhuburl);
         $contactable = get_config('hub', 'site_contactable_' . $cleanhuburl);
         $emailalert = get_config('hub', 'site_emailalert_' . $cleanhuburl);
+        $emailalert = ($emailalert === 0) ? 0 : 1;
         $coursesnumber = get_config('hub', 'site_coursesnumber_' . $cleanhuburl);
         $usersnumber = get_config('hub', 'site_usersnumber_' . $cleanhuburl);
         $roleassignmentsnumber = get_config('hub', 'site_roleassignmentsnumber_' . $cleanhuburl);
@@ -279,9 +280,6 @@ class site_registration_form extends moodleform {
         $mform->setType('description', PARAM_TEXT);
         $mform->addHelpButton('description', 'sitedesc', 'hub');
 
-        $mform->addElement('static', 'urlstring', get_string('siteurl', 'hub'), $CFG->wwwroot);
-        $mform->addHelpButton('urlstring', 'siteurl', 'hub');
-
         $languages = get_string_manager()->get_list_of_languages();
         collatorlib::asort($languages);
         $mform->addElement('select', 'language', get_string('sitelang', 'hub'),
@@ -290,16 +288,6 @@ class site_registration_form extends moodleform {
         $mform->addHelpButton('language', 'sitelang', 'hub');
         $mform->setDefault('language', $language);
 
-        $mform->addElement('static', 'versionstring', get_string('siteversion', 'hub'), $CFG->version);
-        $mform->addElement('hidden', 'moodleversion', $CFG->version);
-        $mform->setType('moodleversion', PARAM_INT);
-        $mform->addHelpButton('versionstring', 'siteversion', 'hub');
-
-        $mform->addElement('static', 'releasestring', get_string('siterelease', 'hub'), $CFG->release);
-        $mform->addElement('hidden', 'moodlerelease', $CFG->release);
-        $mform->setType('moodlerelease', PARAM_TEXT);
-        $mform->addHelpButton('releasestring', 'siterelease', 'hub');
-
         $mform->addElement('textarea', 'address', get_string('postaladdress', 'hub'),
                 array('rows' => 4, 'cols' => 41));
         $mform->setType('address', PARAM_TEXT);
@@ -360,6 +348,20 @@ class site_registration_form extends moodleform {
         //TODO site logo
         $mform->addElement('hidden', 'imageurl', ''); //TODO: temporary
         $mform->setType('imageurl', PARAM_URL);
+
+        $mform->addElement('static', 'urlstring', get_string('siteurl', 'hub'), $CFG->wwwroot);
+        $mform->addHelpButton('urlstring', 'siteurl', 'hub');
+
+        $mform->addElement('static', 'versionstring', get_string('siteversion', 'hub'), $CFG->version);
+        $mform->addElement('hidden', 'moodleversion', $CFG->version);
+        $mform->setType('moodleversion', PARAM_INT);
+        $mform->addHelpButton('versionstring', 'siteversion', 'hub');
+
+        $mform->addElement('static', 'releasestring', get_string('siterelease', 'hub'), $CFG->release);
+        $mform->addElement('hidden', 'moodlerelease', $CFG->release);
+        $mform->setType('moodlerelease', PARAM_TEXT);
+        $mform->addHelpButton('releasestring', 'siterelease', 'hub');
+
         /// Display statistic that are going to be retrieve by the hub
         $coursecount = $DB->count_records('course') - 1;
         $usercount = $DB->count_records('user', array('deleted' => 0));
diff --git a/admin/registration/hubselector.php b/admin/registration/hubselector.php
deleted file mode 100644 (file)
index 6bb215f..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<?php
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/*
- * @package    moodle
- * @subpackage registration
- * @author     Jerome Mouneyrac <jerome@mouneyrac.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
- * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
- *
- * Thsi page displays a hub selector or a hub URL + password. Then it will redirect to
- * the site registration form (with the selected hub as parameter)
-*/
-
-require('../../config.php');
-
-require_once($CFG->libdir.'/adminlib.php');
-require_once($CFG->dirroot.'/' . $CFG->admin . '/registration/forms.php');
-
-admin_externalpage_setup('registrationselector');
-
-$hubselectorform = new hub_selector_form();
-$fromform = $hubselectorform->get_data();
-
-//// Redirect to the registration form if an URL has been choosen ////
-
-$selectedhuburl = optional_param('publichub', false, PARAM_URL);
-$unlistedhuburl = optional_param('unlistedurl', false, PARAM_TEXT);
-$password = optional_param('password', '', PARAM_RAW);
-
-if (!empty($unlistedhuburl)) {
-    if (clean_param($unlistedhuburl, PARAM_URL) !== '') {
-        $huburl = $unlistedhuburl;
-    }
-} else if (!empty($selectedhuburl)) {
-    $huburl = $selectedhuburl;
-}
-
-
-//redirect
-if (!empty($huburl) and confirm_sesskey()) {
-    $hubname = optional_param(clean_param($huburl, PARAM_ALPHANUMEXT), '', PARAM_TEXT);
-    $params = array('sesskey' => sesskey(), 'huburl' => $huburl,
-            'password' => $password, 'hubname' => $hubname);
-    redirect(new moodle_url($CFG->wwwroot."/" . $CFG->admin . "/registration/register.php",
-            $params));
-}
-
-
-//// OUTPUT ////
-
-echo $OUTPUT->header();
-echo $OUTPUT->heading(get_string('registeron', 'hub'), 3, 'main');
-$hubselectorform->display();
-echo $OUTPUT->footer();
\ No newline at end of file
index 02e5cde..4c841ae 100644 (file)
@@ -22,8 +22,9 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
  * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
  *
- * On this page the administrator select if he wants to register on Moodle.org or
- * a specific hub
+ * On this page the administrator selects which hub he wants to register,
+ * except for MOOCH. Admins can register with MOOCH with the top admin menu "Registration" link.
+ * On this page the administrator can also unregister from any hubs, including MOOCH.
  */
 
 require('../../config.php');
@@ -34,7 +35,7 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/forms.php');
 require_once($CFG->dirroot . '/course/publish/lib.php');
 require_once($CFG->dirroot . "/webservice/xmlrpc/lib.php");
 
-admin_externalpage_setup('registrationindex');
+admin_externalpage_setup('registrationhubs');
 
 $renderer = $PAGE->get_renderer('core', 'register');
 
@@ -126,13 +127,15 @@ if (empty($cancel) and $unregistration and $confirm and confirm_sesskey()) {
     }
 }
 
-echo $OUTPUT->header();
-
-//do not check sesskey if confirm = false because this script is linked into email message
-if (!empty($errormessage)) {
-    echo $OUTPUT->notification(get_string('unregistrationerror', 'hub', $errormessage));
-}
 if (empty($cancel) and $unregistration and !$confirm) {
+
+    echo $OUTPUT->header();
+
+    //do not check sesskey if confirm = false because this script is linked into email message
+    if (!empty($errormessage)) {
+        echo $OUTPUT->notification(get_string('unregistrationerror', 'hub', $errormessage));
+    }
+
     $hub = $registrationmanager->get_registeredhub($huburl);
     echo $OUTPUT->heading(get_string('unregisterfrom', 'hub', $hub->hubname), 3, 'main');
     if ($cleanregdata) {
@@ -142,6 +145,7 @@ if (empty($cancel) and $unregistration and !$confirm) {
         $siteunregistrationform = new site_unregistration_form('',
                         array('huburl' => $huburl, 'hubname' => $hub->hubname));
     }
+
     $siteunregistrationform->display();
 } else {
     $registeredonmoodleorg = false;
@@ -150,8 +154,49 @@ if (empty($cancel) and $unregistration and !$confirm) {
         $registeredonmoodleorg = true;
     }
 
-    echo $OUTPUT->heading(get_string('registeron', 'hub'), 3, 'main');
-    echo $renderer->registrationselector($registeredonmoodleorg);
+    // load the hub selector form
+    $hubselectorform = new hub_selector_form();
+    $fromform = $hubselectorform->get_data();
+    $selectedhuburl = optional_param('publichub', false, PARAM_URL);
+    $unlistedhuburl = optional_param('unlistedurl', false, PARAM_TEXT);
+    $password = optional_param('password', '', PARAM_RAW);
+
+    if (!empty($unlistedhuburl)) {
+        if (clean_param($unlistedhuburl, PARAM_URL) !== '') {
+            $huburl = $unlistedhuburl;
+        }
+    } else if (!empty($selectedhuburl)) {
+        $huburl = $selectedhuburl;
+    }
+
+    // a hub has been selected, redirect to the hub registration page
+    if (empty($cancel) and !empty($huburl) and confirm_sesskey()) {
+        $hubname = optional_param(clean_param($huburl, PARAM_ALPHANUMEXT), '', PARAM_TEXT);
+        $params = array('sesskey' => sesskey(), 'huburl' => $huburl,
+            'password' => $password, 'hubname' => $hubname);
+        redirect(new moodle_url($CFG->wwwroot . "/" . $CFG->admin . "/registration/register.php",
+                        $params));
+    }
+
+    echo $OUTPUT->header();
+
+    //check if the site is registered on Moodle.org and display a message about registering on MOOCH
+    $registered = $DB->count_records('registration_hubs', array('huburl' => HUB_MOODLEORGHUBURL, 'confirmed' => 1));
+    if (empty($registered)) {
+        $warningmsg = get_string('registermoochtips', 'hub');
+        $warningmsg .= $renderer->single_button(new moodle_url('register.php', array('huburl' => HUB_MOODLEORGHUBURL
+                    , 'hubname' => 'Moodle.org')), get_string('register', 'admin'));
+        echo $renderer->box($warningmsg, 'buttons mdl-align generalbox adminwarning');
+    }
+
+    //do not check sesskey if confirm = false because this script is linked into email message
+    if (!empty($errormessage)) {
+        echo $OUTPUT->notification(get_string('unregistrationerror', 'hub', $errormessage));
+    }
+
+    echo $OUTPUT->heading(get_string('registerwith', 'hub'));
+
+    $hubselectorform->display();
 
     if (extension_loaded('xmlrpc')) {
         $hubs = $registrationmanager->get_registered_on_hubs();
index eac6400..7a6bc92 100644 (file)
@@ -27,7 +27,7 @@
  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL
  * @copyright  (C) 1999 onwards Martin Dougiamas  http://dougiamas.com
  *
- * This page displays the site registration form.
+ * This page displays the site registration form for Moodle.org/MOOCH or for a different hub.
  * It handles redirection to the hub to continue the registration workflow process.
  * It also handles update operation by web service.
  */
@@ -39,15 +39,17 @@ require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/forms.php');
 require_once($CFG->dirroot . '/webservice/lib.php');
 require_once($CFG->dirroot . '/' . $CFG->admin . '/registration/lib.php');
 
-admin_externalpage_setup('registrationindex');
-
 $huburl = required_param('huburl', PARAM_URL);
 $huburl = rtrim($huburl, "/");
+
+if ($huburl == HUB_MOODLEORGHUBURL) { // register to Moodle.org
+    admin_externalpage_setup('registrationmoodleorg');
+} else { //register to a hub
+    admin_externalpage_setup('registrationhub');
+}
+
 $password = optional_param('password', '', PARAM_TEXT);
 $hubname = optional_param('hubname', '', PARAM_TEXT);
-if (!confirm_sesskey()) {
-    throw new moodle_exception('missingparameter');
-}
 
 $registrationmanager = new registration_manager();
 
@@ -145,5 +147,12 @@ if (!empty($error)) {
     echo $error;
 }
 
+//some Moodle.org resitration explanation
+if ($huburl == HUB_MOODLEORGHUBURL) {
+    echo $OUTPUT->heading(get_string('registerwithmoodleorg', 'admin'));
+    $renderer = $PAGE->get_renderer('core', 'register');
+    echo $renderer->moodleorg_registration_message();
+}
+
 $siteregistrationform->display();
 echo $OUTPUT->footer();
index fdec5fd..d5ca165 100644 (file)
  */
 class core_register_renderer extends plugin_renderer_base {
 
+    /**
+     * Display Moodle.org registration message about benefit to register on Moodle.org
+     *
+     * @return string
+     */
+    public function moodleorg_registration_message() {
+        $moodleorgurl = html_writer::link('http://moodle.org', 'Moodle.org');
+        $moodleorgstatsurl = html_writer::link('http://moodle.org/stats', get_string('statsmoodleorg', 'admin'));
+        $moochurl = html_writer::link(HUB_MOODLEORGHUBURL, 'MOOCH');
+        $moodleorgregmsg = get_string('registermoodleorg', 'admin', $moodleorgurl);
+        $items = array(get_string('registermoodleorgli1', 'admin'),
+            get_string('registermoodleorgli2', 'admin', $moodleorgstatsurl),
+            get_string('registermoodleorgli3', 'admin', $moochurl));
+        $moodleorgregmsg .= html_writer::alist($items);
+        return $moodleorgregmsg;
+    }
+
     /**
      * Display a box message confirming a site registration (add or update)
      * @param string $confirmationmessage
@@ -42,57 +59,6 @@ class core_register_renderer extends plugin_renderer_base {
         return $this->output->box($message);
     }
 
-    /**
-     * Display the page to register on Moodle.org or on a specific hub
-     */
-    public function registrationselector($updatemoodleorg = false) {
-        global $CFG;
-        $table = new html_table();
-        $table->head = array(get_string('moodleorg', 'hub'), get_string('specifichub', 'hub'));
-        $table->size = array('50%', '50%');
-        //$table->attributes['class'] = 'registerindextable';
-        //Moodle.org information cell
-        $moodleorgcell = get_string('moodleorgregistrationdetail', 'hub');
-        $moodleorgcell .= html_writer::empty_tag('br') . html_writer::empty_tag('br');
-        $moodleorgcell = html_writer::tag('div', $moodleorgcell, array('class' => 'justifytext'));
-
-        //Specific hub information cell
-        $specifichubcell = get_string('specifichubregistrationdetail', 'hub');
-        $specifichubcell .= html_writer::empty_tag('br') . html_writer::empty_tag('br');
-        $specifichubcell = html_writer::tag('div', $specifichubcell, array('class' => 'justifytext'));
-
-        //add information cells
-        $cells = array($moodleorgcell, $specifichubcell);
-        $row = new html_table_row($cells);
-        $table->data[] = $row;
-
-        //Moodle.org button cell
-        $registeronmoodleorgurl = new moodle_url("/" . $CFG->admin . "/registration/register.php",
-                        array('sesskey' => sesskey(), 'huburl' => HUB_MOODLEORGHUBURL
-                            , 'hubname' => 'Moodle.org'));
-        $registeronmoodleorgbutton = new single_button($registeronmoodleorgurl,
-                        $updatemoodleorg ? get_string('updatesite', 'hub', 'Moodle.org') : get_string('registeronmoodleorg', 'hub'));
-        $registeronmoodleorgbutton->class = 'centeredbutton';
-        $registeronmoodleorgbuttonhtml = $this->output->render($registeronmoodleorgbutton);
-        $moodleorgcell = $registeronmoodleorgbuttonhtml;
-
-        //Specific hub button cell
-        $registeronspecifichuburl = new moodle_url("/" . $CFG->admin . "/registration/hubselector.php",
-                        array('sesskey' => sesskey()));
-        $registeronspecifichubbutton = new single_button($registeronspecifichuburl,
-                        get_string('registeronspecifichub', 'hub'));
-        $registeronspecifichubbutton->class = 'centeredbutton';
-        $registeronspecifichubbuttonhtml = $this->output->render($registeronspecifichubbutton);
-        $specifichubcell = $registeronspecifichubbuttonhtml;
-
-        //add button cells
-        $cells = array($moodleorgcell, $specifichubcell);
-        $row = new html_table_row($cells);
-        $table->data[] = $row;
-
-        return html_writer::table($table);
-    }
-
     /**
      * Display the listing of registered on hub
      */
index ec9fce2..dd15463 100644 (file)
@@ -39,7 +39,7 @@ $url = optional_param('url', '', PARAM_URL);
 $hubname = optional_param('hubname', '', PARAM_TEXT);
 $token = optional_param('token', '', PARAM_TEXT);
 
-admin_externalpage_setup('registrationindex');
+admin_externalpage_setup('registrationhubs');
 
 //check that we are waiting a confirmation from this hub, and check that the token is correct
 $registrationmanager = new registration_manager();
index b7a1bab..c064ca6 100644 (file)
@@ -107,6 +107,29 @@ class core_admin_renderer extends plugin_renderer_base {
         return $output;
     }
 
+    /**
+     * Displays the list of plugins with unsatisfied dependencies
+     *
+     * @param double|string|int $version Moodle on-disk version
+     * @param array $failed list of plugins with unsatisfied dependecies
+     * @param moodle_url $reloadurl URL of the page to recheck the dependencies
+     * @return string HTML
+     */
+    public function unsatisfied_dependencies_page($version, array $failed, moodle_url $reloadurl) {
+        $output = '';
+
+        $output .= $this->header();
+        $output .= $this->heading(get_string('pluginscheck', 'admin'));
+        $output .= $this->warning(get_string('pluginscheckfailed', 'admin', array('pluginslist' => implode(', ', array_unique($failed)))));
+        $output .= $this->plugins_check_table(plugin_manager::instance(), $version, array('xdep' => true));
+        $output .= $this->warning(get_string('pluginschecktodo', 'admin'));
+        $output .= $this->continue_button($reloadurl);
+
+        $output .= $this->footer();
+
+        return $output;
+    }
+
     /**
      * Display the 'You are about to upgrade Moodle' page. The first page
      * during upgrade.
@@ -197,19 +220,15 @@ class core_admin_renderer extends plugin_renderer_base {
         $output .= $this->box_end();
         $output .= $this->upgrade_reload($reloadurl);
 
-        if ($pluginman->all_plugins_ok($version)) {
-            if ($pluginman->some_plugins_updatable()) {
-                $output .= $this->container_start('upgradepluginsinfo');
-                $output .= $this->help_icon('upgradepluginsinfo', 'core_admin', get_string('upgradepluginsfirst', 'core_admin'));
-                $output .= $this->container_end();
-            }
-            $button = new single_button($continueurl, get_string('upgradestart', 'admin'), 'get');
-            $button->class = 'continuebutton';
-            $output .= $this->render($button);
-        } else {
-            $output .= $this->box(get_string('pluginschecktodo', 'admin'), 'environmentbox errorbox');
+        if ($pluginman->some_plugins_updatable()) {
+            $output .= $this->container_start('upgradepluginsinfo');
+            $output .= $this->help_icon('upgradepluginsinfo', 'core_admin', get_string('upgradepluginsfirst', 'core_admin'));
+            $output .= $this->container_end();
         }
 
+        $button = new single_button($continueurl, get_string('upgradestart', 'admin'), 'get');
+        $button->class = 'continuebutton';
+        $output .= $this->render($button);
         $output .= $this->footer();
 
         return $output;
@@ -223,13 +242,15 @@ class core_admin_renderer extends plugin_renderer_base {
      * @param bool $cronoverdue warn cron not running
      * @param bool $dbproblems warn db has problems
      * @param bool $maintenancemode warn in maintenance mode
+     * @param bool $buggyiconvnomb warn iconv problems
      * @param array|null $availableupdates array of available_update_info objects or null
      * @param int|null $availableupdatesfetch timestamp of the most recent updates fetch or null (unknown)
      *
      * @return string HTML to output.
      */
     public function admin_notifications_page($maturity, $insecuredataroot, $errorsdisplayed,
-            $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch) {
+            $cronoverdue, $dbproblems, $maintenancemode, $availableupdates, $availableupdatesfetch,
+            $buggyiconvnomb, $registered) {
         global $CFG;
         $output = '';
 
@@ -238,9 +259,11 @@ class core_admin_renderer extends plugin_renderer_base {
         $output .= empty($CFG->disableupdatenotifications) ? $this->available_updates($availableupdates, $availableupdatesfetch) : '';
         $output .= $this->insecure_dataroot_warning($insecuredataroot);
         $output .= $this->display_errors_warning($errorsdisplayed);
+        $output .= $this->buggy_iconv_warning($buggyiconvnomb);
         $output .= $this->cron_overdue_warning($cronoverdue);
         $output .= $this->db_problems($dbproblems);
         $output .= $this->maintenance_mode_warning($maintenancemode);
+        $output .= $this->registration_warning($registered);
 
         //////////////////////////////////////////////////////////////////////////////////////////////////
         ////  IT IS ILLEGAL AND A VIOLATION OF THE GPL TO HIDE, REMOVE OR MODIFY THIS COPYRIGHT NOTICE ///
@@ -362,6 +385,19 @@ class core_admin_renderer extends plugin_renderer_base {
         return $this->warning(get_string('displayerrorswarning', 'admin'));
     }
 
+    /**
+     * Render an appropriate message if iconv is buggy and mbstring missing.
+     * @param bool $buggyiconvnomb
+     * @return string HTML to output.
+     */
+    protected function buggy_iconv_warning($buggyiconvnomb) {
+        if (!$buggyiconvnomb) {
+            return '';
+        }
+
+        return $this->warning(get_string('warningiconvbuggy', 'admin'));
+    }
+
     /**
      * Render an appropriate message if cron has not been run recently.
      * @param bool $cronoverdue
@@ -493,6 +529,27 @@ class core_admin_renderer extends plugin_renderer_base {
         return $updateinfo;
     }
 
+    /**
+     * Display a warning about not being registered on Moodle.org if necesary.
+     *
+     * @param boolean $registered true if the site is registered on Moodle.org
+     * @return string HTML to output.
+     */
+    protected function registration_warning($registered) {
+
+        if (!$registered) {
+
+            $registerbutton = $this->single_button(new moodle_url('registration/register.php',
+                    array('huburl' =>  HUB_MOODLEORGHUBURL, 'hubname' => 'Moodle.org')),
+                    get_string('register', 'admin'));
+
+            return $this->warning( get_string('registrationwarning', 'admin')
+                    . '&nbsp;' . $this->help_icon('registration', 'admin') . $registerbutton );
+        }
+
+        return '';
+    }
+
     /**
      * Helper method to render the information about the available Moodle update
      *
@@ -563,13 +620,14 @@ class core_admin_renderer extends plugin_renderer_base {
      * This default implementation renders all plugins into one big table. The rendering
      * options support:
      *     (bool)full = false: whether to display up-to-date plugins, too
+     *     (bool)xdep = false: display the plugins with unsatisified dependecies only
      *
      * @param plugin_manager $pluginman provides information about the plugins.
      * @param int $version the version of the Moodle code from version.php.
      * @param array $options rendering options
      * @return string HTML code
      */
-    public function plugins_check_table(plugin_manager $pluginman, $version, array $options = null) {
+    public function plugins_check_table(plugin_manager $pluginman, $version, array $options = array()) {
         global $CFG;
 
         $plugininfo = $pluginman->get_plugins();
@@ -578,11 +636,8 @@ class core_admin_renderer extends plugin_renderer_base {
             return '';
         }
 
-        if (empty($options)) {
-            $options = array(
-                'full' => false,
-            );
-        }
+        $options['full'] = isset($options['full']) ? (bool)$options['full'] : false;
+        $options['xdep'] = isset($options['xdep']) ? (bool)$options['xdep'] : false;
 
         $table = new html_table();
         $table->id = 'plugins-check';
@@ -666,16 +721,28 @@ class core_admin_renderer extends plugin_renderer_base {
 
                 $statusisboring = in_array($statuscode, array(
                         plugin_manager::PLUGIN_STATUS_NODB, plugin_manager::PLUGIN_STATUS_UPTODATE));
-                $dependenciesok = $pluginman->are_dependencies_satisfied(
-                        $plugin->get_other_required_plugins());
-                if ($isstandard and $statusisboring and $dependenciesok and empty($availableupdates)) {
+
+                $coredependency = $plugin->is_core_dependency_satisfied($version);
+                $otherpluginsdependencies = $pluginman->are_dependencies_satisfied($plugin->get_other_required_plugins());
+                $dependenciesok = $coredependency && $otherpluginsdependencies;
+
+                if ($options['xdep']) {
+                    // we want to see only plugins with failed dependencies
+                    if ($dependenciesok) {
+                        continue;
+                    }
+
+                } else if ($isstandard and $statusisboring and $dependenciesok and empty($availableupdates)) {
+                    // no change is going to happen to the plugin - display it only
+                    // if the user wants to see the full list
                     if (empty($options['full'])) {
                         continue;
                     }
-                } else {
-                    $numofhighlighted[$type]++;
                 }
 
+                // ok, the plugin should be displayed
+                $numofhighlighted[$type]++;
+
                 $row->cells = array($displayname, $rootdir, $source,
                     $versiondb, $versiondisk, $requires, $status);
                 $plugintyperows[] = $row;
@@ -691,7 +758,11 @@ class core_admin_renderer extends plugin_renderer_base {
 
         $sumofhighlighted = array_sum($numofhighlighted);
 
-        if ($sumofhighlighted == 0) {
+        if ($options['xdep']) {
+            // we do not want to display no heading and links in this mode
+            $out = '';
+
+        } else if ($sumofhighlighted == 0) {
             $out  = $this->output->container_start('nonehighlighted', 'plugins-check-info');
             $out .= $this->output->heading(get_string('nonehighlighted', 'core_plugin'));
             if (empty($options['full'])) {
index 0188b6d..461f06c 100644 (file)
@@ -219,7 +219,8 @@ $temp->add(new admin_setting_configselect('memcachedpconn', new lang_string('mem
 $ADMIN->add('server', $temp);
 
 
-$ADMIN->add('server', new admin_externalpage('adminregistration', new lang_string('registration','admin'), "$CFG->wwwroot/$CFG->admin/registration/index.php"));
+$ADMIN->add('server', new admin_externalpage('adminregistration', new lang_string('hubs', 'admin'),
+    "$CFG->wwwroot/$CFG->admin/registration/index.php"));
 
 // "update notifications" settingpage
 if (empty($CFG->disableupdatenotifications)) {
index 4c1295e..147a4e5 100644 (file)
@@ -10,12 +10,12 @@ $hassiteconfig = has_capability('moodle/site:config', $systemcontext);
 
 $ADMIN->add('root', new admin_externalpage('adminnotifications', new lang_string('notifications'), "$CFG->wwwroot/$CFG->admin/index.php"));
 
-$ADMIN->add('root', new admin_externalpage('registrationindex', new lang_string('registration','admin'),
-        "$CFG->wwwroot/$CFG->admin/registration/index.php"));
-$ADMIN->add('root', new admin_externalpage('registration', new lang_string('registeron','hub'),
+$ADMIN->add('root', new admin_externalpage('registrationmoodleorg', new lang_string('registration', 'admin'),
+        "$CFG->wwwroot/$CFG->admin/registration/register.php?huburl=" . HUB_MOODLEORGHUBURL . "&hubname=Moodle.org"));
+$ADMIN->add('root', new admin_externalpage('registrationhub', new lang_string('registerwith', 'hub'),
         "$CFG->wwwroot/$CFG->admin/registration/register.php", 'moodle/site:config', true));
-$ADMIN->add('root', new admin_externalpage('registrationselector', new lang_string('registeron','hub'),
-        "$CFG->wwwroot/$CFG->admin/registration/hubselector.php", 'moodle/site:config', true));
+$ADMIN->add('root', new admin_externalpage('registrationhubs', new lang_string('hubs', 'admin'),
+        "$CFG->wwwroot/$CFG->admin/registration/index.php", 'moodle/site:config', true));
 $ADMIN->add('root', new admin_externalpage('siteregistrationconfirmed',
         new lang_string('registrationconfirmed', 'hub'),
         $CFG->wwwroot."/".$CFG->admin."/registration/confirmregistration.php", 'moodle/site:config', true));
index 9c87eec..bd2a531 100644 (file)
@@ -121,7 +121,7 @@ function bloglevelupgrade_entries($blogentries, $forum, $cm, $groupid=-1) {
         $discussion->groupid = $groupid;
         $message = '';
 
-        $discussionid = forum_add_discussion($discussion, null, $message);
+        $discussionid = forum_add_discussion($discussion, null, $message, $blogentry->userid);
 
         // Copy file attachment records
         $fs = get_file_storage();
index 7bc8f90..a1e4a1c 100644 (file)
@@ -30,6 +30,7 @@
 
 /// If we can find the Shibboleth attribute, save it in session and return to main login page
     if (!empty($_SERVER[$pluginconfig->user_attribute])) {    // Shibboleth auto-login
+        $frm = new stdClass();
         $frm->username = strtolower($_SERVER[$pluginconfig->user_attribute]);
         $frm->password = substr(base64_encode($_SERVER[$pluginconfig->user_attribute]),0,8);
         // The random password consists of the first 8 letters of the base 64 encoded user ID
index d1be602..a2fa948 100644 (file)
@@ -1414,7 +1414,7 @@ class backup_final_files_structure_step extends backup_structure_step {
 
         $file->set_source_sql("SELECT f.*, r.repositoryid, r.reference
                                  FROM {files} f
-                                 JOIN {files_reference} r
+                                 LEFT JOIN {files_reference} r
                                       ON r.id = f.referencefileid
                                  JOIN {backup_ids_temp} bi
                                       ON f.id = bi.itemid
index a3770d0..25c8503 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+// Cache for storing link encoders, so that we don't need to call
+// register_link_encoders each time backup_xml_transformer is constructed
+// TODO MDL-25290 replace global with MUC code.
+global $LINKS_ENCODERS_CACHE;
+
+$LINKS_ENCODERS_CACHE = array();
+
 /**
  * Class implementing the @xml_contenttrasnformed logic to be applied in moodle2 backups
  *
@@ -131,7 +138,19 @@ class backup_xml_transformer extends xml_contenttransformer {
         return $result;
     }
 
+    /**
+     * Register all available content link encoders
+     *
+     * @return array encoder
+     * @todo MDL-25290 replace LINKS_ENCODERS_CACHE global with MUC code
+     */
     private function register_link_encoders() {
+        global $LINKS_ENCODERS_CACHE;
+        // If encoder is linked, then return cached encoder.
+        if (!empty($LINKS_ENCODERS_CACHE)) {
+            return $LINKS_ENCODERS_CACHE;
+        }
+
         $encoders = array();
 
         // Add the course encoder
@@ -160,6 +179,7 @@ class backup_xml_transformer extends xml_contenttransformer {
         // Add local encodes
         // TODO: Any interest? 1.9 never had that.
 
+        $LINKS_ENCODERS_CACHE = $encoders;
         return $encoders;
     }
 }
index e8831d7..377011e 100644 (file)
@@ -135,6 +135,7 @@ class restore_final_task extends restore_task {
         $rules[] = new restore_log_rule('course', 'report outline', 'report/outline/index.php?id={course}', '{course}');
         $rules[] = new restore_log_rule('course', 'report participation', 'report/participation/index.php?id={course}', '{course}');
         $rules[] = new restore_log_rule('course', 'report stats', 'report/stats/index.php?id={course}', '{course}');
+        $rules[] = new restore_log_rule('course', 'view section', 'view.php?id={course}&section={course_sectionnumber}', '{course_section}');
 
         // module 'user' rules
         $rules[] = new restore_log_rule('user', 'view', 'view.php?id={user}&course={course}', '{user}');
index 4d610e5..e6fc1d1 100644 (file)
@@ -212,15 +212,18 @@ class restore_gradebook_structure_step extends restore_structure_step {
         $data->itemid = $this->get_new_parentid('grade_item');
 
         $data->userid = $this->get_mappingid('user', $data->userid, NULL);
-        $data->usermodified = $this->get_mappingid('user', $data->usermodified, NULL);
-        $data->locktime     = $this->apply_date_offset($data->locktime);
-        // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
-        $data->overridden = $this->apply_date_offset($data->overridden);
-        $data->timecreated  = $this->apply_date_offset($data->timecreated);
-        $data->timemodified = $this->apply_date_offset($data->timemodified);
-
-        $newitemid = $DB->insert_record('grade_grades', $data);
-        //$this->set_mapping('grade_grade', $oldid, $newitemid);
+        if (!is_null($data->userid)) {
+            $data->usermodified = $this->get_mappingid('user', $data->usermodified, NULL);
+            $data->locktime     = $this->apply_date_offset($data->locktime);
+            // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
+            $data->overridden = $this->apply_date_offset($data->overridden);
+            $data->timecreated  = $this->apply_date_offset($data->timecreated);
+            $data->timemodified = $this->apply_date_offset($data->timemodified);
+
+            $newitemid = $DB->insert_record('grade_grades', $data);
+        } else {
+            debugging("Mapped user id not found for grade item id '{$data->itemid}'");
+        }
     }
     protected function process_grade_category($data) {
         global $DB;
@@ -1047,6 +1050,7 @@ class restore_section_structure_step extends restore_structure_step {
         global $CFG, $DB;
         $data = (object)$data;
         $oldid = $data->id; // We'll need this later
+        $oldsection = $data->number;
 
         $restorefiles = false;
 
@@ -1099,10 +1103,12 @@ class restore_section_structure_step extends restore_structure_step {
 
             $DB->update_record('course_sections', $section);
             $newitemid = $secrec->id;
+            $oldsection = $secrec->section;
         }
 
         // Annotate the section mapping, with restorefiles option if needed
         $this->set_mapping('course_section', $oldid, $newitemid, $restorefiles);
+        $this->set_mapping('course_sectionnumber', $oldsection, $section->section);
 
         // set the new course_section id in the task
         $this->task->set_sectionid($newitemid);
@@ -1123,10 +1129,19 @@ class restore_section_structure_step extends restore_structure_step {
     public function process_availability($data) {
         global $DB;
         $data = (object)$data;
+
         $data->coursesectionid = $this->task->get_sectionid();
+
         // NOTE: Other values in $data need updating, but these (cm,
-        // grade items) have not yet been restored.
-        $DB->insert_record('course_sections_availability', $data);
+        // grade items) have not yet been restored, so are done later.
+
+        $newid = $DB->insert_record('course_sections_availability', $data);
+
+        // We do not need to map between old and new id but storing a mapping
+        // means it gets added to the backup_ids table to record which ones
+        // need updating. The mapping is stored with $newid => $newid for
+        // convenience.
+        $this->set_mapping('course_sections_availability', $newid, $newid);
     }
 
     protected function after_execute() {
@@ -1139,16 +1154,20 @@ class restore_section_structure_step extends restore_structure_step {
 
         $sectionid = $this->get_task()->get_sectionid();
 
-        // Get data object for current section availability (if any)
-        // TODO: This can be processing already existing records, we need to be able to know which ones
-        //       are the just restored ones, perhaps creating 'course_sections_availability' mappings for them.
-        // TODO: Also, this must avoid duplicates, so if one course module or one grade item already is being
-        //       used for some availability rule... we need to handle that carefully.
+        // Get data object for current section availability (if any).
         $data = $DB->get_record('course_sections_availability',
                 array('coursesectionid' => $sectionid), 'id, sourcecmid, gradeitemid', IGNORE_MISSING);
 
-        // Update mappings
+        // If it exists, update mappings.
         if ($data) {
+            // Only update mappings for entries which are created by this restore.
+            // Otherwise, when you restore to an existing course, it will mess up
+            // existing section availability entries.
+            if (!$this->get_mappingid('course_sections_availability', $data->id, false)) {
+                return;
+            }
+
+            // Update source cmid / grade id to new value.
             $data->sourcecmid = $this->get_mappingid('course_module', $data->sourcecmid);
             if (!$data->sourcecmid) {
                 $data->sourcecmid = null;
@@ -2541,7 +2560,7 @@ class restore_module_structure_step extends restore_structure_step {
 
         $data = (object)$data;
         $oldid = $data->id;
-
+        $oldsection = $data->sectionnumber;
         $this->task->set_old_moduleversion($data->version);
 
         $data->course = $this->task->get_courseid();
@@ -2568,6 +2587,7 @@ class restore_module_structure_step extends restore_structure_step {
                 'course' => $this->get_courseid(),
                 'section' => 1);
             $data->section = $DB->insert_record('course_sections', $sectionrec); // section 1
+            $this->set_mapping('course_sectionnumber', $oldsection, 1); // Assign unmatching sections to section 1.
         }
         $data->groupingid= $this->get_mappingid('grouping', $data->groupingid);      // grouping
         if (!$CFG->enablegroupmembersonly) {                                         // observe groupsmemberonly
index ee98987..68c1752 100644 (file)
@@ -420,7 +420,7 @@ abstract class backup_controller_dbops extends backup_dbops {
 
         $sql = "SELECT count(r.repositoryid)
                   FROM {files} f
-                  JOIN {files_reference} r
+                  LEFT JOIN {files_reference} r
                        ON r.id = f.referencefileid
                   JOIN {backup_ids_temp} bi
                        ON f.id = bi.itemid
index e50c6e6..81dbaf6 100644 (file)
  * TODO: Finish phpdocs
  */
 abstract class restore_dbops {
+    /**
+     * Keep cache of backup records.
+     * @var array
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    private static $backupidscache = array();
+    /**
+     * Keep track of backup ids which are cached.
+     * @var array
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    private static $backupidsexist = array();
+    /**
+     * Count is expensive, so manually keeping track of
+     * backupidscache, to avoid memory issues.
+     * @var int
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    private static $backupidscachesize = 2048;
+    /**
+     * Count is expensive, so manually keeping track of
+     * backupidsexist, to avoid memory issues.
+     * @var int
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    private static $backupidsexistsize = 10240;
+    /**
+     * Slice backupids cache to add more data.
+     * @var int
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    private static $backupidsslice = 512;
 
     /**
      * Return one array containing all the tasks that have been included
@@ -151,6 +183,135 @@ abstract class restore_dbops {
         return $problems;
     }
 
+    /**
+     * Return cached backup id's
+     *
+     * @param int $restoreid id of backup
+     * @param string $itemname name of the item
+     * @param int $itemid id of item
+     * @return array backup id's
+     * @todo MDL-25290 replace static backupids* with MUC code
+     */
+    protected static function get_backup_ids_cached($restoreid, $itemname, $itemid) {
+        global $DB;
+
+        $key = "$itemid $itemname $restoreid";
+
+        // If record exists in cache then return.
+        if (isset(self::$backupidsexist[$key]) && isset(self::$backupidscache[$key])) {
+            // Return a copy of cached data, to avoid any alterations in cached data.
+            return clone self::$backupidscache[$key];
+        }
+
+        // Clean cache, if it's full.
+        if (self::$backupidscachesize <= 0) {
+            // Remove some records, to keep memory in limit.
+            self::$backupidscache = array_slice(self::$backupidscache, self::$backupidsslice, null, true);
+            self::$backupidscachesize = self::$backupidscachesize + self::$backupidsslice;
+        }
+        if (self::$backupidsexistsize <= 0) {
+            self::$backupidsexist = array_slice(self::$backupidsexist, self::$backupidsslice, null, true);
+            self::$backupidsexistsize = self::$backupidsexistsize + self::$backupidsslice;
+        }
+
+        // Retrive record from database.
+        $record = array(
+            'backupid' => $restoreid,
+            'itemname' => $itemname,
+            'itemid'   => $itemid
+        );
+        if ($dbrec = $DB->get_record('backup_ids_temp', $record)) {
+            self::$backupidsexist[$key] = $dbrec->id;
+            self::$backupidscache[$key] = $dbrec;
+            self::$backupidscachesize--;
+            self::$backupidsexistsize--;
+            return $dbrec;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Cache backup ids'
+     *
+     * @param int $restoreid id of backup
+     * @param string $itemname name of the item
+     * @param int $itemid id of item
+     * @param array $extrarecord extra record which needs to be updated
+     * @return void
+     * @todo MDL-25290 replace static BACKUP_IDS_* with MUC code
+     */
+    protected static function set_backup_ids_cached($restoreid, $itemname, $itemid, $extrarecord) {
+        global $DB;
+
+        $key = "$itemid $itemname $restoreid";
+
+        $record = array(
+            'backupid' => $restoreid,
+            'itemname' => $itemname,
+            'itemid'   => $itemid,
+        );
+
+        // If record is not cached then add one.
+        if (!isset(self::$backupidsexist[$key])) {
+            // If we have this record in db, then just update this.
+            if ($existingrecord = $DB->get_record('backup_ids_temp', $record)) {
+                self::$backupidsexist[$key] = $existingrecord->id;
+                self::$backupidsexistsize--;
+                self::update_backup_cached_record($record, $extrarecord, $key, $existingrecord);
+            } else {
+                // Add new record to cache and db.
+                $recorddefault = array (
+                    'newitemid' => 0,
+                    'parentitemid' => null,
+                    'info' => null);
+                $record = array_merge($record, $recorddefault, $extrarecord);
+                $record['id'] = $DB->insert_record('backup_ids_temp', $record);
+                self::$backupidsexist[$key] = $record['id'];
+                self::$backupidsexistsize--;
+                if (self::$backupidscachesize > 0) {
+                    // Cache new records if we haven't got many yet.
+                    self::$backupidscache[$key] = (object) $record;
+                    self::$backupidscachesize--;
+                }
+            }
+        } else {
+            self::update_backup_cached_record($record, $extrarecord, $key);
+        }
+    }
+
+    /**
+     * Updates existing backup record
+     *
+     * @param array $record record which needs to be updated
+     * @param array $extrarecord extra record which needs to be updated
+     * @param string $key unique key which is used to identify cached record
+     * @param stdClass $existingrecord (optional) existing record
+     */
+    protected static function update_backup_cached_record($record, $extrarecord, $key, $existingrecord = null) {
+        global $DB;
+        // Update only if extrarecord is not empty.
+        if (!empty($extrarecord)) {
+            $extrarecord['id'] = self::$backupidsexist[$key];
+            $DB->update_record('backup_ids_temp', $extrarecord);
+            // Update existing cache or add new record to cache.
+            if (isset(self::$backupidscache[$key])) {
+                $record = array_merge((array)self::$backupidscache[$key], $extrarecord);
+                self::$backupidscache[$key] = (object) $record;
+            } else if (self::$backupidscachesize > 0) {
+                if ($existingrecord) {
+                    self::$backupidscache[$key] = $existingrecord;
+                } else {
+                    // Retrive record from database and cache updated records.
+                    self::$backupidscache[$key] = $DB->get_record('backup_ids_temp', $record);
+                }
+                $record = array_merge((array)self::$backupidscache[$key], $extrarecord);
+                self::$backupidscache[$key] = (object) $record;
+                self::$backupidscachesize--;
+            }
+        }
+    }
+
     /**
      * Given one role, as loaded from XML, perform the best possible matching against the assignable
      * roles, using different fallback alternatives (shortname, archetype, editingteacher => teacher, defaultcourseroleid)
@@ -1219,16 +1380,7 @@ abstract class restore_dbops {
         $DB->insert_record('backup_files_temp', $filerec);
     }
 
-
     public static function set_backup_ids_record($restoreid, $itemname, $itemid, $newitemid = 0, $parentitemid = null, $info = null) {
-        global $DB;
-
-        // Build the basic (mandatory) record info
-        $record = array(
-            'backupid' => $restoreid,
-            'itemname' => $itemname,
-            'itemid'   => $itemid
-        );
         // Build conditionally the extra record info
         $extrarecord = array();
         if ($newitemid != 0) {
@@ -1241,34 +1393,16 @@ abstract class restore_dbops {
             $extrarecord['info'] = base64_encode(serialize($info));
         }
 
-        // TODO: Analyze if some static (and limited) cache by the 3 params could save us a bunch of get_record() calls
-        // Note: Sure it will! And also will improve getter
-        if (!$dbrec = $DB->get_record('backup_ids_temp', $record)) { // Need to insert the complete record
-            $DB->insert_record('backup_ids_temp', array_merge($record, $extrarecord));
-
-        } else { // Need to update the extra record info if there is something to
-            if (!empty($extrarecord)) {
-                $extrarecord['id'] = $dbrec->id;
-                $DB->update_record('backup_ids_temp', $extrarecord);
-            }
-        }
+        self::set_backup_ids_cached($restoreid, $itemname, $itemid, $extrarecord);
     }
 
     public static function get_backup_ids_record($restoreid, $itemname, $itemid) {
-        global $DB;
+        $dbrec = self::get_backup_ids_cached($restoreid, $itemname, $itemid);
 
-        // Build the basic (mandatory) record info to look for
-        $record = array(
-            'backupid' => $restoreid,
-            'itemname' => $itemname,
-            'itemid'   => $itemid
-        );
-        // TODO: Analyze if some static (and limited) cache by the 3 params could save us a bunch of get_record() calls
-        if ($dbrec = $DB->get_record('backup_ids_temp', $record)) {
-            if ($dbrec->info != null) {
-                $dbrec->info = unserialize(base64_decode($dbrec->info));
-            }
+        if ($dbrec && isset($dbrec->info) && is_string($dbrec->info)) {
+            $dbrec->info = unserialize(base64_decode($dbrec->info));
         }
+
         return $dbrec;
     }
 
index d25c794..94a7a87 100644 (file)
@@ -315,6 +315,7 @@ abstract class backup_cron_automated_helper {
      */
     public static function launch_automated_backup($course, $starttime, $userid) {
 
+        $outcome = true;
         $config = get_config('backup');
         $bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_AUTOMATED, $userid);
 
@@ -347,7 +348,7 @@ abstract class backup_cron_automated_helper {
 
             $bc->set_status(backup::STATUS_AWAITING);
 
-            $outcome = $bc->execute_plan();
+            $bc->execute_plan();
             $results = $bc->get_results();
             $file = $results['backup_destination'];
             $dir = $config->backup_auto_destination;
@@ -363,16 +364,17 @@ abstract class backup_cron_automated_helper {
                 }
             }
 
-            $outcome = true;
-        } catch (backup_exception $e) {
-            $bc->log('backup_auto_failed_on_course', backup::LOG_WARNING, $course->shortname);
+        } catch (moodle_exception $e) {
+            $bc->log('backup_auto_failed_on_course', backup::LOG_ERROR, $course->shortname); // Log error header.
+            $bc->log('Exception: ' . $e->errorcode, backup::LOG_ERROR, $e->a, 1); // Log original exception problem.
+            $bc->log('Debug: ' . $e->debuginfo, backup::LOG_DEBUG, null, 1); // Log original debug information.
             $outcome = false;
         }
 
         $bc->destroy();
         unset($bc);
 
-        return true;
+        return $outcome;
     }
 
     /**
index 3ed176b..eab1869 100644 (file)
@@ -61,7 +61,12 @@ abstract class base_plan implements checksumable, executable {
         // Append task settings to plan array, if not present, for comodity
         foreach ($task->get_settings() as $key => $setting) {
             if (!in_array($setting, $this->settings)) {
-                $this->settings[] = $setting;
+                $name = $setting->get_name();
+                if (!isset($this->settings[$name])) {
+                    $this->settings[$name] = $setting;
+                } else {
+                    throw new base_plan_exception('multiple_settings_by_name_found', $name);
+                }
             }
         }
     }
@@ -84,23 +89,16 @@ abstract class base_plan implements checksumable, executable {
 
     /**
      * return one setting by name, useful to request root/course settings
-     * that are, by definition, unique by name. Throws exception if multiple
-     * are found
+     * that are, by definition, unique by name.
      *
-     * TODO: Change this to string indexed array for quicker lookup. Not critical
+     * @param string $name name of the setting
+     * @throws base_plan_exception if setting name is not found.
      */
     public function get_setting($name) {
         $result = null;
-        foreach ($this->settings as $key => $setting) {
-            if ($setting->get_name() == $name) {
-                if ($result != null) {
-                    throw new base_plan_exception('multiple_settings_by_name_found', $name);
-                } else {
-                    $result = $setting;
-                }
-            }
-        }
-        if (!$result) {
+        if (isset($this->settings[$name])) {
+            $result = $this->settings[$name];
+        } else {
             throw new base_plan_exception('setting_by_name_not_found', $name);
         }
         return $result;
index 1ce9053..7995561 100644 (file)
@@ -124,6 +124,8 @@ class backup_ui_stage_initial extends backup_ui_stage {
             // Store as a variable so we can iterate by reference
             $tasks = $this->ui->get_tasks();
             // Iterate all tasks by reference
+            $add_settings = array();
+            $dependencies = array();
             foreach ($tasks as &$task) {
                 // For the initial stage we are only interested in the root settings
                 if ($task instanceof backup_root_task) {
@@ -134,17 +136,23 @@ class backup_ui_stage_initial extends backup_ui_stage {
                         if ($setting->get_name() == 'filename') {
                             continue;
                         }
-                        $form->add_setting($setting, $task);
+                        $add_settings[] = array($setting, $task);
                     }
                     // Then add all dependencies
                     foreach ($settings as &$setting) {
                         if ($setting->get_name() == 'filename') {
                             continue;
                         }
-                        $form->add_dependencies($setting);
+                        $dependencies[] = $setting;
                     }
                 }
             }
+            // Add all settings at once.
+            $form->add_settings($add_settings);
+            // Add dependencies.
+            foreach ($dependencies as $depsetting) {
+                $form->add_dependencies($depsetting);
+            }
             $this->stageform = $form;
         }
         // Return the form
@@ -226,6 +234,8 @@ class backup_ui_stage_schema extends backup_ui_stage {
             $tasks = $this->ui->get_tasks();
             $content = '';
             $courseheading = false;
+            $add_settings = array();
+            $dependencies = array();
             foreach ($tasks as $task) {
                 if (!($task instanceof backup_root_task)) {
                     if (!$courseheading) {
@@ -235,11 +245,11 @@ class backup_ui_stage_schema extends backup_ui_stage {
                     }
                     // First add each setting
                     foreach ($task->get_settings() as $setting) {
-                        $form->add_setting($setting, $task);
+                        $add_settings[] = array($setting, $task);
                     }
                     // The add all the dependencies
                     foreach ($task->get_settings() as $setting) {
-                        $form->add_dependencies($setting);
+                        $dependencies[] = $setting;
                     }
                 } else if ($this->ui->enforce_changed_dependencies()) {
                     // Only show these settings if dependencies changed them.
@@ -254,6 +264,10 @@ class backup_ui_stage_schema extends backup_ui_stage {
                     }
                 }
             }
+            $form->add_settings($add_settings);
+            foreach ($dependencies as $depsetting) {
+                $form->add_dependencies($depsetting);
+            }
             $this->stageform = $form;
         }
         return $this->stageform;
index ad99260..e0ffc0f 100644 (file)
@@ -136,24 +136,38 @@ abstract class base_moodleform extends moodleform {
      * @return bool
      */
     function add_setting(backup_setting $setting, base_task $task=null) {
+        return $this->add_settings(array(array($setting, $task)));
+    }
+    /**
+     * Adds multiple backup_settings as elements to the form
+     * @param array $settingstasks Consists of array($setting, $task) elements
+     * @return bool
+     */
+    public function add_settings(array $settingstasks) {
         global $OUTPUT;
 
-        // If the setting cant be changed or isn't visible then add it as a fixed setting.
-        if (!$setting->get_ui()->is_changeable() || $setting->get_visibility() != backup_setting::VISIBLE) {
-            return $this->add_fixed_setting($setting, $task);
-        }
+        $defaults = array();
+        foreach ($settingstasks as $st) {
+            list($setting, $task) = $st;
+            // If the setting cant be changed or isn't visible then add it as a fixed setting.
+            if (!$setting->get_ui()->is_changeable() || $setting->get_visibility() != backup_setting::VISIBLE) {
+                $this->add_fixed_setting($setting, $task);
+                continue;
+            }
 
-        // First add the formatting for this setting
-        $this->add_html_formatting($setting);
+            // First add the formatting for this setting
+            $this->add_html_formatting($setting);
 
-        // The call the add method with the get_element_properties array
-        call_user_func_array(array($this->_form, 'addElement'), $setting->get_ui()->get_element_properties($task, $OUTPUT));
-        $this->_form->setDefault($setting->get_ui_name(), $setting->get_value());
-        if ($setting->has_help()) {
-            list($identifier, $component) = $setting->get_help();
-            $this->_form->addHelpButton($setting->get_ui_name(), $identifier, $component);
+            // Then call the add method with the get_element_properties array
+            call_user_func_array(array($this->_form, 'addElement'), $setting->get_ui()->get_element_properties($task, $OUTPUT));
+            $defaults[$setting->get_ui_name()] = $setting->get_value();
+            if ($setting->has_help()) {
+                list($identifier, $component) = $setting->get_help();
+                $this->_form->addHelpButton($setting->get_ui_name(), $identifier, $component);
+            }
+            $this->_form->addElement('html', html_writer::end_tag('div'));
         }
-        $this->_form->addElement('html', html_writer::end_tag('div'));
+        $this->_form->setDefaults($defaults);
         return true;
     }
     /**
@@ -317,4 +331,4 @@ abstract class base_moodleform extends moodleform {
             $this->definition_after_data();
         }
     }
-}
\ No newline at end of file
+}
index 184449a..8152990 100644 (file)
@@ -242,7 +242,6 @@ function calendar_get_mini($courses, $groups, $users, $cal_month = false, $cal_y
     $days_title = calendar_get_days();
 
     $summary = get_string('calendarheading', 'calendar', userdate(make_timestamp($y, $m), get_string('strftimemonthyear')));
-    $summary = get_string('tabledata', 'access', $summary);
     $content .= '<table class="minicalendar calendartable" summary="'.$summary.'">'; // Begin table
     $content .= '<tr class="weekdays">'; // Header row: day names
 
index f2af45a..3354f1c 100644 (file)
@@ -53,5 +53,7 @@ if ($course->numsections >= 0) {
     $DB->update_record('course', $course);
 }
 
+$url = course_get_url($course);
+$url->set_anchor('changenumsections');
 // Redirect to where we were..
-redirect(course_get_url($course));
+redirect($url);
index d02506f..1dfc4c6 100644 (file)
@@ -119,9 +119,10 @@ class core_course_external extends external_api {
                 $sectionvalues = array();
                 $sectionvalues['id'] = $section->id;
                 $sectionvalues['name'] = get_section_name($course, $section);
-                $summary = file_rewrite_pluginfile_urls($section->summary, 'webservice/pluginfile.php', $context->id, 'course', 'section', $section->id);
                 $sectionvalues['visible'] = $section->visible;
-                $sectionvalues['summary'] = format_text($summary, $section->summaryformat);
+                list($sectionvalues['summary'], $sectionvalues['summaryformat']) =
+                        external_format_text($section->summary, $section->summaryformat,
+                                $context->id, 'course', 'section', $section->id);
                 $sectioncontents = array();
 
                 //for each module of the section
@@ -205,6 +206,7 @@ class core_course_external extends external_api {
                     'name' => new external_value(PARAM_TEXT, 'Section name'),
                     'visible' => new external_value(PARAM_INT, 'is the section visible', VALUE_OPTIONAL),
                     'summary' => new external_value(PARAM_RAW, 'Section description'),
+                    'summaryformat' => new external_format_value('summary'),
                     'modules' => new external_multiple_structure(
                             new external_single_structure(
                                 array(
@@ -311,8 +313,8 @@ class core_course_external extends external_api {
             $courseinfo['fullname'] = $course->fullname;
             $courseinfo['shortname'] = $course->shortname;
             $courseinfo['categoryid'] = $course->category;
-            $courseinfo['summary'] = $course->summary;
-            $courseinfo['summaryformat'] = $course->summaryformat;
+            list($courseinfo['summary'], $courseinfo['summaryformat']) =
+                external_format_text($course->summary, $course->summaryformat, $context->id, 'course', 'summary', 0);
             $courseinfo['format'] = $course->format;
             $courseinfo['startdate'] = $course->startdate;
             $courseinfo['numsections'] = $course->numsections;
@@ -367,8 +369,7 @@ class core_course_external extends external_api {
                             'fullname' => new external_value(PARAM_TEXT, 'full name'),
                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
                             'summary' => new external_value(PARAM_RAW, 'summary'),
-                            'summaryformat' => new external_value(PARAM_INT,
-                                    'the summary text Moodle format'),
+                            'summaryformat' => new external_format_value('summary'),
                             'format' => new external_value(PARAM_PLUGIN,
                                     'course format: weeks, topics, social, site,..'),
                             'showgrades' => new external_value(PARAM_INT,
@@ -435,8 +436,7 @@ class core_course_external extends external_api {
                             'categoryid' => new external_value(PARAM_INT, 'category id'),
                             'idnumber' => new external_value(PARAM_RAW, 'id number', VALUE_OPTIONAL),
                             'summary' => new external_value(PARAM_RAW, 'summary', VALUE_OPTIONAL),
-                            'summaryformat' => new external_value(PARAM_INT,
-                                    'the summary text Moodle format', VALUE_DEFAULT, FORMAT_MOODLE),
+                            'summaryformat' => new external_format_value('summary', VALUE_DEFAULT),
                             'format' => new external_value(PARAM_PLUGIN,
                                     'course format: weeks, topics, social, site,..',
                                     VALUE_DEFAULT, $courseconfig->format),
@@ -560,6 +560,9 @@ class core_course_external extends external_api {
 
             $course['category'] = $course['categoryid'];
 
+            // Summary format.
+            $course['summaryformat'] = external_validate_format($course['summaryformat']);
+
             //Note: create_course() core function check shortname, idnumber, category
             $course['id'] = create_course((object) $course)->id;
 
@@ -1085,13 +1088,9 @@ class core_course_external extends external_api {
                     $categoryinfo = array();
                     $categoryinfo['id'] = $category->id;
                     $categoryinfo['name'] = $category->name;
-                    $categoryinfo['description'] = file_rewrite_pluginfile_urls($category->description,
-                            'webservice/pluginfile.php', $context->id, 'coursecat', 'description', null);
-                    $options = new stdClass;
-                    $options->noclean = true;
-                    $options->para = false;
-                    $categoryinfo['description'] = format_text($categoryinfo['description'],
-                            $category->descriptionformat, $options);
+                    list($categoryinfo['description'], $categoryinfo['descriptionformat']) =
+                        external_format_text($category->description, $category->descriptionformat,
+                                $context->id, 'coursecat', 'description', null);
                     $categoryinfo['parent'] = $category->parent;
                     $categoryinfo['sortorder'] = $category->sortorder;
                     $categoryinfo['coursecount'] = $category->coursecount;
@@ -1160,6 +1159,7 @@ class core_course_external extends external_api {
                     'name' => new external_value(PARAM_TEXT, 'category name'),
                     'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
                     'description' => new external_value(PARAM_RAW, 'category description'),
+                    'descriptionformat' => new external_format_value('description'),
                     'parent' => new external_value(PARAM_INT, 'parent category id'),
                     'sortorder' => new external_value(PARAM_INT, 'category sorting order'),
                     'coursecount' => new external_value(PARAM_INT, 'number of courses in this category'),
@@ -1193,6 +1193,7 @@ class core_course_external extends external_api {
                                         'the new category idnumber', VALUE_OPTIONAL),
                                 'description' => new external_value(PARAM_RAW,
                                         'the new category description', VALUE_OPTIONAL),
+                                'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
                                 'theme' => new external_value(PARAM_THEME,
                                         'the new category theme. This option must be enabled on moodle',
                                         VALUE_OPTIONAL),
@@ -1257,7 +1258,7 @@ class core_course_external extends external_api {
             if (!empty($category['description'])) {
                 $newcategory->description = $category['description'];
             }
-            $newcategory->descriptionformat = FORMAT_HTML;
+            $newcategory->descriptionformat = external_validate_format($category['descriptionformat']);
             if (isset($category['theme']) and !empty($CFG->allowcategorythemes)) {
                 $newcategory->theme = $category['theme'];
             }
@@ -1308,6 +1309,7 @@ class core_course_external extends external_api {
                             'idnumber' => new external_value(PARAM_RAW, 'category id number', VALUE_OPTIONAL),
                             'parent' => new external_value(PARAM_INT, 'parent category id', VALUE_OPTIONAL),
                             'description' => new external_value(PARAM_RAW, 'category description', VALUE_OPTIONAL),
+                            'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
                             'theme' => new external_value(PARAM_THEME,
                                     'the category theme. This option must be enabled on moodle', VALUE_OPTIONAL),
                         )
@@ -1356,7 +1358,7 @@ class core_course_external extends external_api {
             }
             if (!empty($cat['description'])) {
                 $category->description = $cat['description'];
-                $category->descriptionformat = FORMAT_HTML;
+                $category->descriptionformat = external_validate_format($cat['descriptionformat']);
             }
             if (!empty($cat['theme'])) {
                 $category->theme = $cat['theme'];
index fb59c38..f0dd91f 100644 (file)
@@ -56,6 +56,21 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
      */
     abstract protected function page_title();
 
+    /**
+     * Generate the section title
+     *
+     * @param stdClass $section The course_section entry from DB
+     * @param stdClass $course The course entry from DB
+     * @return string HTML to output.
+     */
+    public function section_title($section, $course) {
+        $title = get_section_name($course, $section);
+        if ($section->section != 0 && $course->coursedisplay == COURSE_DISPLAY_MULTIPAGE) {
+            $title = html_writer::link(course_get_url($course, $section->section), $title);
+        }
+        return $title;
+    }
+
     /**
      * Generate the content to displayed on the right part of a section
      * before course modules are included
@@ -106,7 +121,7 @@ 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 section page
+     * @param bool $onsectionpage true if being printed on a single-section page
      * @return string HTML to output.
      */
     protected function section_header($section, $course, $onsectionpage) {
@@ -115,7 +130,6 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o = '';
         $currenttext = '';
         $sectionstyle = '';
-        $linktitle = false;
 
         if ($section->section != 0) {
             // Only in the non-general sections.
@@ -124,7 +138,6 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
             } else if ($this->is_section_current($section, $course)) {
                 $sectionstyle = ' current';
             }
-            $linktitle = ($course->coursedisplay == COURSE_DISPLAY_MULTIPAGE);
         }
 
         $o.= html_writer::start_tag('li', array('id' => 'section-'.$section->section,
@@ -138,11 +151,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o.= html_writer::start_tag('div', array('class' => 'content'));
 
         if (!$onsectionpage) {
-            $title = get_section_name($course, $section);
-            if ($linktitle) {
-                $title = html_writer::link(course_get_url($course, $section->section), $title);
-            }
-            $o.= $this->output->heading($title, 3, 'sectionname');
+            $o.= $this->output->heading($this->section_title($section, $course), 3, 'sectionname');
         }
 
         $o.= html_writer::start_tag('div', array('class' => 'summary'));
@@ -506,7 +515,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $completioninfo = new completion_info($course);
         echo $completioninfo->display_help_icon();
 
-        print_section($course, $thissection, $mods, $modnamesused, true);
+        print_section($course, $thissection, $mods, $modnamesused, true, '100%', false, true);
         if ($PAGE->user_is_editing()) {
             print_section_add_menus($course, $displaysection, $modnames);
         }
@@ -624,7 +633,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
 
             echo $this->end_section_list();
 
-            echo html_writer::start_tag('div', array('class' => 'mdl-right'));
+            echo html_writer::start_tag('div', array('id' => 'changenumsections', 'class' => 'mdl-right'));
 
             // Increase number of sections.
             $straddsection = get_string('increasesections', 'moodle');
index 218492d..71e8797 100644 (file)
@@ -1,4 +1,4 @@
-// Javascript functions for course format
+// Javascript functions for Topics course format
 
 M.course = M.course || {};
 
@@ -36,13 +36,32 @@ M.course.format.get_config = function() {
 M.course.format.swap_sections = function(Y, node1, node2) {
     var CSS = {
         COURSECONTENT : 'course-content',
-        LEFT : 'left',
         SECTIONADDMENUS : 'section_add_menus'
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
-    // Swap left block
-    sectionlist.item(node1).one('.'+CSS.LEFT).swap(sectionlist.item(node2).one('.'+CSS.LEFT));
     // Swap menus
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
 }
+
+/**
+ * Process sections after ajax response
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.course.format.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+    var CSS = {
+        SECTIONNAME : 'sectionname'
+    };
+
+    if (response.action == 'move') {
+        // update titles in all affected sections
+        for (var i = sectionfrom; i <= sectionto; i++) {
+            sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+        }
+    }
+}
index 710abaf..e8632fc 100644 (file)
@@ -80,3 +80,24 @@ function callback_topics_ajax_support() {
     $ajaxsupport->testedbrowsers = array('MSIE' => 6.0, 'Gecko' => 20061111, 'Safari' => 531, 'Chrome' => 6.0);
     return $ajaxsupport;
 }
+
+/**
+ * Callback function to do some action after section move
+ *
+ * @param stdClass $course The course entry from DB
+ * @return array This will be passed in ajax respose.
+ */
+function callback_topics_ajax_section_move($course) {
+    global $COURSE, $PAGE;
+
+    $titles = array();
+    rebuild_course_cache($course->id);
+    $modinfo = get_fast_modinfo($COURSE);
+    $renderer = $PAGE->get_renderer('format_topics');
+    if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+        foreach ($sections as $number => $section) {
+            $titles[$number] = $renderer->section_title($section, $course);
+        }
+    }
+    return array('sectiontitles' => $titles, 'action' => 'move');
+}
index 159f94b..f410e07 100644 (file)
@@ -1,4 +1,4 @@
-// Javascript functions for course format
+// Javascript functions for Weeks course format
 
 M.course = M.course || {};
 
@@ -36,16 +36,32 @@ M.course.format.get_config = function() {
 M.course.format.swap_sections = function(Y, node1, node2) {
     var CSS = {
         COURSECONTENT : 'course-content',
-        LEFT : 'left',
         SECTIONADDMENUS : 'section_add_menus',
-        WEEKDATES: 'sectionname'
     };
 
     var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.course.format.get_section_selector(Y));
-    // Swap left block
-    sectionlist.item(node1).one('.'+CSS.LEFT).swap(sectionlist.item(node2).one('.'+CSS.LEFT));
     // Swap menus
     sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
-    // Swap week dates
-    sectionlist.item(node1).one('.'+CSS.WEEKDATES).swap(sectionlist.item(node2).one('.'+CSS.WEEKDATES));
+}
+
+/**
+ * Process sections after ajax response
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.course.format.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+    var CSS = {
+        SECTIONNAME : 'sectionname'
+    };
+
+    if (response.action == 'move') {
+        // update titles in all affected sections
+        for (var i = sectionfrom; i <= sectionto; i++) {
+            sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+        }
+    }
 }
index 834adda..a1e1ea9 100644 (file)
@@ -117,3 +117,24 @@ function format_weeks_get_section_dates($section, $course) {
 
     return $dates;
 }
+
+/**
+ * Callback function to do some action after section move
+ *
+ * @param stdClass $course The course entry from DB
+ * @return array This will be passed in ajax respose.
+ */
+function callback_weeks_ajax_section_move($course) {
+    global $COURSE, $PAGE;
+
+    $titles = array();
+    rebuild_course_cache($course->id);
+    $modinfo = get_fast_modinfo($COURSE);
+    $renderer = $PAGE->get_renderer('format_weeks');
+    if ($renderer && ($sections = $modinfo->get_section_info_all())) {
+        foreach ($sections as $number => $section) {
+            $titles[$number] = $renderer->section_title($section, $course);
+        }
+    }
+    return array('sectiontitles' => $titles, 'action' => 'move');
+}
index 8e07373..a5566d6 100644 (file)
@@ -1728,7 +1728,7 @@ function print_section($course, $section, $mods, $modnamesused, $absolute=false,
             // see the activity itself, or for staff)
             if (!$mod->uservisible) {
                 echo '<div class="availabilityinfo">'.$mod->availableinfo.'</div>';
-            } else if ($canviewhidden && !empty($CFG->enableavailability)) {
+            } else if ($canviewhidden && !empty($CFG->enableavailability) && $mod->visible) {
                 $ci = new condition_info($mod);
                 $fullinfo = $ci->get_full_information();
                 if($fullinfo) {
@@ -2995,6 +2995,8 @@ function move_section($course, $section, $move) {
         }
         $n++;
     }
+    // After moving section, rebuild course cache.
+    rebuild_course_cache($course->id, true);
     return true;
 }
 
index f33ef54..8b31e29 100644 (file)
@@ -221,7 +221,9 @@ if (!empty($activities)) {
                 echo $OUTPUT->spacer(array('height'=>30, 'br'=>true)); // should be done with CSS instead
             }
             echo $OUTPUT->box_start();
-            echo "<h2>$activity->name</h2>";
+            if (!empty($activity->name)) {
+                echo html_writer::tag('h2', $activity->name);
+            }
             $inbox = true;
 
         } else if ($activity->type == 'activity') {
@@ -230,16 +232,17 @@ if (!empty($activities)) {
                 $cm = $modinfo->cms[$activity->cmid];
 
                 if ($cm->visible) {
-                    $linkformat = '';
+                    $class = '';
                 } else {
-                    $linkformat = 'class="dimmed"';
+                    $class = 'dimmed';
                 }
                 $name        = format_string($cm->name);
                 $modfullname = $modnames[$cm->modname];
 
-                $image = "<img src=\"" . $OUTPUT->pix_url('icon', $cm->modname) . "\" class=\"icon\" alt=\"$modfullname\" />";
-                echo "<h3>$image $modfullname".
-                     " <a href=\"$CFG->wwwroot/mod/$cm->modname/view.php?id=$cm->id\" $linkformat>$name</a></h3>";
+                $image = $OUTPUT->pix_icon('icon', $modfullname, $cm->modname, array('class' => 'icon smallicon'));
+                $link = html_writer::link(new moodle_url("/mod/$cm->modname/view.php",
+                            array("id" => $cm->id)), $name, array('class' => $class));
+                echo html_writer::tag('h3', "$image $modfullname $link");
            }
 
         } else {
@@ -269,7 +272,7 @@ if (!empty($activities)) {
 
 } else {
 
-    echo '<h3><center>' . get_string('norecentactivity') . '</center></h3>';
+    echo html_writer::tag('h3', get_string('norecentactivity'), array('class' => 'mdl-align'));
 
 }
 
index 5152563..6efd228 100644 (file)
@@ -89,6 +89,15 @@ switch($requestmethod) {
 
                     case 'move':
                         move_section_to($course, $id, $value);
+                        // See if format wants to do something about it
+                        $libfile = $CFG->dirroot.'/course/format/'.$course->format.'/lib.php';
+                        $functionname = 'callback_'.$course->format.'_ajax_section_move';
+                        if (!function_exists($functionname) && file_exists($libfile)) {
+                            require_once $libfile;
+                        }
+                        if (function_exists($functionname)) {
+                            echo json_encode($functionname($course));
+                        }
                         break;
                 }
                 rebuild_course_cache($course->id);
index 2504281..0e34d0c 100644 (file)
 
     require_once($CFG->dirroot.'/calendar/lib.php');    /// This is after login because it needs $USER
 
-    //TODO: danp do we need different urls?
-    add_to_log($course->id, 'course', 'view', "view.php?id=$course->id", "$course->id");
+    $logparam = 'id='. $course->id;
+    $loglabel = 'view';
+    $infoid = $course->id;
+    if(!empty($section)) {
+        $logparam .= '&section='. $section;
+        $loglabel = 'view section';
+        $sectionparams = array('course' => $course->id, 'section' => $section);
+        if ($coursesections = $DB->get_record('course_sections', $sectionparams, 'id', MUST_EXIST)) {
+            $infoid = $coursesections->id;
+    }
+    }
+    add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
 
     $course->format = clean_param($course->format, PARAM_ALPHA);
     if (!file_exists($CFG->dirroot.'/course/format/'.$course->format.'/format.php')) {
index 3dbbf0b..b02c362 100644 (file)
@@ -68,6 +68,21 @@ YUI.add('moodle-course-coursebase', function(Y) {
         return null;
     }
 
+   /**
+    * Process sections after ajax response (should be defined in format.js)
+    * If some response is expected, we pass it over to format, as it knows better
+    * hot to process it.
+    *
+    * @param {YUI} Y YUI3 instance
+    * @param {NodeList} list of sections
+    * @param {array} response ajax response
+    * @param {string} sectionfrom first affected section
+    * @param {string} sectionto last affected section
+    * @return void
+    */
+    M.course.format.process_sections = M.course.format.process_sections || function(Y, sectionlist, response, sectionfrom, sectionto) {
+        return null;
+    }
 
    /**
     * Get sections config for this format, for examples see function definition
index 012355e..080cace 100644 (file)
@@ -119,6 +119,13 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             drag.get('dragNode').addClass(CSS.COURSECONTENT);
         },
 
+        drag_dropmiss : function(e) {
+            // Missed the target, but we assume the user intended to drop it
+            // on the last last ghost node location, e.drag and e.drop should be
+            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+            this.drop_hit(e);
+        },
+
         drop_hit : function(e) {
             var drag = e.drag;
             // Get a reference to our drag node
@@ -172,9 +179,16 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                         lightbox.show();
                     },
                     success: function(tid, response) {
-                        window.setTimeout(function(e) {
-                            lightbox.hide();
-                        }, 250);
+                        // Update section titles, we can't simply swap them as
+                        // they might have custom title
+                        try {
+                            var responsetext = Y.JSON.parse(response.responseText);
+                            if (responsetext.error) {
+                                new M.core.ajaxException(responsetext);
+                            }
+                            M.course.format.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
+                        } catch (e) {}
+
                         // Classic bubble sort algorithm is applied to the section
                         // nodes between original drag node location and the new one.
                         do {
@@ -193,6 +207,11 @@ YUI.add('moodle-course-dragdrop', function(Y) {
                             }
                             loopend = loopend - 1;
                         } while (swapped);
+
+                        // Finally, hide the lightbox
+                        window.setTimeout(function(e) {
+                            lightbox.hide();
+                        }, 250);
                     },
                     failure: function(tid, response) {
                         this.ajax_failure(response);
@@ -309,6 +328,13 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
         },
 
+        drag_dropmiss : function(e) {
+            // Missed the target, but we assume the user intended to drop it
+            // on the last last ghost node location, e.drag and e.drop should be
+            // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+            this.drop_hit(e);
+        },
+
         drop_hit : function(e) {
             var drag = e.drag;
             // Get a reference to our drag node
index 08438e1..0b10031 100644 (file)
@@ -277,7 +277,7 @@ class core_enrol_external extends external_api {
                     'firstaccess' => new external_value(PARAM_INT, 'first access to the site (0 if never)', VALUE_OPTIONAL),
                     'lastaccess'  => new external_value(PARAM_INT, 'last access to the site (0 if never)', VALUE_OPTIONAL),
                     'description' => new external_value(PARAM_RAW, 'User profile description', VALUE_OPTIONAL),
-                    'descriptionformat' => new external_value(PARAM_INT, 'User profile description format', VALUE_OPTIONAL),
+                    'descriptionformat' => new external_format_value('description', VALUE_OPTIONAL),
                     'city'        => new external_value(PARAM_NOTAGS, 'Home city of the user', VALUE_OPTIONAL),
                     'url'         => new external_value(PARAM_URL, 'URL of the user', VALUE_OPTIONAL),
                     'country'     => new external_value(PARAM_ALPHA, 'Home country code of the user, such as AU or CZ', VALUE_OPTIONAL),
@@ -298,6 +298,7 @@ class core_enrol_external extends external_api {
                                 'id'  => new external_value(PARAM_INT, 'group id'),
                                 'name' => new external_value(PARAM_RAW, 'group name'),
                                 'description' => new external_value(PARAM_RAW, 'group description'),
+                                'descriptionformat' => new external_format_value('description'),
                             )
                         ), 'user groups', VALUE_OPTIONAL),
                     'roles' => new external_multiple_structure(
index ac8e582..2f5f5d4 100644 (file)
@@ -185,7 +185,6 @@ class core_files_renderer extends plugin_renderer_base {
         $strmakedir  = get_string('makeafolder', 'moodle');
         $strdownload = get_string('downloadfolder', 'repository');
         $strloading  = get_string('loading', 'repository');
-        $strnofilesattached = get_string('nofilesattached', 'repository');
         $strdroptoupload = get_string('droptoupload', 'moodle');
         $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
         $restrictions = $this->fm_print_restrictions($fm);
@@ -220,7 +219,7 @@ class core_files_renderer extends plugin_renderer_base {
     <div class="filemanager-container" >
         <div class="fm-content-wrapper">
             <div class="fp-content"></div>
-            <div class="fm-empty-container <!--mdl-align-->">'.$strnofilesattached.'
+            <div class="fm-empty-container <!--mdl-align-->">
                 <span class="dndupload-message">'.$strdndenabledinbox.'<br/><span class="dndupload-arrow"></span></span>
             </div>
             <div class="dndupload-target">'.$strdroptoupload.'<br/><span class="dndupload-arrow"></span></div>
@@ -302,10 +301,9 @@ class core_files_renderer extends plugin_renderer_base {
      */
     private function fm_js_template_mkdir() {
         $rv = '
-<div class="fp-mkdir-dlg">
-    <p>New folder name:</p>
-    <input type="text"><br/>
-    <a class="{!}fp-dlg-butcreate fp-panel-button" href="#">'.get_string('create').'</a>
+<div class="filemanager fp-mkdir-dlg">
+    <div class="fp-mkdir-dlg-text">'.get_string('newfoldername','repository').'<br/><input type="text" /></div>
+    <a class="{!}fp-dlg-butcreate fp-panel-button" href="#">'.get_string('makeafolder').'</a>
     <a class="{!}fp-dlg-butcancel fp-panel-button" href="#">'.get_string('cancel').'</a>
 </div>';
         return preg_replace('/\{\!\}/', '', $rv);
@@ -383,15 +381,15 @@ class core_files_renderer extends plugin_renderer_base {
             <tr class="{!}fp-saveas"><td class="mdl-right"><label>'.get_string('name', 'moodle').'</label>:</td>
             <td class="mdl-left"><input type="text"/></td></tr>
             <tr class="{!}fp-author"><td class="mdl-right"><label>'.get_string('author', 'repository').'</label>:</td>
-            <td class="mdl-left"><input type="text" /></td></tr>
+            <td class="mdl-left"><input type="text"/></td></tr>
             <tr class="{!}fp-license"><td class="mdl-right"><label>'.get_string('chooselicense', 'repository').'</label>:</td>
             <td class="mdl-left"><select></select></td></tr>
             <tr class="{!}fp-path"><td class="mdl-right"><label>'.get_string('path', 'moodle').'</label>:</td>
             <td class="mdl-left"><select></select></td></tr>
             <tr class="{!}fp-original"><td class="mdl-right"><label>'.get_string('original', 'repository').'</label>:</td>
-            <td class="mdl-left"><span class="fp-originloading">'.$icon_progress.' '.$strloading.'</span><span class="fp-value"/></td></tr>
+            <td class="mdl-left"><span class="fp-originloading">'.$icon_progress.' '.$strloading.'</span><span class="fp-value"></span></td></tr>
             <tr class="{!}fp-reflist"><td class="mdl-right"><label>'.get_string('referenceslist', 'repository').'</label>:</td>
-            <td class="mdl-left"><p class="{!}fp-refcount"/><span class="fp-reflistloading">'.$icon_progress.' '.$strloading.'</span><ul class="fp-value"/></td></tr>
+            <td class="mdl-left"><p class="{!}fp-refcount"></p><span class="fp-reflistloading">'.$icon_progress.' '.$strloading.'</span><ul class="fp-value"></ul></td></tr>
         </table>
     </form>
     <p class="{!}fp-thumbnail"></p>
@@ -402,10 +400,10 @@ class core_files_renderer extends plugin_renderer_base {
         </p>
     </form>
     <div class="fp-fileinfo">
-        <div class="{!}fp-datemodified">'.get_string('lastmodified', 'moodle').': <span class="fp-value"/></div>
-        <div class="{!}fp-datecreated">'.get_string('datecreated', 'repository').': <span class="fp-value"/></div>
-        <div class="{!}fp-size">'.get_string('size', 'repository').': <span class="fp-value"/></div>
-        <div class="{!}fp-dimensions">'.get_string('dimensions', 'repository').': <span class="fp-value"/></div>
+        <div class="{!}fp-datemodified">'.get_string('lastmodified', 'moodle').': <span class="fp-value"></span></div>
+        <div class="{!}fp-datecreated">'.get_string('datecreated', 'repository').': <span class="fp-value"></span></div>
+        <div class="{!}fp-size">'.get_string('size', 'repository').': <span class="fp-value"></span></div>
+        <div class="{!}fp-dimensions">'.get_string('dimensions', 'repository').': <span class="fp-value"></span></div>
     </div>
 </div>';
         return preg_replace('/\{\!\}/', '', $rv);
@@ -516,7 +514,7 @@ class core_files_renderer extends plugin_renderer_base {
 <div class="file-picker fp-generallayout">
     <div class="fp-repo-area">
         <ul class="fp-list">
-            <li class="{!}fp-repo"><a href="#"><img class="{!}fp-repo-icon" width="16" height="16" />&nbsp;<span class="{!}fp-repo-name" /span></a></li>
+            <li class="{!}fp-repo"><a href="#"><img class="{!}fp-repo-icon" width="16" height="16" />&nbsp;<span class="{!}fp-repo-name"></span></a></li>
         </ul>
     </div>
     <div class="fp-repo-items">
@@ -524,7 +522,7 @@ class core_files_renderer extends plugin_renderer_base {
             <div>
                 <div class="{!}fp-toolbar">
                     <div class="{!}fp-tb-back"><a href="#">'.get_string('back', 'repository').'</a></div>
-                    <div class="{!}fp-tb-search fp-search"><form/></div>
+                    <div class="{!}fp-tb-search"><form></form></div>
                     <div class="{!}fp-tb-refresh"><a href="#"><img src="'.$this->pix_url('a/refresh').'" /></a></div>
                     <div class="{!}fp-tb-logout"><img src="'.$this->pix_url('a/logout').'" /><a href="#"></a></div>
                     <div class="{!}fp-tb-manage"><a href="#"><img src="'.$this->pix_url('a/setting').'" /> '.get_string('manageurl', 'repository').'</a></div>
@@ -535,7 +533,7 @@ class core_files_renderer extends plugin_renderer_base {
                     <a class="{!}fp-vb-details" href="#"></a>
                     <a class="{!}fp-vb-tree" href="#"></a>
                 </div>
-                <div class="fp-clear-right"></div>
+                <div class="fp-clear-left"></div>
             </div>
             <div class="fp-pathbar">
                  <span class="{!}fp-path-folder"><a class="{!}fp-path-folder-name" href="#"></a></span>
@@ -691,12 +689,12 @@ class core_files_renderer extends plugin_renderer_base {
     </form>
     <p class="{!}fp-thumbnail"></p>
     <div class="fp-fileinfo">
-        <div class="{!}fp-datemodified">'.get_string('lastmodified', 'moodle').': <span class="fp-value"/></div>
-        <div class="{!}fp-datecreated">'.get_string('datecreated', 'repository').': <span class="fp-value"/></div>
-        <div class="{!}fp-size">'.get_string('size', 'repository').': <span class="fp-value"/></div>
-        <div class="{!}fp-license">'.get_string('license', 'moodle').': <span class="fp-value"/></div>
-        <div class="{!}fp-author">'.get_string('author', 'repository').': <span class="fp-value"/></div>
-        <div class="{!}fp-dimensions">'.get_string('dimensions', 'repository').': <span class="fp-value"/></div>
+        <div class="{!}fp-datemodified">'.get_string('lastmodified', 'moodle').': <span class="fp-value"></span></div>
+        <div class="{!}fp-datecreated">'.get_string('datecreated', 'repository').': <span class="fp-value"></span></div>
+        <div class="{!}fp-size">'.get_string('size', 'repository').': <span class="fp-value"></span></div>
+        <div class="{!}fp-license">'.get_string('license', 'moodle').': <span class="fp-value"></span></div>
+        <div class="{!}fp-author">'.get_string('author', 'repository').': <span class="fp-value"></span></div>
+        <div class="{!}fp-dimensions">'.get_string('dimensions', 'repository').': <span class="fp-value"></span></div>
     </div>
 </div>';
         return preg_replace('/\{\!\}/', '', $rv);
@@ -737,7 +735,7 @@ class core_files_renderer extends plugin_renderer_base {
                     <td class="mdl-left"><input type="text"/></td></tr>
                 <tr class="{!}fp-setlicense">
                     <td class="mdl-right"><label>'.get_string('chooselicense', 'repository').'</label>:</td>
-                    <td class="mdl-left"><select/></td></tr>
+                    <td class="mdl-left"><select></select></td></tr>
             </table>
         </form>
         <div><button class="{!}fp-upload-btn">'.get_string('upload', 'repository').'</button></div>
@@ -772,7 +770,7 @@ class core_files_renderer extends plugin_renderer_base {
      */
     private function fp_js_template_error() {
         $rv = '
-<div class="fp-content-error" ><div class="{!}fp-error" /></div>';
+<div class="fp-content-error" ><div class="{!}fp-error"></div></div>';
         return preg_replace('/\{\!\}/', '', $rv);
     }
 
@@ -818,7 +816,7 @@ class core_files_renderer extends plugin_renderer_base {
     <p class="{!}fp-dlg-text"></p>
     <a class="{!}fp-dlg-butoverwrite fp-panel-button" href="#">'.get_string('overwrite', 'repository').'</a>
     <a class="{!}fp-dlg-butcancel fp-panel-button" href="#">'.get_string('cancel').'</a>
-    <a class="{!}fp-dlg-butrename fp-panel-button" href="#"/>
+    <a class="{!}fp-dlg-butrename fp-panel-button" href="#"></a>
 </div>';
         return preg_replace('/\{\!\}/', '', $rv);
     }
@@ -861,11 +859,11 @@ class core_files_renderer extends plugin_renderer_base {
                     <td align="right"><label></label></td>
                     <td align="left"><select></select></td></tr>
                 <tr class="{!}fp-login-input">
-                    <td class="label"><label /></td>
+                    <td class="label"><label></label></td>
                     <td class="input"><input/></td></tr>
                 <tr class="{!}fp-login-radiogroup">
-                    <td align="right" width="30%" valign="top"><label /></td>
-                    <td align="left" valign="top"><p class="{!}fp-login-radio"><input /> <label /></p></td></tr>
+                    <td align="right" width="30%" valign="top"><label></label></td>
+                    <td align="left" valign="top"><p class="{!}fp-login-radio"><input /> <label></label></p></td></tr>
             </table>
             <p><button class="{!}fp-login-submit">'.get_string('submit', 'repository').'</button></p>
         </form>
@@ -897,7 +895,7 @@ class core_files_renderer extends plugin_renderer_base {
      * Default contents is one text input field with name="s"
      */
     public function repository_default_searchform() {
-        $str = '<input class="search-entry" name="s" value="Search" />';
+        $str = '<div class="fp-def-search"><input name="s" value='.get_string('search', 'repository').' /></div>';
         return $str;
     }
 }
index 0a0c2c5..d44b2ca 100644 (file)
@@ -171,7 +171,7 @@ if ($mform->is_cancelled()) {
 // form processing
 } else if ($data = $mform->get_data(false)) {
 
-    if (is_array($data->feedback)) {
+    if (isset($data->feedback) && is_array($data->feedback)) {
         $data->feedbackformat = $data->feedback['format'];
         $data->feedback = $data->feedback['text'];
     }
index e1937be..9d1bda9 100644 (file)
@@ -32,16 +32,56 @@ require_once $CFG->libdir.'/gradelib.php';
  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 class graded_users_iterator {
-    public $course;
-    public $grade_items;
-    public $groupid;
-    public $users_rs;
-    public $grades_rs;
-    public $gradestack;
-    public $sortfield1;
-    public $sortorder1;
-    public $sortfield2;
-    public $sortorder2;
+
+    /**
+     * The couse whose users we are interested in
+     */
+    protected $course;
+
+    /**
+     * An array of grade items or null if only user data was requested
+     */
+    protected $grade_items;
+
+    /**
+     * The group ID we are interested in. 0 means all groups.
+     */
+    protected $groupid;
+
+    /**
+     * A recordset of graded users
+     */
+    protected $users_rs;
+
+    /**
+     * A recordset of user grades (grade_grade instances)
+     */
+    protected $grades_rs;
+
+    /**
+     * Array used when moving to next user while iterating through the grades recordset
+     */
+    protected $gradestack;
+
+    /**
+     * The first field of the users table by which the array of users will be sorted
+     */
+    protected $sortfield1;
+
+    /**
+     * Should sortfield1 be ASC or DESC
+     */
+    protected $sortorder1;
+
+    /**
+     * The second field of the users table by which the array of users will be sorted
+     */
+    protected $sortfield2;
+
+    /**
+     * Should sortfield2 be ASC or DESC
+     */
+    protected $sortorder2;
 
     /**
      * Should users whose enrolment has been suspended be ignored?
@@ -59,7 +99,7 @@ class graded_users_iterator {
      * @param string $sortfield2 The second field of the users table by which the array of users will be sorted
      * @param string $sortorder2 The order in which the second sorting field will be sorted (ASC or DESC)
      */
-    public function graded_users_iterator($course, $grade_items=null, $groupid=0,
+    public function __construct($course, $grade_items=null, $groupid=0,
                                           $sortfield1='lastname', $sortorder1='ASC',
                                           $sortfield2='firstname', $sortorder2='ASC') {
         $this->course      = $course;
@@ -75,6 +115,7 @@ class graded_users_iterator {
 
     /**
      * Initialise the iterator
+     *
      * @return boolean success
      */
     public function init() {
@@ -177,7 +218,7 @@ class graded_users_iterator {
      * Returns information about the next user
      * @return mixed array of user info, all grades and feedback or null when no more users found
      */
-    function next_user() {
+    public function next_user() {
         if (!$this->users_rs) {
             return false; // no users present
         }
@@ -244,10 +285,9 @@ class graded_users_iterator {
     }
 
     /**
-     * Close the iterator, do not forget to call this function.
-     * @return void
+     * Close the iterator, do not forget to call this function
      */
-    function close() {
+    public function close() {
         if ($this->users_rs) {
             $this->users_rs->close();
             $this->users_rs = null;
@@ -273,23 +313,23 @@ class graded_users_iterator {
 
 
     /**
-     * _push
+     * Add a grade_grade instance to the grade stack
      *
      * @param grade_grade $grade Grade object
      *
      * @return void
      */
-    function _push($grade) {
+    private function _push($grade) {
         array_push($this->gradestack, $grade);
     }
 
 
     /**
-     * _pop
+     * Remove a grade_grade instance from the grade stack
      *
-     * @return object current grade object
+     * @return grade_grade current grade object
      */
-    function _pop() {
+    private function _pop() {
         global $DB;
         if (empty($this->gradestack)) {
             if (empty($this->grades_rs) || !$this->grades_rs->valid()) {
index 9045afd..080f406 100644 (file)
@@ -52,6 +52,7 @@ class core_group_external extends external_api {
                             'courseid' => new external_value(PARAM_INT, 'id of course'),
                             'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                             'description' => new external_value(PARAM_RAW, 'group description text'),
+                            'descriptionformat' => new external_format_value('description', VALUE_DEFAULT),
                             'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                         )
                     ), 'List of group object. A group has a courseid, a name, a description and an enrolment key.'
@@ -99,6 +100,9 @@ class core_group_external extends external_api {
             }
             require_capability('moodle/course:managegroups', $context);
 
+            // Validate format.
+            $group->descriptionformat = external_validate_format($group->descriptionformat);
+
             // finally create the group
             $group->id = groups_create_group($group, false);
             $groups[] = (array)$group;
@@ -123,6 +127,7 @@ class core_group_external extends external_api {
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description' => new external_value(PARAM_RAW, 'group description text'),
+                    'descriptionformat' => new external_format_value('description'),
                     'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                 )
             ), 'List of group object. A group has an id, a courseid, a name, a description and an enrolment key.'
@@ -157,7 +162,7 @@ class core_group_external extends external_api {
         $groups = array();
         foreach ($params['groupids'] as $groupid) {
             // validate params
-            $group = groups_get_group($groupid, 'id, courseid, name, description, enrolmentkey', MUST_EXIST);
+            $group = groups_get_group($groupid, 'id, courseid, name, description, descriptionformat, enrolmentkey', MUST_EXIST);
 
             // now security checks
             $context = get_context_instance(CONTEXT_COURSE, $group->courseid);
@@ -171,6 +176,10 @@ class core_group_external extends external_api {
             }
             require_capability('moodle/course:managegroups', $context);
 
+            list($group->description, $group->descriptionformat) =
+                external_format_text($group->description, $group->descriptionformat,
+                        $context->id, 'group', 'description', $group->id);
+
             $groups[] = (array)$group;
         }
 
@@ -191,6 +200,7 @@ class core_group_external extends external_api {
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description' => new external_value(PARAM_RAW, 'group description text'),
+                    'descriptionformat' => new external_format_value('description'),
                     'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                 )
             )
@@ -233,10 +243,14 @@ class core_group_external extends external_api {
         }
         require_capability('moodle/course:managegroups', $context);
 
-        $gs = groups_get_all_groups($params['courseid'], 0, 0, 'g.id, g.courseid, g.name, g.description, g.enrolmentkey');
+        $gs = groups_get_all_groups($params['courseid'], 0, 0,
+            'g.id, g.courseid, g.name, g.description, g.descriptionformat, g.enrolmentkey');
 
         $groups = array();
         foreach ($gs as $group) {
+            list($group->description, $group->descriptionformat) =
+                external_format_text($group->description, $group->descriptionformat,
+                        $context->id, 'group', 'description', $group->id);
             $groups[] = (array)$group;
         }
 
@@ -257,6 +271,7 @@ class core_group_external extends external_api {
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
                     'description' => new external_value(PARAM_RAW, 'group description text'),
+                    'descriptionformat' => new external_format_value('description'),
                     'enrolmentkey' => new external_value(PARAM_RAW, 'group enrol secret phrase'),
                 )
             )
@@ -557,7 +572,8 @@ class core_group_external extends external_api {
                         array(
                             'courseid' => new external_value(PARAM_INT, 'id of course'),
                             'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                            'description' => new external_value(PARAM_RAW, 'grouping description text')
+                            'description' => new external_value(PARAM_RAW, 'grouping description text'),
+                            'descriptionformat' => new external_format_value('descripiton', VALUE_DEFAULT)
                         )
                     ), 'List of grouping object. A grouping has a courseid, a name and a description.'
                 )
@@ -604,8 +620,7 @@ class core_group_external extends external_api {
             }
             require_capability('moodle/course:managegroups', $context);
 
-            // We must force allways FORMAT_HTML.
-            $grouping->descriptionformat = FORMAT_HTML;
+            $grouping->descriptionformat = external_validate_format($grouping->descriptionformat);
 
             // Finally create the grouping.
             $grouping->id = groups_create_grouping($grouping);
@@ -630,7 +645,8 @@ class core_group_external extends external_api {
                     'id' => new external_value(PARAM_INT, 'grouping record id'),
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                    'description' => new external_value(PARAM_CLEANHTML, 'grouping description text')
+                    'description' => new external_value(PARAM_RAW, 'grouping description text'),
+                    'descriptionformat' => new external_format_value('description')
                 )
             ), 'List of grouping object. A grouping has an id, a courseid, a name and a description.'
         );
@@ -650,7 +666,8 @@ class core_group_external extends external_api {
                         array(
                             'id' => new external_value(PARAM_INT, 'id of grouping'),
                             'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                            'description' => new external_value(PARAM_RAW, 'grouping description text')
+                            'description' => new external_value(PARAM_RAW, 'grouping description text'),
+                            'descriptionformat' => new external_format_value('description', VALUE_DEFAULT)
                         )
                     ), 'List of grouping object. A grouping has a courseid, a name and a description.'
                 )
@@ -705,7 +722,7 @@ class core_group_external extends external_api {
             require_capability('moodle/course:managegroups', $context);
 
             // We must force allways FORMAT_HTML.
-            $grouping->descriptionformat = FORMAT_HTML;
+            $grouping->descriptionformat = external_validate_format($grouping->descriptionformat);
 
             // Finally update the grouping.
             groups_update_grouping($grouping);
@@ -772,12 +789,9 @@ class core_group_external extends external_api {
             }
             require_capability('moodle/course:managegroups', $context);
 
-            $grouping->description = file_rewrite_pluginfile_urls($grouping->description, 'webservice/pluginfile.php', $context->id, 'grouping', 'description', $grouping->id);
-
-            $options = new stdClass;
-            $options->noclean = true;
-            $options->para = false;
-            $grouping->description = format_text($grouping->description, FORMAT_HTML, $options);
+            list($grouping->description, $grouping->descriptionformat) =
+                external_format_text($grouping->description, $grouping->descriptionformat,
+                        $context->id, 'grouping', 'description', $grouping->id);
 
             $groupings[] = (array)$grouping;
         }
@@ -798,7 +812,8 @@ class core_group_external extends external_api {
                     'id' => new external_value(PARAM_INT, 'grouping record id'),
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                    'description' => new external_value(PARAM_CLEANHTML, 'grouping description text')
+                    'description' => new external_value(PARAM_RAW, 'grouping description text'),
+                    'descriptionformat' => new external_format_value('description')
                 )
             )
         );
@@ -849,13 +864,9 @@ class core_group_external extends external_api {
 
         $groupings = array();
         foreach ($gs as $grouping) {
-            $grouping->description = file_rewrite_pluginfile_urls($grouping->description, 'webservice/pluginfile.php', $context->id, 'grouping', 'description', $grouping->id);
-
-            $options = new stdClass;
-            $options->noclean = true;
-            $options->para = false;
-            $grouping->description = format_text($grouping->description, FORMAT_HTML, $options);
-
+            list($grouping->description, $grouping->descriptionformat) =
+                external_format_text($grouping->description, $grouping->descriptionformat,
+                        $context->id, 'grouping', 'description', $grouping->id);
             $groupings[] = (array)$grouping;
         }
 
@@ -875,7 +886,8 @@ class core_group_external extends external_api {
                     'id' => new external_value(PARAM_INT, 'grouping record id'),
                     'courseid' => new external_value(PARAM_INT, 'id of course'),
                     'name' => new external_value(PARAM_TEXT, 'multilang compatible name, course unique'),
-                    'description' => new external_value(PARAM_CLEANHTML, 'grouping description text')
+                    'description' => new external_value(PARAM_RAW, 'grouping description text'),
+                    'descriptionformat' => new external_format_value('description')
                 )
             )
         );
index 341a147..e50e3bc 100644 (file)
@@ -73,11 +73,6 @@ if (!function_exists('iconv')) {
     echo 'Moodle requires the iconv PHP extension. Please install or enable the iconv extension.';
     die();
 }
-if (iconv('UTF-8', 'UTF-8//IGNORE', 'abc') !== 'abc') {
-    // known to be broken in mid-2011 MAMP installations
-    echo 'Broken iconv PHP extension detected, installation can not continue.';
-    die;
-}
 
 if (PHP_INT_SIZE > 4) {
     // most probably 64bit PHP - we need a lot more memory
similarity index 53%
rename from portfolio/googledocs/db/events.php
rename to install/lang/es_mx/admin.php
index b0d3d0c..c50bbe7 100644 (file)
@@ -1,4 +1,5 @@
 <?php
+
 // This file is part of Moodle - http://moodle.org/
 //
 // Moodle is free software: you can redistribute it and/or modify
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Add event handlers for the googledocs portfolio.
+ * Automatically generated strings for Moodle 2.3dev 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    portfolio_googledocs
- * @category   event
- * @copyright  2009 Penny Leach
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package   installer
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
 
-$handlers = array (
-    'user_deleted' => array (
-         'handlerfile'      => '/portfolio/googledocs/lib.php',
-         'handlerfunction'  => 'portfolio_googledocs_user_deleted',
-         'schedule'         => 'cron',
-         'internal'         => 0,
-     ),
-);
-
+defined('MOODLE_INTERNAL') || die();
 
+$string['environmentrequireinstall'] = 'debe estar instalado y activado';
diff --git a/install/lang/es_mx/install.php b/install/lang/es_mx/install.php
new file mode 100644 (file)
index 0000000..a3586f0
--- /dev/null
@@ -0,0 +1,38 @@
+<?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 2.3dev 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['databasehost'] = 'host de la Base de Datos';
+$string['environmentsub2'] = 'Cada versión de Moodle tiene algún requisito mínimo de la versión de PHP y un número obligatorio de extensiones de PHP. Una comprobación del entorno completo se realiza antes de cada instalación y actualización. Por favor, póngase en contacto con el administrador del servidor si no sabe cómo instalar la nueva versión o habilitar las extensiones PHP.';
+$string['errorsinenvironment'] = '¡La comprobación del entorno falló!';
+$string['welcomep20'] = 'Si está viendo esta página es porque ha podido ejecutar el paquete <strong>{$a->packname} {$a->packversion}</strong> en su computadora. !Enhorabuena!';
+$string['welcomep30'] = 'Esta versión de <strong>{$a->installername}</strong> incluye las aplicaciones necesarias para que <strong>Moodle</strong> funcione en su computadora principalmente:';
+$string['welcomep60'] = 'Las siguientes páginas le guiarán a través de algunos sencillos pasos para configurar y ajustar <strong>Moodle</strong> en su computadora. Puede utilizar los valores por defecto sugeridos o, de forma opcional, modificarlos para que se ajusten a sus necesidades.';
index b338d42..ee81afc 100644 (file)
@@ -39,8 +39,6 @@ $string['skipa'] = 'Skip {$a}';
 $string['skipblock'] = 'Skip block';
 $string['skipnavigation'] = 'Skip navigation';
 $string['skipto'] = 'Skip to {$a}';
-$string['tabledata'] = 'Data table, {$a}';
-$string['tablelayout'] = 'Layout table, {$a}';
 $string['tocontent'] = 'Skip to main content';
 $string['tonavigation'] = 'Go to navigation';
 $string['youarehere'] = 'You are here';
index 2ac7b1e..42dbcf3 100644 (file)
@@ -473,7 +473,7 @@ $string['enabledevicedetection'] = 'Enable device detection';
 $string['enablegravatar'] = 'Enable Gravatar';
 $string['enablegravatar_help'] = 'When enabled Moodle will attempt to fetch a user profile picture from Gravatar if the user has not uploaded an image.';
 $string['enablegroupmembersonly'] = 'Enable group members only';
-$string['enablemobilewebservice'] = 'Enable mobile web service';
+$string['enablemobilewebservice'] = 'Enable web services for mobile devices';
 $string['enablerecordcache'] = 'Enable record cache';
 $string['enablerssfeeds'] = 'Enable RSS feeds';
 $string['enablesafebrowserintegration'] = 'Enable Safe Exam Browser integration';
@@ -568,6 +568,7 @@ $string['htmleditorsettings'] = 'HTML editor settings';
 $string['htmlsettings'] = 'HTML settings';
 $string['http'] = 'HTTP';
 $string['httpsecurity'] = 'HTTP security';
+$string['hubs'] = 'Hubs';
 $string['change'] = 'change';
 $string['checkboxno'] = 'No';
 $string['checkboxyes'] = 'Yes';
@@ -636,6 +637,7 @@ $string['maturity50'] = 'Alpha';
 $string['maturity100'] = 'Beta';
 $string['maturity150'] = 'Release candidate';
 $string['maturity200'] = 'Stable version';
+$string['maturityallowunstable'] = 'Hint: You may want to run this script with --allow-unstable option';
 $string['maturitycoreinfo'] = 'Your site is currently running unstable "{$a}" development code.';
 $string['maturitycorewarning'] = 'The version of Moodle that you are about to install or upgrade to contains
 unstable "{$a}" development code that is not suitable for use on most production
@@ -753,6 +755,8 @@ $string['pleaserefreshregistration'] = 'Your site has been registered with moodl
 $string['pleaseregister'] = 'Please register your site to remove this button';
 $string['plugin'] = 'Plugin';
 $string['plugins'] = 'Plugins';
+$string['pluginscheck'] = 'Plugin dependencies check';
+$string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}';
 $string['pluginschecktodo'] = 'You must solve all the plugin requirements before proceeding to install this Moodle version!';
 $string['pluginsoverview'] = 'Plugins overview';
 $string['profilecategory'] = 'Category';
@@ -843,7 +847,15 @@ $string['rcache'] = 'Record cache';
 $string['rcachettl'] = 'Record cache TTL';
 $string['recaptchaprivatekey'] = 'ReCAPTCHA private key';
 $string['recaptchapublickey'] = 'ReCAPTCHA public key';
+$string['register'] = 'Register your site';
+$string['registermoodleorg'] = 'When you register your site with {$a}';
+$string['registermoodleorgli1'] = 'You are added to a low-volume mailing list for important notifications such as security alerts and new releases of Moodle.';
+$string['registermoodleorgli2'] = 'Statistics about your site will be added to the {$a} of the worldwide Moodle community.';
+$string['registermoodleorgli3'] = 'Your site is also registered with the Moodle.org Open Community Hub ({$a}), allowing users with the publish courses capability (by default only managers) the option of publishing courses to MOOCH.';
+$string['registerwithmoodleorg'] = 'Register with Moodle.org';
 $string['registration'] = 'Registration';
+$string['registration_help'] = 'It is recommended to register on Moodle.org to receive security email alerts, to contribute to the Moodle growth and statistics, or to be able to share courses on MOOCH.';
+$string['registrationwarning'] = 'Your site is not yet registered.';
 $string['releasenoteslink'] = 'For information about this version of Moodle, please see the online <a target="_blank" href="{$a}">Release Notes</a>';
 $string['rememberusername'] = 'Remember username';
 $string['rememberusername_desc'] = 'Enable if you want to store permanent cookies with usernames during user login. Permanent cookies may be considered a privacy issue if used without consent.';
@@ -922,6 +934,7 @@ $string['splrequired'] = 'The SPL PHP extension is now required by Moodle.';
 $string['stats'] = 'Statistics';
 $string['statsfirstrun'] = 'Maximum processing interval';
 $string['statsmaxruntime'] = 'Maximum runtime';
+$string['statsmoodleorg'] = 'statistics';
 $string['statsruntimedays'] = 'Days to process';
 $string['statsruntimestart'] = 'Run at';
 $string['statsuserthreshold'] = 'User threshold';
@@ -1028,6 +1041,7 @@ $string['usetags'] = 'Enable tags functionality';
 $string['validateerror'] = 'This value was not valid:';
 $string['verifychangedemail'] = 'Restrict domains when changing email';
 $string['warningcurrentsetting'] = 'Invalid current value: {$a}';
+$string['warningiconvbuggy'] = 'Your version of the iconv library does not support the //IGNORE modifier. You should install the mbstring extension which can be used instead for cleaning strings containing invalid UTF-8 characters.';
 $string['webproxy'] = 'Web proxy';
 $string['webproxyinfo'] = 'Fill in following options if your Moodle server can not access internet directly. Internet access is required for download of environment data, language packs, RSS feeds, timezones, etc.<br /><em>PHP cURL extension is highly recommended.</em>';
 $string['xmlrpcrecommended'] = 'The xmlrpc extension is needed for hub communication, and useful for web services and Moodle networking';
index 42abfad..4b8cd53 100644 (file)
@@ -118,12 +118,6 @@ $string['licence_link'] = 'licenses';
 $string['logourl'] = 'Logo URL';
 $string['modulenumberaverage'] = 'Average number of course modules ({$a})';
 $string['moodleorg'] = 'Moodle.org';
-$string['moodleorgregistrationdetail'] = 'The main community hub is called MOOCH, at hub.moodle.org.  By registering your site with MOOCH you will contribute to the statistics of the worldwide Moodle community.  You can also join a low-volume mailing list providing early notifications of security fixes and new releases of Moodle.';
-$string['moodleorgregistrationdetail2'] = 'This option allows you to register your Moodle site with MOOCH, at hub.moodle.org.  Registration is free.
-The main benefit of registering is that you will be added to a low-volume mailing list for important notifications such as security alerts and new releases of Moodle.
-By default, your information will be kept private, and will never be sold or passed on to anyone else.  The only reason for collecting this information is for support purposes, and to help build up a statistical picture of the Moodle community as a whole.
-If you choose, you can allow your site name, country and URL to be added to the public list of Moodle Sites.
-All new registrations are verified manually before they are added to the list, but once you are added you can update your registration (and your entry on the public list) at any time.';
 $string['mustselectsubject'] = 'You must select a subject';
 $string['name'] = 'Name';
 $string['name_help'] = 'This name will be showing in the course listing.';
@@ -165,11 +159,10 @@ $string['registeredcourses'] = 'Registered courses';
 $string['registeredsites'] = 'Registered sites';
 $string['registrationinfo'] = 'Registration information';
 $string['registeredmoodleorg'] = 'Moodle.org ({$a})';
-$string['registeredon'] = 'Registered with';
+$string['registeredon'] = 'Hubs with which you are registered';
+$string['registermoochtips'] = 'To be registered with Moodle.org Open Community Hub (MOOCH), your site must be registered with Moodle.org.';
 $string['registersite'] = 'Register with {$a}';
-$string['registeron'] = 'Register your site';
-$string['registeronmoodleorg'] = 'Register with Moodle.org (MOOCH)';
-$string['registeronspecifichub'] = 'Register with a specific hub';
+$string['registerwith'] = 'Register with a hub';
 $string['registrationconfirmed'] = 'Site registration confirmed';
 $string['registrationconfirmedon'] = 'You are now registered on the hub {$a}. You are now able to publish courses to this hub, using the "Publish" link in course administration menus.';
 $string['registrationupdated'] = 'Registration has been updated.';
@@ -237,7 +230,6 @@ $string['siteversion'] = 'Moodle version';
 $string['siteversion_help'] = 'The Moodle version of this site.';
 $string['subject'] = 'Subject';
 $string['subject_help'] = 'Select the main subject area which the course covers.';
-$string['specifichub'] = 'Specific hub';
 $string['specifichubregistrationdetail'] = 'You can also register your site with other community hubs.';
 $string['statistics'] = 'Statistics privacy';
 $string['status'] = 'Hub listing';
index 073d3eb..224b741 100644 (file)
@@ -466,7 +466,7 @@ $string['displayonpage'] = 'Display on page';
 $string['dndenabled'] = 'Drag and drop available';
 $string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.<br />Note: this may not work with other web browsers';
 $string['dndenabled_insentence'] = 'drag and drop available';
-$string['dndenabled_inbox'] = 'drag and drop files here to upload them';
+$string['dndenabled_inbox'] = 'You can drag and drop files here to add them.';
 $string['dnduploadwithoutcontent'] = 'This upload does not have any content';
 $string['dndworkingfiletextlink'] = 'Drag and drop files, text or links onto course sections to upload them';
 $string['dndworkingfilelink'] = 'Drag and drop files or links onto course sections to upload them';
index 3bc89ef..3bba405 100644 (file)
@@ -31,7 +31,7 @@ $string['checkforupdates'] = 'Check for available updates';
 $string['checkforupdateslast'] = 'Last check done on {$a}';
 $string['displayname'] = 'Plugin name';
 $string['moodleversion'] = 'Moodle {$a}';
-$string['nonehighlighted'] = 'No plugins require your attention during this upgrade';
+$string['nonehighlighted'] = 'No plugins require your attention now';
 $string['nonehighlightedinfo'] = 'Display the list of all installed plugins anyway';
 $string['noneinstalled'] = 'No plugins of this type are installed';
 $string['notes'] = 'Notes';
@@ -52,7 +52,7 @@ $string['requiredby'] = 'Required by: {$a}';
 $string['requires'] = 'Requires';
 $string['rootdir'] = 'Directory';
 $string['settings'] = 'Settings';
-$string['somehighlighted'] = 'Number of plugins requiring attention during this upgrade: {$a}';
+$string['somehighlighted'] = 'Number of plugins requiring your attention: {$a}';
 $string['somehighlightedinfo'] = 'Display the full list of installed plugins';
 $string['somehighlightedonly'] = 'Display only plugins requiring your attention';
 $string['source'] = 'Source';
index 42c8658..c0ca916 100644 (file)
@@ -145,6 +145,7 @@ $string['manage'] = 'Manage repositories';
 $string['manageurl'] = 'Manage';
 $string['manageuserrepository'] = 'Manage individual repository';
 $string['moving'] = 'Moving';
+$string['newfoldername'] = 'New folder name:';
 $string['noenter'] = 'Nothing entered';
 $string['nofilesattached'] = 'No files attached';
 $string['nofilesavailable'] = 'No files available';
index 2b72efe..f92c829 100644 (file)
@@ -122,8 +122,8 @@ $string['mobilewsenabled'] = 'Enabled';
 $string['nofunctions'] = 'This service has no functions.';
 $string['norequiredcapability'] = 'No required capability';
 $string['notoken'] = 'The token list is empty.';
-$string['onesystemcontrolling'] = 'One system controlling Moodle with a token';
-$string['onesystemcontrollingdescription'] = 'The following steps help you to set up the Moodle web service for a system to control Moodle. These steps also help to set up the recommended token (security keys) authentication method.';
+$string['onesystemcontrolling'] = 'Allow an external system to control Moodle';
+$string['onesystemcontrollingdescription'] = 'The following steps help you to set up the Moodle web services to allow an external system to interact with Moodle. This includes setting up a token (security key) authentication method.';
 $string['operation'] = 'Operation';
 $string['optional'] = 'Optional';
 $string['passwordisexpired'] = 'Password is expired.';
index ba27546..375c966 100644 (file)
@@ -235,7 +235,7 @@ class completion_criteria_activity extends completion_criteria {
         // Loop through completions, and mark as complete
         $rs = $DB->get_recordset_sql($sql);
         foreach ($rs as $record) {
-            $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
+            $completion = new completion_criteria_completion((array) $record, DATA_OBJECT_FETCH_BY_KEY);
             $completion->mark_complete($record->timecompleted);
         }
         $rs->close();
index 04be448..a25dea2 100644 (file)
@@ -193,8 +193,8 @@ class completion_criteria_course extends completion_criteria {
         // Loop through completions, and mark as complete
         $rs = $DB->get_recordset_sql($sql);
         foreach ($rs as $record) {
-            $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
-            $completion->mark_complete($record['timecompleted']);
+            $completion = new completion_criteria_completion((array) $record, DATA_OBJECT_FETCH_BY_KEY);
+            $completion->mark_complete($record->timecompleted);
         }
         $rs->close();
     }
index bacc144..baecc20 100644 (file)
@@ -179,8 +179,8 @@ class completion_criteria_date extends completion_criteria {
         // Loop through completions, and mark as complete
         $rs = $DB->get_recordset_sql($sql, array(time()));
         foreach ($rs as $record) {
-            $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
-            $completion->mark_complete($record['timeend']);
+            $completion = new completion_criteria_completion((array) $record, DATA_OBJECT_FETCH_BY_KEY);
+            $completion->mark_complete($record->timeend);
         }
         $rs->close();
     }
index 962b052..ead1a55 100644 (file)
@@ -229,7 +229,7 @@ class completion_criteria_duration extends completion_criteria {
         $now = time();
         $rs = $DB->get_recordset_sql($sql, array($now, $now));
         foreach ($rs as $record) {
-            $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
+            $completion = new completion_criteria_completion((array) $record, DATA_OBJECT_FETCH_BY_KEY);
 
             // Use time start if not 0, otherwise use timeenrolled
             if ($record->otimestart) {
index 3e0da11..6bc32ed 100644 (file)
@@ -215,8 +215,8 @@ class completion_criteria_grade extends completion_criteria {
         // Loop through completions, and mark as complete
         $rs = $DB->get_recordset_sql($sql);
         foreach ($rs as $record) {
-            $completion = new completion_criteria_completion($record, DATA_OBJECT_FETCH_BY_KEY);
-            $completion->mark_complete($record['timecompleted']);
+            $completion = new completion_criteria_completion((array) $record, DATA_OBJECT_FETCH_BY_KEY);
+            $completion->mark_complete($record->timecompleted);
         }
         $rs->close();
     }
index 2034af1..4df3bc0 100644 (file)
@@ -992,172 +992,72 @@ class completion_info {
      * @return bool
      */
     public function is_tracked_user($userid) {
-        global $DB;
-
-        $tracked = $this->generate_tracked_user_sql();
-
-        $sql  = "SELECT u.id ";
-        $sql .= $tracked->sql;
-        $sql .= ' AND u.id = :userid';
-
-        $params = $tracked->data;
-        $params['userid'] = (int)$userid;
-        return $DB->record_exists_sql($sql, $params);
+        return is_enrolled(context_course::instance($this->course->id), $userid, '', true);
     }
 
     /**
-     * Return number of users whose progress is tracked in this course
+     * Returns the number of users whose progress is tracked in this course.
      *
-     * Optionally supply a search's where clause, or a group id
+     * Optionally supply a search's where clause, or a group id.
      *
-     * @param string $where Where clause sql
-     * @param array $where_params Where clause params
+     * @param string $where Where clause sql (use 'u.whatever' for user table fields)
+     * @param array $whereparams Where clause params
      * @param int $groupid Group id
-     * @return int
+     * @return int Number of tracked users
      */
-    public function get_num_tracked_users($where = '', $where_params = array(), $groupid = 0) {
+    public function get_num_tracked_users($where = '', $whereparams = array(), $groupid = 0) {
         global $DB;
 
-        $tracked = $this->generate_tracked_user_sql($groupid);
-
-        $sql  = "SELECT COUNT(u.id) ";
-        $sql .= $tracked->sql;
-
+        list($enrolledsql, $enrolledparams) = get_enrolled_sql(
+                context_course::instance($this->course->id), '', $groupid, true);
+        $sql  = 'SELECT COUNT(eu.id) FROM (' . $enrolledsql . ') eu JOIN {user} u ON u.id = eu.id';
         if ($where) {
-            $sql .= " AND $where";
+            $sql .= " WHERE $where";
         }
 
-        $params = array_merge($tracked->data, $where_params);
+        $params = array_merge($enrolledparams, $whereparams);
         return $DB->count_records_sql($sql, $params);
     }
 
     /**
-     * Return array of users whose progress is tracked in this course
+     * Return array of users whose progress is tracked in this course.
      *
-     * Optionally supply a search's where caluse, group id, sorting, paging
+     * Optionally supply a search's where clause, group id, sorting, paging.
      *
-     * @param string $where Where clause sql (optional)
-     * @param array $where_params Where clause params (optional)
-     * @param integer $groupid Group ID to restrict to (optional)
+     * @param string $where Where clause sql, referring to 'u.' fields (optional)
+     * @param array $whereparams Where clause params (optional)
+     * @param int $groupid Group ID to restrict to (optional)
      * @param string $sort Order by clause (optional)
-     * @param integer $limitfrom Result start (optional)
-     * @param integer $limitnum Result max size (optional)
+     * @param int $limitfrom Result start (optional)
+     * @param int $limitnum Result max size (optional)
      * @param context $extracontext If set, includes extra user information fields
      *   as appropriate to display for current user in this context
-     * @return array
+     * @return array Array of user objects with standard user fields
      */
-    public function get_tracked_users($where = '', $where_params = array(), $groupid = 0,
+    public function get_tracked_users($where = '', $whereparams = array(), $groupid = 0,
              $sort = '', $limitfrom = '', $limitnum = '', context $extracontext = null) {
 
         global $DB;
 
-        $tracked = $this->generate_tracked_user_sql($groupid);
-        $params = $tracked->data;
+        list($enrolledsql, $params) = get_enrolled_sql(
+                context_course::instance($this->course->id), '', $groupid, true);
 
-        $sql = "
-            SELECT
-                u.id,
-                u.firstname,
-                u.lastname,
-                u.idnumber
-        ";
+        $sql = 'SELECT u.id, u.firstname, u.lastname, u.idnumber';
         if ($extracontext) {
             $sql .= get_extra_user_fields_sql($extracontext, 'u', '', array('idnumber'));
         }
-
-        $sql .= $tracked->sql;
+        $sql .= ' FROM (' . $enrolledsql . ') eu JOIN {user} u ON u.id = eu.id';
 
         if ($where) {
             $sql .= " AND $where";
-            $params = array_merge($params, $where_params);
+            $params = array_merge($params, $whereparams);
         }
 
         if ($sort) {
             $sql .= " ORDER BY $sort";
         }
 
-        $users = $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
-        return $users ? $users : array(); // In case it returns false
-    }
-
-    /**
-     * Generate the SQL for finding tracked users in this course
-     *
-     * Returns an object containing the sql fragment and an array of
-     * bound data params.
-     *
-     * @param integer $groupid
-     * @return stdClass With two properties, sql (string), and data (array)
-     */
-    public function generate_tracked_user_sql($groupid = 0) {
-        global $CFG;
-
-        $return = new stdClass();
-        $return->sql = '';
-        $return->data = array();
-
-        if (!empty($CFG->gradebookroles)) {
-            $roles = ' AND ra.roleid IN ('.$CFG->gradebookroles.')';
-        } else {
-            // This causes it to default to everyone (if there is no student role)
-            $roles = '';
-        }
-
-        // Build context sql
-        $context = get_context_instance(CONTEXT_COURSE, $this->course->id);
-        $parentcontexts = substr($context->path, 1); // kill leading slash
-        $parentcontexts = str_replace('/', ',', $parentcontexts);
-        if ($parentcontexts !== '') {
-            $parentcontexts = ' OR ra.contextid IN ('.$parentcontexts.' )';
-        }
-
-        $groupjoin   = '';
-        $groupselect = '';
-        if ($groupid) {
-            $groupjoin   = "JOIN {groups_members} gm
-                              ON gm.userid = u.id";
-            $groupselect = " AND gm.groupid = :groupid ";
-
-            $return->data['groupid'] = $groupid;
-        }
-
-        $return->sql = "
-            FROM
-                {user} u
-            INNER JOIN
-                {role_assignments} ra
-             ON ra.userid = u.id
-            INNER JOIN
-                {role} r
-             ON r.id = ra.roleid
-            INNER JOIN
-                {user_enrolments} ue
-             ON ue.userid = u.id
-            INNER JOIN
-                {enrol} e
-             ON e.id = ue.enrolid
-            INNER JOIN
-                {course} c
-             ON c.id = e.courseid
-            $groupjoin
-            WHERE
-                (ra.contextid = :contextid $parentcontexts)
-            AND c.id = :courseid
-            AND ue.status = 0
-            AND e.status = 0
-            AND ue.timestart < :now1
-            AND (ue.timeend > :now2 OR ue.timeend = 0)
-                $groupselect
-                $roles
-        ";
-
-        $now = time();
-        $return->data['now1'] = $now;
-        $return->data['now2'] = $now;
-        $return->data['contextid'] = $context->id;
-        $return->data['courseid'] = $this->course->id;
-
-        return $return;
+        return $DB->get_records_sql($sql, $params, $limitfrom, $limitnum);
     }
 
     /**
index 9bcd4bb..0d8b3cb 100644 (file)
@@ -69,7 +69,8 @@ class condition_info extends condition_info_base {
      * @param object $cm Moodle course-module object. May have extra fields
      *   ->conditionsgrade, ->conditionscompletion which should come from
      *   get_fast_modinfo. Should have ->availablefrom, ->availableuntil,
-     *   and ->showavailability, ->course; but the only required thing is ->id.
+     *   and ->showavailability, ->course, ->visible; but the only required
+     *   thing is ->id.
      * @param int $expectingmissing Used to control whether or not a developer
      *   debugging message (performance warning) will be displayed if some of
      *   the above data is missing and needs to be retrieved; a
@@ -426,7 +427,8 @@ abstract class condition_info_base {
      * @return array Array of field names
      */
     protected function get_main_table_fields() {
-        return array('id', 'course', 'availablefrom', 'availableuntil', 'showavailability');
+        return array('id', 'course', 'visible',
+                'availablefrom', 'availableuntil', 'showavailability');
     }
 
     /**
@@ -846,6 +848,16 @@ abstract class condition_info_base {
             }
         }
 
+        // If the item is marked as 'not visible' then we don't change the available
+        // flag (visible/available are treated distinctly), but we remove any
+        // availability info. If the item is hidden with the eye icon, it doesn't
+        // make sense to show 'Available from <date>' or similar, because even
+        // when that date arrives it will still not be available unless somebody
+        // toggles the eye icon.
+        if (!$this->item->visible) {
+            $information = '';
+        }
+
         $information = trim($information);
         return $available;
     }
index 791d246..031ac61 100644 (file)
  * @return mixed
  */
 function min_optional_param($name, $default, $type) {
-    $value = $default;
     if (isset($_GET[$name])) {
         $value = $_GET[$name];
 
     } else if (isset($_GET['amp;'.$name])) {
         // very, very, very ugly hack, unfortunately $OUTPUT->pix_url() is not used properly in javascript code :-(
         $value = $_GET['amp;'.$name];
+
+    } else if (isset($_POST[$name])) {
+        $value = $_POST[$name];
+
+    } else {
+        return $default;
     }
 
     return min_clean_param($value, $type);
@@ -50,15 +55,16 @@ function min_optional_param($name, $default, $type) {
 
 /**
  * Minimalistic parameter cleaning function.
- * Can not use optional param because moodlelib.php is not loaded yet
- * @param string $name
- * @param mixed $default
+ *
+ * Note: Can not use optional param because moodlelib.php is not loaded yet.
+ *
+ * @param string $value
  * @param string $type
  * @return mixed
  */
 function min_clean_param($value, $type) {
     switch($type) {
-        case 'RAW':      $value = iconv('UTF-8', 'UTF-8//IGNORE', $value);
+        case 'RAW':      $value = min_fix_utf8((string)$value);
                          break;
         case 'INT':      $value = (int)$value;
                          break;
@@ -74,6 +80,49 @@ function min_clean_param($value, $type) {
     return $value;
 }
 
+/**
+ * Minimalistic UTF-8 sanitisation.
+ *
+ * Note: This duplicates fix_utf8() intentionally for now.
+ *
+ * @param string $value
+ * @return string
+ */
+function min_fix_utf8($value) {
+    // Lower error reporting because glibc throws bogus notices.
+    $olderror = error_reporting();
+    if ($olderror & E_NOTICE) {
+        error_reporting($olderror ^ E_NOTICE);
+    }
+
+    static $buggyiconv = null;
+    if ($buggyiconv === null) {
+        $buggyiconv = (!function_exists('iconv') or iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
+    }
+
+    if ($buggyiconv) {
+        if (function_exists('mb_convert_encoding')) {
+            $subst = mb_substitute_character();
+            mb_substitute_character('');
+            $result = mb_convert_encoding($value, 'utf-8', 'utf-8');
+            mb_substitute_character($subst);
+
+        } else {
+            // Warn admins on admin/index.php page.
+            $result = $value;
+        }
+
+    } else {
+        $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
+    }
+
+    if ($olderror & E_NOTICE) {
+        error_reporting($olderror);
+    }
+
+    return $result;
+}
+
 /**
  * This method tries to enable output compression if possible.
  * This function must be called before any output or headers.
@@ -112,7 +161,9 @@ function min_enable_zlib_compression() {
 
 /**
  * Returns the slashargument part of the URL.
- * Note: ".php" is NOT allowed in slasharguments!
+ *
+ * Note: ".php" is NOT allowed in slasharguments,
+ *       it is intended for ASCII characters only.
  *
  * @return string
  */
index f89adbd..b79057d 100644 (file)
@@ -174,9 +174,8 @@ function cron_run() {
 
     // Send login failures notification - brute force protection in moodle is weak,
     // we should at least send notices early in each cron execution
-    if (!empty($CFG->notifyloginfailures)) {
-        notify_login_failures();
-        mtrace(' Notified login failured');
+    if (notify_login_failures()) {
+        mtrace(' Notified login failures');
     }
 
 
@@ -603,16 +602,27 @@ function cron_bc_hack_plugin_functions($plugintype, $plugins) {
  * Note that this function must be only executed from the cron script
  * It uses the cache_flags system to store temporary records, deleting them
  * by name before finishing
+ *
+ * @return bool True if executed, false if not
  */
 function notify_login_failures() {
     global $CFG, $DB, $OUTPUT;
 
+    if (empty($CFG->notifyloginfailures)) {
+        return false;
+    }
+
     $recip = get_users_from_config($CFG->notifyloginfailures, 'moodle/site:config');
 
     if (empty($CFG->lastnotifyfailure)) {
         $CFG->lastnotifyfailure=0;
     }
 
+    // If it has been less than an hour, or if there are no recipients, don't execute.
+    if (((time() - HOURSECS) < $CFG->lastnotifyfailure) || !is_array($recip) || count($recip) <= 0) {
+        return false;
+    }
+
     // we need to deal with the threshold stuff first.
     if (empty($CFG->notifyloginthreshold)) {
         $CFG->notifyloginthreshold = 10; // default to something sensible.
@@ -685,10 +695,8 @@ function notify_login_failures() {
     }
     $rs->close();
 
-    // If we haven't run in the last hour and
-    // we have something useful to report and we
-    // are actually supposed to be reporting to somebody
-    if ((time() - HOURSECS) > $CFG->lastnotifyfailure && $count > 0 && is_array($recip) && count($recip) > 0) {
+    // If we have something useful to report.
+    if ($count > 0) {
         $site = get_site();
         $subject = get_string('notifyloginfailuressubject', '', format_string($site->fullname));
         // Calculate the complete body of notification (start + messages + end)
@@ -710,4 +718,6 @@ function notify_login_failures() {
 
     // Finally, delete all the temp records we have created in cache_flags
     $DB->delete_records_select('cache_flags', "flagtype IN ('login_failure_by_ip', 'login_failure_by_info')");
+
+    return true;
 }
index 76ebc66..173659e 100644 (file)
@@ -1679,6 +1679,7 @@ $capabilities = array(
         'contextlevel' => CONTEXT_BLOCK,
         'archetypes' => array(
             'editingteacher' => CAP_ALLOW,
+            'manager' => CAP_ALLOW
         )
     ),
 
index 0463430..d4b7077 100644 (file)
       </KEYS>
       <INDEXES>
         <INDEX NAME="course" UNIQUE="false" FIELDS="course" NEXT="criteriatype"/>
-        <INDEX NAME="criteriatype" UNIQUE="false" FIELDS="criteriatype" PREVIOUS="course"/>
+        <INDEX NAME="criteriatype" UNIQUE="false" FIELDS="criteriatype" PREVIOUS="course" NEXT="coursecriteriatype"/>
+        <INDEX NAME="coursecriteriatype" UNIQUE="true" FIELDS="course, criteriatype" PREVIOUS="criteriatype"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="course_completion_criteria" COMMENT="Course completion criteria" PREVIOUS="course_completion_aggr_methd" NEXT="course_completion_crit_compl">
         <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" NEXT="course"/>
         <INDEX NAME="course" UNIQUE="false" FIELDS="course" PREVIOUS="userid" NEXT="criteriaid"/>
         <INDEX NAME="criteriaid" UNIQUE="false" FIELDS="criteriaid" PREVIOUS="course" NEXT="timecompleted"/>
-        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="criteriaid"/>
+        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="criteriaid" NEXT="useridcoursecriteriaid"/>
+        <INDEX NAME="useridcoursecriteriaid" UNIQUE="true" FIELDS="userid, course, criteriaid" PREVIOUS="timecompleted"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="course_completion_notify" COMMENT="Course completion notification emails" PREVIOUS="course_completion_crit_compl" NEXT="course_completions">
       <INDEXES>
         <INDEX NAME="userid" UNIQUE="false" FIELDS="userid" NEXT="course"/>
         <INDEX NAME="course" UNIQUE="false" FIELDS="course" PREVIOUS="userid" NEXT="timecompleted"/>
-        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="course"/>
+        <INDEX NAME="timecompleted" UNIQUE="false" FIELDS="timecompleted" PREVIOUS="course" NEXT="useridcourse"/>
+        <INDEX NAME="useridcourse" UNIQUE="true" FIELDS="userid, course" PREVIOUS="timecompleted"/>
       </INDEXES>
     </TABLE>
     <TABLE NAME="enrol" COMMENT="Instances of enrolment plugins used in courses, fields marked as custom have a plugin defined meaning, core does not touch them. Create a new linked table if you need even more custom fields." PREVIOUS="course_completions" NEXT="user_enrolments">
       </KEYS>
     </TABLE>
   </TABLES>
-</XMLDB>
\ No newline at end of file
+</XMLDB>
index f404e01..f0475f6 100644 (file)
@@ -38,6 +38,7 @@ global $DB; // TODO: this is a hack, we should really do something with the SQL
 $logs = array(
     array('module'=>'course', 'action'=>'user report', 'mtable'=>'user', 'field'=>$DB->sql_concat('firstname', "' '" , 'lastname')),
     array('module'=>'course', 'action'=>'view', 'mtable'=>'course', 'field'=>'fullname'),
+    array('module'=>'course', 'action'=>'view section', 'mtable'=>'course_sections', 'field'=>'COALESCE(name, section)'),
     array('module'=>'course', 'action'=>'update', 'mtable'=>'course', 'field'=>'fullname'),
     array('module'=>'course', 'action'=>'enrol', 'mtable'=>'course', 'field'=>'fullname'), // there should be some way to store user id of the enrolled user!
     array('module'=>'course', 'action'=>'unenrol', 'mtable'=>'course', 'field'=>'fullname'), // there should be some way to store user id of the enrolled user!
index c74c724..ba53f99 100644 (file)
@@ -648,5 +648,126 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012052100.00);
     }
 
+    if ($oldversion < 2012052500.03) { // fix invalid course_completion_records MDL-27368
+        //first get all instances of duplicate records
+        $sql = 'SELECT userid, course FROM {course_completions} WHERE (deleted IS NULL OR deleted <> 1) GROUP BY userid, course HAVING (count(id) > 1)';
+        $duplicates = $DB->get_recordset_sql($sql, array());
+
+        foreach ($duplicates as $duplicate) {
+            $pointer = 0;
+            //now get all the records for this user/course
+            $sql = 'userid = ? AND course = ? AND (deleted IS NULL OR deleted <> 1)';
+            $completions = $DB->get_records_select('course_completions', $sql,
+                array($duplicate->userid, $duplicate->course), 'timecompleted DESC, timestarted DESC');
+            $needsupdate = false;
+            $origcompletion = null;
+            foreach ($completions as $completion) {
+                $pointer++;
+                if ($pointer === 1) { //keep 1st record but delete all others.
+                    $origcompletion = $completion;
+                } else {
+                    //we need to keep the "oldest" of all these fields as the valid completion record.
+                    $fieldstocheck = array('timecompleted', 'timestarted', 'timeenrolled');
+                    foreach ($fieldstocheck as $f) {
+                        if ($origcompletion->$f > $completion->$f) {
+                            $origcompletion->$f = $completion->$f;
+                            $needsupdate = true;
+                        }
+                    }
+                    $DB->delete_records('course_completions', array('id'=>$completion->id));
+                }
+            }
+            if ($needsupdate) {
+                $DB->update_record('course_completions', $origcompletion);
+            }
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052500.03);
+    }
+
+    if ($oldversion < 2012052900.00) {
+        // Clean up all duplicate records in the course_completions table in preparation
+        // for adding a new index there.
+        upgrade_course_completion_remove_duplicates(
+            'course_completions',
+            array('userid', 'course'),
+            array('timecompleted', 'timestarted', 'timeenrolled')
+        );
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.00);
+    }
+
+    if ($oldversion < 2012052900.01) {
+        // Add indexes to prevent new duplicates in the course_completions table.
+        // Define index useridcourse (unique) to be added to course_completions
+        $table = new xmldb_table('course_completions');
+        $index = new xmldb_index('useridcourse', XMLDB_INDEX_UNIQUE, array('userid', 'course'));
+
+        // Conditionally launch add index useridcourse
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.01);
+    }
+
+    if ($oldversion < 2012052900.02) {
+        // Clean up all duplicate records in the course_completion_crit_compl table in preparation
+        // for adding a new index there.
+        upgrade_course_completion_remove_duplicates(
+            'course_completion_crit_compl',
+            array('userid', 'course', 'criteriaid'),
+            array('timecompleted')
+        );
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.02);
+    }
+
+    if ($oldversion < 2012052900.03) {
+        // Add indexes to prevent new duplicates in the course_completion_crit_compl table.
+        // Define index useridcoursecriteraid (unique) to be added to course_completion_crit_compl
+        $table = new xmldb_table('course_completion_crit_compl');
+        $index = new xmldb_index('useridcoursecriteraid', XMLDB_INDEX_UNIQUE, array('userid', 'course', 'criteriaid'));
+
+        // Conditionally launch add index useridcoursecriteraid
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.03);
+    }
+
+    if ($oldversion < 2012052900.04) {
+        // Clean up all duplicate records in the course_completion_aggr_methd table in preparation
+        // for adding a new index there.
+        upgrade_course_completion_remove_duplicates(
+            'course_completion_aggr_methd',
+            array('course', 'criteriatype')
+        );
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.04);
+    }
+
+    if ($oldversion < 2012052900.05) {
+        // Add indexes to prevent new duplicates in the course_completion_aggr_methd table.
+        // Define index coursecriteratype (unique) to be added to course_completion_aggr_methd
+        $table = new xmldb_table('course_completion_aggr_methd');
+        $index = new xmldb_index('coursecriteriatype', XMLDB_INDEX_UNIQUE, array('course', 'criteriatype'));
+
+        // Conditionally launch add index coursecriteratype
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        // Main savepoint reached
+        upgrade_main_savepoint(true, 2012052900.05);
+    }
+
     return true;
-}
+}
\ No newline at end of file
index 028538c..5ae728e 100644 (file)
@@ -826,7 +826,7 @@ class mssql_native_moodle_database extends moodle_database {
         } else {
             unset($params['id']);
             if ($returnid) {
-                $returning = "; SELECT SCOPE_IDENTITY()";
+                $returning = "OUTPUT inserted.id";
             }
         }
 
@@ -838,18 +838,29 @@ class mssql_native_moodle_database extends moodle_database {
         $qms    = array_fill(0, count($params), '?');
         $qms    = implode(',', $qms);
 
-        $sql = "INSERT INTO {" . $table . "} ($fields) VALUES($qms) $returning";
+        $sql = "INSERT INTO {" . $table . "} ($fields) $returning VALUES ($qms)";
 
         list($sql, $params, $type) = $this->fix_sql_params($sql, $params);
         $rawsql = $this->emulate_bound_params($sql, $params);
 
         $this->query_start($sql, $params, SQL_QUERY_INSERT);
         $result = mssql_query($rawsql, $this->mssql);
-        $this->query_end($result);
+        // Expected results are:
+        //     - true: insert ok and there isn't returned information.
+        //     - false: insert failed and there isn't returned information.
+        //     - resource: insert executed, need to look for returned (output)
+        //           values to know if the insert was ok or no. Posible values
+        //           are false = failed, integer = insert ok, id returned.
+        $end = false;
+        if (is_bool($result)) {
+            $end = $result;
+        } else if (is_resource($result)) {
+            $end = mssql_result($result, 0, 0); // Fetch 1st column from 1st row.
+        }
+        $this->query_end($end); // End the query with the calculated $end.
 
         if ($returning !== "") {
-            $row = mssql_fetch_assoc($result);
-            $params['id'] = reset($row);
+            $params['id'] = $end;
         }
         $this->free_result($result);
 
index 0ef6eca..410c7ff 100644 (file)
@@ -2078,6 +2078,34 @@ class dml_testcase extends database_driver_testcase {
         $this->assertEquals(1e-300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
         $id = $DB->insert_record($tablename, array('onetext' => 1e300));
         $this->assertEquals(1e300, $DB->get_field($tablename, 'onetext', array('id' => $id)));
+
+        // Test that inserting data violating one unique key leads to error.
+        // Empty the table completely.
+        $this->assertTrue($DB->delete_records($tablename));
+
+        // Add one unique constraint (index).
+        $key = new xmldb_key('testuk', XMLDB_KEY_UNIQUE, array('course', 'oneint'));
+        $dbman->add_key($table, $key);
+
+        // Let's insert one record violating the constraint multiple times.
+        $record = (object)array('course' => 1, 'oneint' => 1);
+        $this->assertTrue($DB->insert_record($tablename, $record, false)); // insert 1st. No problem expected.
+
+        // Re-insert same record, not returning id. dml_exception expected.
+        try {
+            $DB->insert_record($tablename, $record, false);
+            $this->fail("Expecting an exception, none occurred");
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof dml_exception);
+        }
+
+        // Re-insert same record, returning id. dml_exception expected.
+        try {
+            $DB->insert_record($tablename, $record, true);
+            $this->fail("Expecting an exception, none occurred");
+        } catch (exception $e) {
+            $this->assertTrue($e instanceof dml_exception);
+        }
     }
 
     public function test_import_record() {
index aa5539f..9d9c7d6 100644 (file)
@@ -107,7 +107,7 @@ class EvalMath {
         'sin','sinh','arcsin','asin','arcsinh','asinh',
         'cos','cosh','arccos','acos','arccosh','acosh',
         'tan','tanh','arctan','atan','arctanh','atanh',
-        'sqrt','abs','ln','log','exp','floor','ceil','round');
+        'sqrt','abs','ln','log','exp','floor','ceil');
 
     var $fc = array( // calc functions emulation
         'average'=>array(-1), 'max'=>array(-1),  'min'=>array(-1),
index 9ef3b12..1913f45 100644 (file)
@@ -584,19 +584,242 @@ function external_delete_descriptions($component) {
 }
 
 /**
- * Creates a warnings external_multiple_structure
+ * Standard Moodle web service warnings
  *
- * @return external_multiple_structure
- * @since  Moodle 2.3
+ * @package    core_webservice
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+class external_warnings extends external_multiple_structure {
+
+    /**
+     * Constructor
+     *
+     * @since Moodle 2.3
+     */
+    public function __construct() {
+
+        parent::__construct(
+            new external_single_structure(
+                array(
+                    'item' => new external_value(PARAM_TEXT, 'item', VALUE_OPTIONAL),
+                    'itemid' => new external_value(PARAM_INT, 'item id', VALUE_OPTIONAL),
+                    'warningcode' => new external_value(PARAM_ALPHANUM,
+                            'the warning code can be used by the client app to implement specific behaviour'),
+                    'message' => new external_value(PARAM_TEXT,
+                            'untranslated english message to explain the warning')
+                ), 'warning'),
+            'list of warnings', VALUE_OPTIONAL);
+    }
+}
+
+/**
+ * A pre-filled external_value class for text format.
+ *
+ * Default is FORMAT_HTML
+ * This should be used all the time in external xxx_params()/xxx_returns functions
+ * as it is the standard way to implement text format param/return values.
+ *
+ * @package    core_webservice
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
  */
-function external_warnings() {
-    return new external_multiple_structure(
-        new external_single_structure(array(
-            'item' => new external_value(PARAM_TEXT, 'item', VALUE_OPTIONAL),
-            'itemid' => new external_value(PARAM_INT, 'item id', VALUE_OPTIONAL),
-            'warningcode' => new external_value(PARAM_ALPHANUM, 'the warning code can be used by
-                the client app to implement specific behaviour'),
-            'message' => new external_value(PARAM_TEXT, 'untranslated english message to explain the warning')
-                ), 'warning'), 'list of warnings', VALUE_OPTIONAL
-    );
+class external_format_value extends external_value {
+
+    /**
+     * Constructor
+     *
+     * @param string $textfieldname Name of the text field
+     * @param int $required if VALUE_REQUIRED then set standard default FORMAT_HTML
+     * @since Moodle 2.3
+     */
+    public function __construct($textfieldname, $required = VALUE_REQUIRED) {
+
+        $default = ($required == VALUE_DEFAULT) ? FORMAT_HTML : null;
+
+        $desc = $textfieldname . ' format (' . FORMAT_HTML . ' = HTML, '
+                . FORMAT_MOODLE . ' = MOODLE, '
+                . FORMAT_PLAIN . ' = PLAIN or '
+                . FORMAT_MARKDOWN . ' = MARKDOWN)';
+
+        parent::__construct($type, $desc='', $required, $default);
+    }
+}
+
+/**
+ * Validate text field format against known FORMAT_XXX
+ *
+ * @param array $format the format to validate
+ * @return the validated format
+ * @throws coding_exception
+ * @since 2.3
+ */
+function external_validate_format($format) {
+    $allowedformats = array(FORMAT_HTML, FORMAT_MOODLE, FORMAT_PLAIN, FORMAT_MARKDOWN);
+    if (!in_array($format, $allowedformats)) {
+        throw new moodle_exception('formatnotsupported', 'webservice', '' , null,
+                'The format with value=' . $format . ' is not supported by this Moodle site');
+    }
+    return $format;
+}
+
+/**
+ * Format the text to be returned properly as requested by the either the web service server,
+ * either by an internally call.
+ * The caller can change the format (raw, filter, file, fileurl) with the external_settings singleton
+ * All web service servers must set this singleton when parsing the $_GET and $_POST.
+ *
+ * @param string $text The content that may contain ULRs in need of rewriting.
+ * @param int $textformat The text format, by default FORMAT_HTML.
+ * @param int $contextid This parameter and the next two identify the file area to use.
+ * @param string $component
+ * @param string $filearea helps identify the file area.
+ * @param int $itemid helps identify the file area.
+ * @return array text + textformat
+ * @since Moodle 2.3
+ */
+function external_format_text($text, $textformat, $contextid, $component, $filearea, $itemid) {
+    global $CFG;
+
+    // Get settings (singleton).
+    $settings = external_settings::get_instance();
+
+    if ($settings->get_fileurl()) {
+        $text = file_rewrite_pluginfile_urls($text, $settings->get_file(), $contextid, $component, $filearea, $itemid);
+    }
+
+    if (!$settings->get_raw()) {
+        $textformat = FORMAT_HTML; // Force format to HTML when not raw.
+        $text = format_text($text, $textformat,
+                array('noclean' => true, 'para' => false, 'filter' => $settings->get_filter()));
+    }
+
+    return array($text, $textformat);
+}
+
+/**
+ * Singleton to handle the external settings.
+ *
+ * We use singleton to encapsulate the "logic"
+ *
+ * @package    core_webservice
+ * @copyright  2012 Jerome Mouneyrac
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since Moodle 2.3
+ */
+class external_settings {
+
+    /** @var object the singleton instance */
+    public static $instance = null;
+
+    /** @var boolean Should the external function return raw text or formatted */
+    private $raw = false;
+
+    /** @var boolean Should the external function filter the text */
+    private $filter = false;
+
+    /** @var boolean Should the external function rewrite plugin file url */
+    private $fileurl = true;
+
+    /** @var string In which file should the urls be rewritten */
+    private $file = 'webservice/pluginfile.php';
+
+    /**
+     * Constructor - protected - can not be instanciated
+     */
+    protected function __construct() {
+    }
+
+    /**
+     * Clone - private - can not be cloned
+     */
+    private final function __clone() {
+    }
+
+    /**
+     * Return only one instance
+     *
+     * @return object
+     */
+    public static function get_instance() {
+        if (self::$instance === null) {
+            self::$instance = new external_settings;
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * Set raw
+     *
+     * @param boolean $raw
+     */
+    public function set_raw($raw) {
+        $this->raw = $raw;
+    }
+
+    /**
+     * Get raw
+     *
+     * @return boolean
+     */
+    public function get_raw() {
+        return $this->raw;
+    }
+
+    /**
+     * Set filter
+     *
+     * @param boolean $filter
+     */
+    public function set_filter($filter) {
+        $this->filter = $filter;
+    }
+
+    /**
+     * Get filter
+     *
+     * @return boolean
+     */
+    public function get_filter() {
+        return $this->filter;
+    }
+
+    /**
+     * Set fileurl
+     *
+     * @param boolean $fileurl
+     */
+    public function set_fileurl($fileurl) {
+        $this->fileurl = $fileurl;
+    }
+
+    /**
+     * Get fileurl
+     *
+     * @return boolean
+     */
+    public function get_fileurl() {
+        return $this->fileurl;
+    }
+
+    /**
+     * Set file
+     *
+     * @param string $file
+     */
+    public function set_file($file) {
+        $this->file = $file;
+    }
+
+    /**
+     * Get file
+     *
+     * @return string
+     */
+    public function get_file() {
+        return $this->file;
+    }
 }
index e53640b..2771e04 100644 (file)
@@ -303,15 +303,11 @@ abstract class file_info {
     /**
      * Copy content of this file to local storage, overriding current file if needed.
      *
-     * @param int $contextid context ID
-     * @param string $component component
-     * @param string $filearea file area
-     * @param int $itemid item ID
-     * @param string $filepath file path
-     * @param string $filename file name
-     * @return boolean success
+     * @param array|stdClass $filerecord contains contextid, component, filearea,
+     *    itemid, filepath, filename and optionally other attributes of the new file
+     * @return bool success
      */
-    public function copy_to_storage($contextid, $component, $filearea, $itemid, $filepath, $filename) {
+    public function copy_to_storage($filerecord) {
         return false;
     }
 
index a5a37a3..6eb760f 100644 (file)
@@ -505,7 +505,7 @@ class file_info_stored extends file_info {
         if ($this->is_directory()) {
             $filepath = $this->lf->get_filepath();
             $fs = get_file_storage();
-            $storedfiles = $fs->get_area_files($this->context->id, $this->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid(), "");
+            $storedfiles = $fs->get_area_files($this->context->id, $this->get_component(), $this->lf->get_filearea(), $this->lf->get_itemid());
             foreach ($storedfiles as $file) {
                 if (strpos($file->get_filepath(), $filepath) === 0) {
                     $file->delete();
@@ -519,25 +519,21 @@ class file_info_stored extends file_info {
     /**
      * Copy content of this file to local storage, overriding current file if needed.
      *
-     * @param int $contextid context ID
-     * @param string $component file component
-     * @param string $filearea file area
-     * @param int $itemid item ID
-     * @param string $filepath file path
-     * @param string $filename file name
+     * @param array|stdClass $filerecord contains contextid, component, filearea,
+     *    itemid, filepath, filename and optionally other attributes of the new file
      * @return bool success
      */
-    public function copy_to_storage($contextid, $component, $filearea, $itemid, $filepath, $filename) {
+    public function copy_to_storage($filerecord) {
         if (!$this->is_readable() or $this->is_directory()) {
             return false;
         }
+        $filerecord = (array)$filerecord;
 
         $fs = get_file_storage();
-        if ($existing = $fs->get_file($contextid, $component, $filearea, $itemid, $filepath, $filename)) {
+        if ($existing = $fs->get_file($filerecord['contextid'], $filerecord['component'], $filerecord['filearea'], $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename'])) {
             $existing->delete();
         }
-        $file_record = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename);
-        $fs->create_file_from_storedfile($file_record, $this->lf);
+        $fs->create_file_from_storedfile($filerecord, $this->lf);
 
         return true;
     }
index aa46e62..7aa8841 100644 (file)
@@ -2287,14 +2287,22 @@ function send_stored_file($stored_file, $lifetime=86400 , $filter=0, $forcedownl
     if (!empty($options['preview'])) {
         // replace the file with its preview
         $fs = get_file_storage();
-        $stored_file = $fs->get_file_preview($stored_file, $options['preview']);
-        if (!$stored_file) {
-            // unable to create a preview of the file
-            send_header_404();
-            die();
+        $preview_file = $fs->get_file_preview($stored_file, $options['preview']);
+        if (!$preview_file) {
+            // unable to create a preview of the file, send its default mime icon instead
+            if ($options['preview'] === 'tinyicon') {
+                $size = 24;
+            } else if ($options['preview'] === 'thumb') {
+                $size = 90;
+            } else {
+                $size = 256;
+            }
+            $fileicon = file_file_icon($stored_file, $size);
+            send_file($CFG->dirroot.'/pix/'.$fileicon.'.png', basename($fileicon).'.png');
         } else {
             // preview images have fixed cache lifetime and they ignore forced download
             // (they are generated by GD and therefore they are considered reasonably safe).
+            $stored_file = $preview_file;
             $lifetime = DAYSECS;
             $filter = 0;
             $forcedownload = false;
@@ -3612,7 +3620,7 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
             }
 
             // fix file name automatically
-            if ($filename !== 'f1' and $filename !== 'f2') {
+            if ($filename !== 'f1' and $filename !== 'f2' and $filename !== 'f3') {
                 $filename = 'f1';
             }
 
@@ -3625,20 +3633,28 @@ function file_pluginfile($relativepath, $forcedownload, $preview = null) {
                 redirect($theme->pix_url('u/'.$filename, 'moodle')); // intentionally not cached
             }
 
-            if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.png')) {
-                if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'/.jpg')) {
-                    // bad reference - try to prevent future retries as hard as possible!
-                    if ($user = $DB->get_record('user', array('id'=>$context->instanceid), 'id, picture')) {
-                        if ($user->picture == 1 or $user->picture > 10) {
-                            $DB->set_field('user', 'picture', 0, array('id'=>$user->id));
+            if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.png')) {
+                if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', $filename.'.jpg')) {
+                    if ($filename === 'f3') {
+                        // f3 512x512px was introduced in 2.3, there might be only the smaller version.
+                        if (!$file = $fs->get_file($context->id, 'user', 'icon', 0, '/', 'f1.png')) {
+                            $file = $fs->get_file($context->id, 'user', 'icon', 0, '/', 'f1.jpg');
                         }
                     }
-                    // no redirect here because it is not cached
-                    $theme = theme_config::load($themename);
-                    $imagefile = $theme->resolve_image_location('u/'.$filename, 'moodle');
-                    send_file($imagefile, basename($imagefile), 60*60*24*14);
                 }
             }
+            if (!$file) {
+                // bad reference - try to prevent future retries as hard as possible!
+                if ($user = $DB->get_record('user', array('id'=>$context->instanceid), 'id, picture')) {
+                    if ($user->picture > 0) {
+                        $DB->set_field('user', 'picture', 0, array('id'=>$user->id));
+                    }
+                }
+                // no redirect here because it is not cached
+                $theme = theme_config::load($themename);
+                $imagefile = $theme->resolve_image_location('u/'.$filename, 'moodle');
+                send_file($imagefile, basename($imagefile), 60*60*24*14);
+            }
 
             send_stored_file($file, 60*60*24*365, 0, false, array('preview' => $preview)); // enable long caching, there are many images on each page
 
index edac5a5..d5ce7c1 100644 (file)
@@ -366,7 +366,7 @@ class file_storage {
      * Returns all files belonging to given repository
      *
      * @param int $repositoryid
-     * @param string $sort
+     * @param string $sort A fragment of SQL to use for sorting
      */
     public function get_external_files($repositoryid, $sort = 'sortorder, itemid, filepath, filename') {
         global $DB;
@@ -374,8 +374,10 @@ class file_storage {
                   FROM {files} f
              LEFT JOIN {files_reference} r
                        ON f.referencefileid = r.id
-                 WHERE r.repositoryid = ?
-              ORDER BY $sort";
+                 WHERE r.repositoryid = ?";
+        if (!empty($sort)) {
+            $sql .= " ORDER BY {$sort}";
+        }
 
         $result = array();
         $filerecords = $DB->get_records_sql($sql, array($repositoryid));
@@ -392,11 +394,11 @@ class file_storage {
      * @param string $component component
      * @param string $filearea file area
      * @param int $itemid item ID or all files if not specified
-     * @param string $sort sort fields
+     * @param string $sort A fragment of SQL to use for sorting
      * @param bool $includedirs whether or not include directories
      * @return array of stored_files indexed by pathanmehash
      */
-    public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort="sortorder, itemid, filepath, filename", $includedirs = true) {
+    public function get_area_files($contextid, $component, $filearea, $itemid = false, $sort = "sortorder, itemid, filepath, filename", $includedirs = true) {
         global $DB;
 
         $conditions = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea);
@@ -414,8 +416,10 @@ class file_storage {
                  WHERE f.contextid = :contextid
                        AND f.component = :component
                        AND f.filearea = :filearea
-                       $itemidsql
-              ORDER BY $sort";
+                       $itemidsql";
+        if (!empty($sort)) {
+            $sql .= " ORDER BY {$sort}";
+        }
 
         $result = array();
         $filerecords = $DB->get_records_sql($sql, $conditions);
@@ -489,7 +493,7 @@ class file_storage {
      * @param int $filepath directory path
      * @param bool $recursive include all subdirectories
      * @param bool $includedirs include files and directories
-     * @param string $sort sort fields
+     * @param string $sort A fragment of SQL to use for sorting
      * @return array of stored_files indexed by pathanmehash
      */
     public function get_directory_files($contextid, $component, $filearea, $itemid, $filepath, $recursive = false, $includedirs = true, $sort = "filepath, filename") {
@@ -499,6 +503,8 @@ class file_storage {
             return array();
         }
 
+        $orderby = (!empty($sort)) ? " ORDER BY {$sort}" : '';
+
         if ($recursive) {
 
             $dirs = $includedirs ? "" : "AND filename <> '.'";
@@ -512,7 +518,7 @@ class file_storage {
                            AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
                            AND f.id <> :dirid
                            $dirs
-                  ORDER BY $sort";
+                           $orderby";
             $params = array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'dirid'=>$directory->get_id());
 
             $files = array();
@@ -542,7 +548,7 @@ class file_storage {
                                AND f.itemid = :itemid AND f.filename = '.'
                                AND ".$DB->sql_substr("f.filepath", 1, $length)." = :filepath
                                AND f.id <> :dirid
-                      ORDER BY $sort";
+                               $orderby";
                 $reqlevel = substr_count($filepath, '/') + 1;
                 $filerecords = $DB->get_records_sql($sql, $params);
                 foreach ($filerecords as $filerecord) {
@@ -559,7 +565,7 @@ class file_storage {
                            ON f.referencefileid = r.id
                      WHERE f.contextid = :contextid AND f.component = :component AND f.filearea = :filearea AND f.itemid = :itemid
                            AND f.filepath = :filepath AND f.filename <> '.'
-                  ORDER BY $sort";
+                           $orderby";
 
             $filerecords = $DB->get_records_sql($sql, $params);
             foreach ($filerecords as $filerecord) {
@@ -1028,7 +1034,7 @@ class file_storage {
 
         $newrecord->timecreated  = $filerecord->timecreated;
         $newrecord->timemodified = $filerecord->timemodified;
-        $newrecord->mimetype     = empty($filerecord->mimetype) ? $this->mimetype($pathname) : $filerecord->mimetype;
+        $newrecord->mimetype     = empty($filerecord->mimetype) ? $this->mimetype($pathname, $filerecord->filename) : $filerecord->mimetype;
         $newrecord->userid       = empty($filerecord->userid) ? null : $filerecord->userid;
         $newrecord->source       = empty($filerecord->source) ? null : $filerecord->source;
         $newrecord->author       = empty($filerecord->author) ? null : $filerecord->author;
@@ -1154,7 +1160,7 @@ class file_storage {
         list($newrecord->contenthash, $newrecord->filesize, $newfile) = $this->add_string_to_pool($content);
         $filepathname = $this->path_from_hash($newrecord->contenthash) . '/' . $newrecord->contenthash;
         // get mimetype by magic bytes
-        $newrecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($filepathname) : $filerecord->mimetype;
+        $newrecord->mimetype = empty($filerecord->mimetype) ? $this->mimetype($filepathname, $filerecord->filename) : $filerecord->mimetype;
 
         $newrecord->pathnamehash = $this->get_pathname_hash($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid, $newrecord->filepath, $newrecord->filename);
 
@@ -1260,6 +1266,8 @@ class file_storage {
             $filerecord->timemodified = $now;
         }
 
+        $transaction = $DB->start_delegated_transaction();
+
         // Insert file reference record.
         try {
             $referencerecord = new stdClass;
@@ -1292,6 +1300,8 @@ class file_storage {
 
         $this->create_directory($filerecord->contextid, $filerecord->component, $filerecord->filearea, $filerecord->itemid, $filerecord->filepath, $filerecord->userid);
 
+        $transaction->allow_commit();
+
         // Adding repositoryid and reference to file record to create stored_file instance
         $filerecord->repositoryid = $repositoryid;
         $filerecord->reference = $reference;
@@ -1801,11 +1811,15 @@ class file_storage {
      * If file has a known extension, we return the mimetype based on extension.
      * Otherwise (when possible) we try to get the mimetype from file contents.
      *
-     * @param string $pathname
+     * @param string $pathname full path to the file
+     * @param string $filename correct file name with extension, if omitted will be taken from $path
      * @return string
      */
-    public static function mimetype($pathname) {
-        $type = mimeinfo('type', $pathname);
+    public static function mimetype($pathname, $filename = null) {
+        if (empty($filename)) {
+            $filename = $pathname;
+        }
+        $type = mimeinfo('type', $filename);
         if ($type === 'document/unknown' && class_exists('finfo') && file_exists($pathname)) {
             $finfo = new finfo(FILEINFO_MIME_TYPE);
             $type = mimeinfo_from_type('type', $finfo->file($pathname));
index a17d45f..75ab483 100644 (file)
@@ -161,7 +161,7 @@ class stored_file {
                 throw new file_exception('storedfilecannotread', '', $pathname);
             }
         }
-        $mimetype = $this->fs->mimetype($pathname);
+        $mimetype = $this->fs->mimetype($pathname, $this->file_record->filename);
         $this->file_record->mimetype = $mimetype;
 
         $DB->update_record('files', $this->file_record);
@@ -205,6 +205,8 @@ class stored_file {
         // Remove repository info.
         $this->repository = null;
 
+        $transaction = $DB->start_delegated_transaction();
+
         // Remove reference info from DB.
         $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid));
 
@@ -216,6 +218,8 @@ class stored_file {
         $filerecord->referencefileid = null;
         $this->update($filerecord);
 
+        $transaction->allow_commit();
+
         // unset object variable
         unset($this->file_record->repositoryid);
         unset($this->file_record->reference);
@@ -247,6 +251,9 @@ class stored_file {
      */
     public function delete() {
         global $DB;
+
+        $transaction = $DB->start_delegated_transaction();
+
         // If other files referring to this file, we need convert them
         if ($files = $this->fs->get_references_by_storedfile($this)) {
             foreach ($files as $file) {
@@ -256,6 +263,9 @@ class stored_file {
         // Now delete file records in DB
         $DB->delete_records('files', array('id'=>$this->file_record->id));
         $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid));
+
+        $transaction->allow_commit();
+
         // moves pool file to trash if content not needed any more
         $this->fs->deleted_file_cleanup($this->file_record->contenthash);
         return true; // BC only
index d571beb..41feea1 100644 (file)
@@ -77,6 +77,30 @@ class filestoragelib_testcase extends advanced_testcase {
         $previewtinyicon = $fs->get_file_preview($file, 'thumb');
         $this->assertInstanceOf('stored_file', $previewtinyicon);
         $this->assertEquals('6b9864ae1536a8eeef54e097319175a8be12f07c', $previewtinyicon->get_filename());
+
+        $this->setExpectedException('file_exception');
+        $fs->get_file_preview($file, 'amodewhichdoesntexist');
+    }
+
+    public function test_get_file_preview_nonimage() {
+        $this->resetAfterTest(true);
+        $syscontext = context_system::instance();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/textfiles/',
+            'filename'  => 'testtext.txt',
+        );
+
+        $fs = get_file_storage();
+        $fs->create_file_from_string($filerecord, 'text contents');
+        $textfile = $fs->get_file($syscontext->id, $filerecord['component'], $filerecord['filearea'],
+            $filerecord['itemid'], $filerecord['filepath'], $filerecord['filename']);
+
+        $preview = $fs->get_file_preview($textfile, 'thumb');
+        $this->assertFalse($preview);
     }
 
     /**
@@ -209,20 +233,14 @@ class filestoragelib_testcase extends advanced_testcase {
         $this->assertEquals($content, $importedfile->get_content());
     }
 
-    /**
-     * TODO: the tests following this line were added to demonstrate specific Oracle problems in
-     * MDL-33172. They need to be improved to properly evalulate the results of the tests. This is
-     * tracked in MDL-33326.
-     */
     private function setup_three_private_files() {
-        global $USER, $DB;
 
         $this->resetAfterTest(true);
 
         $generator = $this->getDataGenerator();
         $user = $generator->create_user();
+        $this->setUser($user->id);
         $usercontext = context_user::instance($user->id);
-        $USER = $DB->get_record('user', array('id'=>$user->id));
         // create a user private file
         $file1 = new stdClass;
         $file1->contextid = $usercontext->id;
@@ -235,53 +253,120 @@ class filestoragelib_testcase extends advanced_testcase {
 
         $fs = get_file_storage();
         $userfile1 = $fs->create_file_from_string($file1, 'file1 content');
+        $this->assertInstanceOf('stored_file', $userfile1);
+
         $file2 = clone($file1);
         $file2->filename = '2.txt';
         $userfile2 = $fs->create_file_from_string($file2, 'file2 content');
+        $this->assertInstanceOf('stored_file', $userfile2);
 
         $file3 = clone($file1);
         $file3->filename = '3.txt';
         $userfile3 = $fs->create_file_from_storedfile($file3, $userfile2);
+        $this->assertInstanceOf('stored_file', $userfile3);
 
         $user->ctxid = $usercontext->id;
 
         return $user;
     }
 
-
     public function test_get_area_files() {
         $user = $this->setup_three_private_files();
         $fs = get_file_storage();
 
         // Get area files with default options.
         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
+
         // Should be the two files we added plus the folder.
         $this->assertEquals(4, count($areafiles));
 
+        // Verify structure.
+        foreach ($areafiles as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
+
         // Get area files without a folder.
-        $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'sortorder', false);
+        $folderlessfiles = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'sortorder', false);
         // Should be the two files without folder.
-        $this->assertEquals(3, count($areafiles));
+        $this->assertEquals(3, count($folderlessfiles));
+
+        // Verify structure.
+        foreach ($folderlessfiles as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
 
         // Get area files ordered by id (was breaking on oracle).
-        $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'id', false);
+        $filesbyid  = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'id', false);
         // Should be the two files without folder.
-        $this->assertEquals(3, count($areafiles));
+        $this->assertEquals(3, count($filesbyid));
+
+        // Verify structure.
+        foreach ($filesbyid as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
 
         // Test with an itemid with no files
         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private', 666, 'sortorder', false);
-        // Should none
-        $this->assertEquals(0, count($areafiles));
+        // Should be none.
+        $this->assertEmpty($areafiles);
     }
 
     public function test_get_area_tree() {
         $user = $this->setup_three_private_files();
         $fs = get_file_storage();
 
+
         // Get area files with default options.
-        $areafiles = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
-        $areafiles = $fs->get_area_tree($user->ctxid, 'user', 'private', 666);
-        //TODO: verify result!! MDL-33326
+        $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
+        $this->assertEmpty($areatree['subdirs']);
+        $this->assertNotEmpty($areatree['files']);
+        $this->assertCount(3, $areatree['files']);
+
+        // Ensure an empty try with a fake itemid.
+        $emptytree = $fs->get_area_tree($user->ctxid, 'user', 'private', 666);
+        $this->assertEmpty($emptytree['subdirs']);
+        $this->assertEmpty($emptytree['files']);
+
+        // Create a subdir.
+        $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
+        $this->assertInstanceOf('stored_file', $dir);
+
+        // Add a file to the subdir.
+        $filerecord = array(
+            'contextid' => $user->ctxid,
+            'component' => 'user',
+            'filearea'  => 'private',
+            'itemid'    => 0,
+            'filepath'  => '/testsubdir/',
+            'filename'  => 'test-get-area-tree.txt',
+        );
+
+        $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
+        $this->assertInstanceOf('stored_file', $directoryfile);
+
+        $areatree = $fs->get_area_tree($user->ctxid, 'user', 'private', 0);
+
+        // At the top level there should still be 3 files.
+        $this->assertCount(3, $areatree['files']);
+
+        // There should now be a subdirectory.
+        $this->assertCount(1, $areatree['subdirs']);
+
+        // The test subdir is named testsubdir.
+        $subdir = $areatree['subdirs']['testsubdir'];
+        $this->assertNotEmpty($subdir);
+        // It should have one file we added.
+        $this->assertCount(1, $subdir['files']);
+        // And no subdirs itself.
+        $this->assertCount(0, $subdir['subdirs']);
+
+        // Verify the file is the one we added.
+        $subdirfile = reset($subdir['files']);
+        $this->assertInstanceOf('stored_file', $subdirfile);
+        $this->assertEquals($filerecord['filename'], $subdirfile->get_filename());
     }
 
     public function test_get_file_by_id() {
@@ -294,6 +379,10 @@ class filestoragelib_testcase extends advanced_testcase {
         $filebyid = reset($areafiles);
         $shouldbesame = $fs->get_file_by_id($filebyid->get_id());
         $this->assertEquals($filebyid->get_contenthash(), $shouldbesame->get_contenthash());
+
+        // Test an id which doens't exist.
+        $doesntexist = $fs->get_file_by_id(99999);
+        $this->assertFalse($doesntexist);
     }
 
     public function test_get_file_by_hash() {
@@ -305,6 +394,10 @@ class filestoragelib_testcase extends advanced_testcase {
         $filebyhash = reset($areafiles);
         $shouldbesame = $fs->get_file_by_hash($filebyhash->get_pathnamehash());
         $this->assertEquals($filebyhash->get_id(), $shouldbesame->get_id());
+
+        // Test an hash which doens't exist.
+        $doesntexist = $fs->get_file_by_hash('DOESNTEXIST');
+        $this->assertFalse($doesntexist);
     }
 
     public function test_get_references_by_storedfile() {
@@ -316,7 +409,7 @@ class filestoragelib_testcase extends advanced_testcase {
 
         $testfile = reset($areafiles);
         $references = $fs->get_references_by_storedfile($testfile);
-        //TODO: verify result!! MDL-33326
+        // TODO MDL-33368 Verify result!!
     }
 
     public function test_get_external_files() {
@@ -327,45 +420,132 @@ class filestoragelib_testcase extends advanced_testcase {
         $userrepository = reset($repos);
         $this->assertInstanceOf('repository', $userrepository);
 
-        // this should break on oracle
+        // This should break on oracle.
         $fs->get_external_files($userrepository->id, 'id');
-        //TODO: verify result!! MDL-33326
-     }
+        // TODO MDL-33368 Verify result!!
+    }
+
+    public function test_create_directory_contextid_negative() {
+        $fs = get_file_storage();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory(-1, 'core', 'unittest', 0, '/');
+    }
+
+    public function test_create_directory_contextid_invalid() {
+        $fs = get_file_storage();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory('not an int', 'core', 'unittest', 0, '/');
+    }
+
+    public function test_create_directory_component_invalid() {
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory($syscontext->id, 'bad/component', 'unittest', 0, '/');
+    }
+
+    public function test_create_directory_filearea_invalid() {
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory($syscontext->id, 'core', 'bad-filearea', 0, '/');
+    }
+
+    public function test_create_directory_itemid_negative() {
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory($syscontext->id, 'core', 'unittest', -1, '/');
+    }
+
+    public function test_create_directory_itemid_invalid() {
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory($syscontext->id, 'core', 'unittest', 'notanint', '/');
+    }
+
+    public function test_create_directory_filepath_invalid() {
+        $fs = get_file_storage();
+        $syscontext = context_system::instance();
+
+        $this->setExpectedException('file_exception');
+        $fs->create_directory($syscontext->id, 'core', 'unittest', 0, '/not-with-trailing/or-leading-slash');
+    }
 
     public function test_get_directory_files() {
         $user = $this->setup_three_private_files();
         $fs = get_file_storage();
 
-        // This should also break on oracle.
-        $fs->create_directory($user->ctxid, 'user', 'private', 0, '/');
-        //TODO: verify result!! MDL-33326
+        $dir = $fs->create_directory($user->ctxid, 'user', 'private', 0, '/testsubdir/');
+        $this->assertInstanceOf('stored_file', $dir);
 
-        // Don't recurse with dirs
-        $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, true, 'id');
-        //TODO: verify result!! MDL-33326
+        // Add a file to the subdir.
+        $filerecord = array(
+            'contextid' => $user->ctxid,
+            'component' => 'user',
+            'filearea'  => 'private',
+            'itemid'    => 0,
+            'filepath'  => '/testsubdir/',
+            'filename'  => 'test-get-area-tree.txt',
+        );
 
-        // Don't recurse without dirs
-        $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, false, 'id');
-        //TODO: verify result!! MDL-33326
+        $directoryfile = $fs->create_file_from_string($filerecord, 'Test content');
+        $this->assertInstanceOf('stored_file', $directoryfile);
 
-        // Recurse with dirs
-        $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, true, 'id');
-        //TODO: verify result!! MDL-33326
-        // Recurse without dirs
-        $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, false, 'id');
-        //TODO: verify result!! MDL-33326
+        // Don't recurse without dirs
+        $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, false, 'id');
+        // 3 files only.
+        $this->assertCount(3, $files);
+        foreach ($files as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
+
+        // Don't recurse with dirs.
+        $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', false, true, 'id');
+        // 3 files + 1 directory.
+        $this->assertCount(4, $files);
+        foreach ($files as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
+
+        // Recurse with dirs.
+        $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, true, 'id');
+        // 3 files + 1 directory +  1 subdir file.
+        $this->assertCount(5, $files);
+        foreach ($files as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
+
+        // Recurse without dirs.
+        $files = $fs->get_directory_files($user->ctxid, 'user', 'private', 0, '/', true, false, 'id');
+        // 3 files +  1 subdir file.
+        $this->assertCount(4, $files);
+        foreach ($files as $key => $file) {
+            $this->assertInstanceOf('stored_file', $file);
+            $this->assertEquals($key, $file->get_pathnamehash());
+        }
     }
 
     public function test_search_references() {
         $fs = get_file_storage();
         $references = $fs->search_references('testsearch');
-        //TODO: verify result!! MDL-33326
+        // TODO MDL-33368 Verify result!!
     }
 
     public function test_search_references_count() {
         $fs = get_file_storage();
         $references = $fs->search_references_count('testsearch');
-        //TODO: verify result!! MDL-33326
+        // TODO MDL-33368 Verify result!!
     }
 
     public function test_delete_area_files() {
@@ -383,6 +563,20 @@ class filestoragelib_testcase extends advanced_testcase {
         $this->assertEquals(0, count($areafiles));
     }
 
+    public function test_delete_area_files_itemid() {
+        $user = $this->setup_three_private_files();
+        $fs = get_file_storage();
+
+        // Get area files with default options.
+        $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
+        // Should be the two files we added plus the folder.
+        $this->assertEquals(4, count($areafiles));
+        $fs->delete_area_files($user->ctxid, 'user', 'private', 9999);
+
+        $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
+        $this->assertEquals(4, count($areafiles));
+    }
+
     public function test_delete_area_files_select() {
         $user = $this->setup_three_private_files();
         $fs = get_file_storage();
@@ -397,4 +591,579 @@ class filestoragelib_testcase extends advanced_testcase {
         // Should be the two files we added plus the folder.
         $this->assertEquals(0, count($areafiles));
     }
+
+    public function test_create_file_from_url() {
+        $this->resetAfterTest(true);
+
+        $syscontext = context_system::instance();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/downloadtest/',
+        );
+        $url = 'http://download.moodle.org/unittest/test.html';
+
+        $fs = get_file_storage();
+
+        // Test creating file without filename.
+        $file1 = $fs->create_file_from_url($filerecord, $url);
+        $this->assertInstanceOf('stored_file', $file1);
+
+        // Set filename.
+        $filerecord['filename'] = 'unit-test-filename.html';
+        $file2 = $fs->create_file_from_url($filerecord, $url);
+        $this->assertInstanceOf('stored_file', $file2);
+
+        // Use temporary file.
+        $filerecord['filename'] = 'unit-test-with-temp-file.html';
+        $file3 = $fs->create_file_from_url($filerecord, $url, null, true);
+        $file3 = $this->assertInstanceOf('stored_file', $file3);
+    }
+
+    public function test_cron() {
+        $this->resetAfterTest(true);
+
+        // Note: this is only testing DB compatibility atm, rather than
+        // that work is done.
+        $fs = get_file_storage();
+
+        $this->expectOutputRegex('/Cleaning up/');
+        $fs->cron();
+    }
+
+    public function test_is_area_empty() {
+        $user = $this->setup_three_private_files();
+        $fs = get_file_storage();
+
+        $this->assertFalse($fs->is_area_empty($user->ctxid, 'user', 'private'));
+
+        // File area with madeup itemid should be empty.
+        $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999));
+        // Still empty with dirs included.
+        $this->assertTrue($fs->is_area_empty($user->ctxid, 'user', 'private', 9999, false));
+    }
+
+    public function test_move_area_files_to_new_context() {
+        $this->resetAfterTest(true);
+
+        // Create a course with a page resource.
+        $course = $this->getDataGenerator()->create_course();
+        $page1 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
+        $page1context = context_module::instance($page1->cmid);
+
+        // Add a file to the page.
+        $fs = get_file_storage();
+        $filerecord = array(
+            'contextid' => $page1context->id,
+            'component' => 'mod_page',
+            'filearea'  => 'content',
+            'itemid'    => 0,
+            'filepath'  => '/',
+            'filename'  => 'unit-test-file.txt',
+        );
+
+        $originalfile = $fs->create_file_from_string($filerecord, 'Test content');
+        $this->assertInstanceOf('stored_file', $originalfile);
+
+        $pagefiles = $fs->get_area_files($page1context->id, 'mod_page', 'content', 0, 'sortorder', false);
+        // Should be one file in filearea.
+        $this->assertFalse($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
+
+        // Create a new page.
+        $page2 = $this->getDataGenerator()->create_module('page', array('course'=>$course->id));
+        $page2context = context_module::instance($page2->cmid);
+
+        // Newly created page area is empty.
+        $this->assertTrue($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
+
+        // Move the files.
+        $fs->move_area_files_to_new_context($page1context->id, $page2context->id, 'mod_page', 'content');
+
+        // Page2 filearea should no longer be empty.
+        $this->assertFalse($fs->is_area_empty($page2context->id, 'mod_page', 'content'));
+
+        // Page1 filearea should now be empty.
+        $this->assertTrue($fs->is_area_empty($page1context->id, 'mod_page', 'content'));
+
+        $page2files = $fs->get_area_files($page2context->id, 'mod_page', 'content', 0, 'sortorder', false);
+        $movedfile = reset($page2files);
+
+        // The two files should have the same content hash.
+        $this->assertEquals($movedfile->get_contenthash(), $originalfile->get_contenthash());
+    }
+
+    public function test_convert_image() {
+        global $CFG;
+
+        $this->resetAfterTest(false);
+
+        $filepath = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+        $syscontext = context_system::instance();
+        $filerecord = array(
+            'contextid' => $syscontext->id,
+            'component' => 'core',
+            'filearea'  => 'unittest',
+            'itemid'    => 0,
+            'filepath'  => '/images/',
+            'filename'  => 'testimage.jpg',
+        );
+
+        $fs = get_file_storage();
+        $original = $fs->create_file_from_pathname($filerecord, $filepath);
+
+        $filerecord['filename'] = 'testimage-converted-10x10.jpg';
+        $converted = $fs->convert_image($filerecord, $original, 10, 10, true, 100);
+        $this->assertInstanceOf('stored_file', $converted);
+
+        $filerecord['filename'] = 'testimage-convereted-nosize.jpg';
+        $converted = $fs->convert_image($filerecord, $original);
+        $this->assertInstanceOf('stored_file', $converted);
+    }
+
+    private function generate_file_record() {
+        $syscontext = context_system::instance();
+        $filerecord = new stdClass();
+        $filerecord->contextid = $syscontext->id;
+        $filerecord->component = 'core';
+        $filerecord->filearea = 'phpunit';
+        $filerecord->filepath = '/';
+        $filerecord->filename = 'testfile.txt';
+        $filerecord->itemid = 0;
+
+        return $filerecord;
+    }
+
+    public function test_create_file_from_storedfile_file_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $this->setExpectedException('file_exception');
+        // Create a file from a file id which doesn't exist.
+        $fs->create_file_from_storedfile($filerecord,  9999);
+    }
+
+    public function test_create_file_from_storedfile_contextid_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->contextid = 'invalid';
+
+        $this->setExpectedException('file_exception', 'Invalid contextid');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_component_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->component = 'bad/component';
+
+        $this->setExpectedException('file_exception', 'Invalid component');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_filearea_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->filearea = 'bad-filearea';
+
+        $this->setExpectedException('file_exception', 'Invalid filearea');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_itemid_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->itemid = 'bad-itemid';
+
+        $this->setExpectedException('file_exception', 'Invalid itemid');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_filepath_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->filepath = 'a-/bad/-filepath';
+
+        $this->setExpectedException('file_exception', 'Invalid file path');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_filename_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = '';
+
+        $this->setExpectedException('file_exception', 'Invalid file name');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_timecreated_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->timecreated = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timecreated');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_timemodified_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'invalid.txt';
+        $filerecord->timemodified  = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timemodified');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile_duplicate() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+
+        $fs = get_file_storage();
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        // Creating a file validating unique constraint.
+        $this->setExpectedException('stored_file_creation_exception');
+        $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+    }
+
+    public function test_create_file_from_storedfile() {
+        $this->resetAfterTest(true);
+
+        $syscontext = context_system::instance();
+
+        $filerecord = new stdClass();
+        $filerecord->contextid = $syscontext->id;
+        $filerecord->component = 'core';
+        $filerecord->filearea = 'phpunit';
+        $filerecord->filepath = '/';
+        $filerecord->filename = 'testfile.txt';
+        $filerecord->itemid = 0;
+
+        $fs = get_file_storage();
+
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+        $this->assertInstanceOf('stored_file', $file1);
+
+        $filerecord->filename = 'test-create-file-from-storedfile.txt';
+        $file2 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+        $this->assertInstanceOf('stored_file', $file2);
+
+        // These will be normalised to current time..
+        $filerecord->timecreated = -100;
+        $filerecord->timemodified= -100;
+        $filerecord->filename = 'test-create-file-from-storedfile-bad-dates.txt';
+
+        $file3 = $fs->create_file_from_storedfile($filerecord, $file1->get_id());
+        $this->assertInstanceOf('stored_file', $file3);
+
+        $this->assertNotEquals($file3->get_timemodified(), $filerecord->timemodified);
+        $this->assertNotEquals($file3->get_timecreated(), $filerecord->timecreated);
+    }
+
+    public function test_create_file_from_string_contextid_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->contextid = 'invalid';
+
+        $this->setExpectedException('file_exception', 'Invalid contextid');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_component_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->component = 'bad/component';
+
+        $this->setExpectedException('file_exception', 'Invalid component');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_filearea_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filearea = 'bad-filearea';
+
+        $this->setExpectedException('file_exception', 'Invalid filearea');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_itemid_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->itemid = 'bad-itemid';
+
+        $this->setExpectedException('file_exception', 'Invalid itemid');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_filepath_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filepath = 'a-/bad/-filepath';
+
+        $this->setExpectedException('file_exception', 'Invalid file path');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_filename_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filename = '';
+
+        $this->setExpectedException('file_exception', 'Invalid file name');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_timecreated_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->timecreated = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timecreated');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_timemodified_invalid() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->timemodified  = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timemodified');
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_string_duplicate() {
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $file1 = $fs->create_file_from_string($filerecord, 'text contents');
+
+        // Creating a file validating unique constraint.
+        $this->setExpectedException('stored_file_creation_exception');
+        $file2 = $fs->create_file_from_string($filerecord, 'text contents');
+    }
+
+    public function test_create_file_from_pathname_contextid_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->contextid = 'invalid';
+
+        $this->setExpectedException('file_exception', 'Invalid contextid');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_component_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->component = 'bad/component';
+
+        $this->setExpectedException('file_exception', 'Invalid component');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_filearea_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filearea = 'bad-filearea';
+
+        $this->setExpectedException('file_exception', 'Invalid filearea');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_itemid_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->itemid = 'bad-itemid';
+
+        $this->setExpectedException('file_exception', 'Invalid itemid');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_filepath_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filepath = 'a-/bad/-filepath';
+
+        $this->setExpectedException('file_exception', 'Invalid file path');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_filename_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->filename = '';
+
+        $this->setExpectedException('file_exception', 'Invalid file name');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_timecreated_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->timecreated = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timecreated');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_timemodified_invalid() {
+        global $CFG;
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $this->resetAfterTest(true);
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $filerecord->timemodified  = 'today';
+
+        $this->setExpectedException('file_exception', 'Invalid file timemodified');
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+    }
+
+    public function test_create_file_from_pathname_duplicate_file() {
+        global $CFG;
+        $this->resetAfterTest(true);
+
+        $path = $CFG->dirroot.'/lib/filestorage/tests/fixtures/testimage.jpg';
+
+        $filerecord = $this->generate_file_record();
+        $fs = get_file_storage();
+
+        $file1 = $fs->create_file_from_pathname($filerecord, $path);
+        $this->assertInstanceOf('stored_file', $file1);
+
+        // Creating a file validating unique constraint.
+        $this->setExpectedException('stored_file_creation_exception');
+        $file2 = $fs->create_file_from_pathname($filerecord, $path);
+    }
 }
index bf7316a..2e1514c 100644 (file)
@@ -91,6 +91,7 @@ M.form_dndupload.init = function(Y, options) {
             this.maxfiles = options.maxfiles;
             this.maxbytes = options.maxbytes;
             this.itemid = options.itemid;
+            this.author = options.author;
             this.container = this.Y.one('#'+options.containerid);
 
             if (options.filemanager) {
@@