Merge branch 'MDL-33554' of git://github.com/mouneyrac/moodle
authorSam Hemelryk <sam@moodle.com>
Wed, 13 Jun 2012 23:20:16 +0000 (11:20 +1200)
committerSam Hemelryk <sam@moodle.com>
Wed, 13 Jun 2012 23:20:16 +0000 (11:20 +1200)
260 files changed:
admin/index.php
admin/oauth2callback.php
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/repositoryinstance.php
admin/settings/development.php
admin/settings/server.php
admin/settings/subsystems.php
admin/settings/top.php
admin/tool/xmldb/lang/en/tool_xmldb.php
backup/backup.class.php
backup/backup.php
backup/bb/restore_bb.php
backup/cc/cc2moodle.php
backup/moodle2/restore_final_task.class.php
backup/moodle2/restore_stepslib.php
backup/util/dbops/restore_controller_dbops.class.php
backup/util/dbops/restore_dbops.class.php
backup/util/dbops/tests/dbops_test.php
backup/util/factories/backup_factory.class.php
backup/util/helper/backup_cron_helper.class.php
backup/util/helper/backup_general_helper.class.php
backup/util/helper/backup_helper.class.php
backup/util/ui/backup_ui_setting.class.php
config-dist.php
course/dndupload.js
course/dnduploadlib.php
course/externallib.php
course/format/topics/lib.php
course/format/weeks/lib.php
course/lib.php
course/publish/backup.php
course/renderer.php
course/tests/courselib_test.php
course/view.php
course/yui/dragdrop/dragdrop.js
course/yui/modchooser/modchooser.js
enrol/externallib.php
enrol/mnet/addinstance_form.php
enrol/mnet/enrol.php
enrol/yui/notification/notification.js
files/externallib.php
files/renderer.php
grade/edit/tree/grade.php
grade/grading/form/guide/edit_form.php
grade/import/csv/index.php
group/externallib.php
install.php
install/lang/es_mx/admin.php
install/lang/sv_fi/install.php [new file with mode: 0644]
lang/en/admin.php
lang/en/hub.php
lang/en/repository.php
lang/en/webservice.php
lib/completionlib.php
lib/configonlylib.php
lib/cronlib.php
lib/db/access.php
lib/db/install.xml
lib/db/log.php
lib/db/upgrade.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_exceptions.php
lib/filestorage/file_storage.php
lib/filestorage/stored_file.php
lib/filestorage/tests/file_storage_test.php
lib/form/editor.php
lib/form/filemanager.js
lib/googleapi.php
lib/messagelib.php
lib/moodlelib.php
lib/navigationlib.php
lib/oauthlib.php
lib/outputrequirementslib.php
lib/phpunit/bootstraplib.php
lib/phpunit/lib.php
lib/pluginlib.php
lib/rsslib.php
lib/setup.php
lib/setuplib.php
lib/tablelib.php
lib/tests/configonlylib_test.php [new file with mode: 0644]
lib/tests/mathslib_test.php
lib/tests/moodlelib_test.php
lib/tests/textlib_test.php
lib/textlib.class.php
lib/webdavlib.php
lib/yui/chooserdialogue/chooserdialogue.js
lib/yui/dragdrop/dragdrop.js
local/readme.txt
message/defaultoutputs.php
message/externallib.php
message/lib.php
mod/assign/db/messages.php
mod/assign/feedback/comments/db/install.xml
mod/assign/feedback/comments/locallib.php
mod/assign/feedback/file/db/install.xml
mod/assign/feedbackplugin.php
mod/assign/gradeform.php
mod/assign/gradingtable.php
mod/assign/lang/en/assign.php
mod/assign/lib.php
mod/assign/locallib.php
mod/assign/module.js
mod/assign/portfolio_callback.php
mod/assign/renderable.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/version.php
mod/assignment/lib.php
mod/assignment/type/online/assignment.class.php
mod/book/locallib.php
mod/book/tool/importhtml/locallib.php
mod/book/tool/print/index.php
mod/chat/lang/en/chat.php
mod/choice/lang/en/choice.php
mod/data/field/file/mod.html
mod/data/field/picture/mod.html
mod/data/import.php
mod/data/lang/en/data.php
mod/data/lib.php
mod/data/locallib.php
mod/folder/lang/en/folder.php
mod/folder/lib.php
mod/forum/lang/en/forum.php
mod/forum/lib.php
mod/forum/post_form.php
mod/forum/unsubscribeall.php
mod/glossary/formats/entrylist/entrylist_format.php
mod/glossary/lang/en/glossary.php
mod/glossary/lib.php
mod/glossary/locallib.php
mod/glossary/styles.css
mod/label/lang/en/label.php
mod/lesson/lang/en/lesson.php
mod/page/lang/en/page.php
mod/page/lib.php
mod/quiz/comment.php
mod/quiz/cronlib.php
mod/quiz/edit.php
mod/quiz/lang/en/quiz.php
mod/quiz/lib.php
mod/quiz/mod_form.php
mod/quiz/processattempt.php
mod/quiz/report/grading/report.php
mod/quiz/summary.php
mod/resource/lib.php
mod/resource/mod_form.php
mod/resource/pix/icon.gif
mod/scorm/lang/en/scorm.php
mod/scorm/lib.php
mod/survey/lang/en/survey.php
mod/url/lang/en/url.php
mod/url/lib.php
mod/wiki/lang/en/wiki.php
mod/workshop/submission.php
notes/externallib.php
phpunit.xml.dist
pix/f/archive.png [new file with mode: 0644]
pix/f/audio.png
pix/f/avi.png
pix/f/base.png [new file with mode: 0644]
pix/f/bmp.png
pix/f/calc.png [new file with mode: 0644]
pix/f/chart.png [new file with mode: 0644]
pix/f/database.png
pix/f/document.png
pix/f/draw.png [new file with mode: 0644]
pix/f/eps.png
pix/f/flash.png
pix/f/gif.png
pix/f/image-128.png
pix/f/image-24.png
pix/f/image-256.png
pix/f/image-32.png
pix/f/image-48.png
pix/f/image-64.png
pix/f/image-72.png
pix/f/image-80.png
pix/f/image-96.png
pix/f/image.png
pix/f/impress.png [new file with mode: 0644]
pix/f/isf.gif [deleted file]
pix/f/isf.png [new file with mode: 0644]
pix/f/jpeg.png
pix/f/markup.png [new file with mode: 0644]
pix/f/math.png [new file with mode: 0644]
pix/f/moodle.png
pix/f/mp3.png
pix/f/mpeg.png
pix/f/oth.png
pix/f/pdf.png
pix/f/png.png
pix/f/powerpoint.png
pix/f/psd.png
pix/f/quicktime.png [new file with mode: 0644]
pix/f/sourcecode.png [new file with mode: 0644]
pix/f/spreadsheet.png
pix/f/text.png
pix/f/tiff.png
pix/f/video.png
pix/f/wav.png
pix/f/wmv.png
pix/f/writer.png [new file with mode: 0644]
portfolio/googledocs/lib.php
portfolio/picasa/lib.php
question/behaviour/behaviourbase.php
question/previewlib.php
question/type/essay/renderer.php
question/type/multianswer/renderer.php
rating/index.php
repository/alfresco/lib.php
repository/draftfiles_manager.php
repository/dropbox/lang/en/repository_dropbox.php
repository/equella/callback.php [new file with mode: 0644]
repository/equella/db/access.php [new file with mode: 0644]
repository/equella/lang/en/repository_equella.php [new file with mode: 0644]
repository/equella/lib.php [new file with mode: 0644]
repository/equella/pix/icon.png [new file with mode: 0644]
repository/equella/version.php [new file with mode: 0644]
repository/filepicker.js
repository/filepicker.php
repository/filesystem/lib.php
repository/flickr_public/lib.php
repository/googledocs/lib.php
repository/lib.php
repository/picasa/lib.php
repository/recent/lib.php
repository/repository_ajax.php
repository/upload/lib.php
repository/webdav/lib.php
theme/base/pix/fp/dnd_arrow.gif [new file with mode: 0644]
theme/base/pix/fp/dnd_arrow.png [deleted file]
theme/base/style/core.css
theme/base/style/course.css
theme/base/style/filemanager.css
theme/formal_white/db/upgrade.php
theme/formal_white/lang/en/theme_formal_white.php
theme/formal_white/layout/frontpage.php
theme/formal_white/layout/general.php
theme/formal_white/layout/report.php
theme/formal_white/settings.php
theme/formal_white/style/formal_white.css
theme/formal_white/version.php
theme/yui_combo.php
user/externallib.php
user/lib.php
version.php
webservice/lib.php
webservice/rest/locallib.php
webservice/rest/server.php

index bf3eea5..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);
 
@@ -434,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) {
@@ -444,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);
index 364c002..c032a9c 100644 (file)
@@ -33,6 +33,14 @@ 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_URL);
+$state = required_param('state', PARAM_LOCALURL);
 
-redirect(new moodle_url($state, array('code' => $code)));
+$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..42f1c07 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);
+    $registeringhuburl = null;
+    if (!empty($unlistedhuburl)) {
+        if (clean_param($unlistedhuburl, PARAM_URL) !== '') {
+            $registeringhuburl = $unlistedhuburl;
+        }
+    } else if (!empty($selectedhuburl)) {
+        $registeringhuburl = $selectedhuburl;
+    }
+
+    // a hub has been selected, redirect to the hub registration page
+    if (empty($cancel) and !empty($registeringhuburl) and confirm_sesskey()) {
+        $hubname = optional_param(clean_param($registeringhuburl, PARAM_ALPHANUMEXT), '', PARAM_TEXT);
+        $params = array('sesskey' => sesskey(), 'huburl' => $registeringhuburl,
+            '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 9b689a6..c064ca6 100644 (file)
@@ -242,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 = '';
 
@@ -257,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 ///
@@ -381,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
@@ -512,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
      *
index fe75146..fa4a230 100644 (file)
@@ -41,7 +41,7 @@ if ($edit){
     $pagename = 'repositoryinstancenew';
 }
 
-admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstances.php'));
+admin_externalpage_setup($pagename, '', null, new moodle_url('/admin/repositoryinstance.php'));
 require_capability('moodle/site:config', $context);
 
 $baseurl = new moodle_url("/$CFG->admin/repositoryinstance.php", array('sesskey'=>sesskey()));
index e55e1f2..5c41b86 100644 (file)
@@ -14,6 +14,7 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $temp->add(new admin_setting_configcheckbox('enablegroupmembersonly', new lang_string('enablegroupmembersonly', 'admin'), new lang_string('configenablegroupmembersonly', 'admin'), 0));
 
     $temp->add(new admin_setting_configcheckbox('dndallowtextandlinks', new lang_string('dndallowtextandlinks', 'admin'), new lang_string('configdndallowtextandlinks', 'admin'), 0));
+    $temp->add(new admin_setting_configcheckbox('enablecssoptimiser', new lang_string('enablecssoptimiser','admin'), new lang_string('enablecssoptimiser_desc','admin'), 0));
 
     $ADMIN->add('experimental', $temp);
 
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 7c70af3..0e629b8 100644 (file)
@@ -45,6 +45,4 @@ if ($hassiteconfig) { // speedup for non-admins, add all caps used on this page
     $checkbox->set_affects_modinfo(true);
 
     $optionalsubsystems->add(new admin_setting_configcheckbox('enableplagiarism', new lang_string('enableplagiarism','plagiarism'), new lang_string('configenableplagiarism','plagiarism'), 0));
-
-    $optionalsubsystems->add(new admin_setting_configcheckbox('enablecssoptimiser', new lang_string('enablecssoptimiser','admin'), new lang_string('enablecssoptimiser_desc','admin'), 0));
 }
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 a378c26..4c502d4 100644 (file)
@@ -69,6 +69,7 @@ $string['documentationintro'] = 'This documentation is generated automatically f
 $string['down'] = 'Down';
 $string['duplicate'] = 'Duplicate';
 $string['duplicatefieldname'] = 'Another field with that name exists';
+$string['duplicatefieldsused'] = 'Duplicate fields used';
 $string['duplicatekeyname'] = 'Another key with that name exists';
 $string['duplicatetablename'] = 'Another table with that name exists';
 $string['edit'] = 'Edit';
@@ -88,6 +89,7 @@ $string['field'] = 'Field';
 $string['fieldnameempty'] = 'Name field empty';
 $string['fields'] = 'Fields';
 $string['fieldsnotintable'] = 'Field doesn\'t exist in table';
+$string['fieldsusedinindex'] = 'This field is used as index';
 $string['fieldsusedinkey'] = 'This field is used as key.';
 $string['filenotwriteable'] = 'File not writeable';
 $string['fkviolationdetails'] = 'Foreign key {$a->keyname} on table {$a->tablename} is violated by {$a->numviolations} out of {$a->numrows} rows.';
@@ -113,8 +115,10 @@ $string['incorrectfieldname'] = 'Incorrect name';
 $string['index'] = 'Index';
 $string['indexes'] = 'Indexes';
 $string['integerincorrectlength'] = 'Incorrect length for integer field';
+$string['incorrectkeyname'] = 'Incorrect key name';
 $string['incorrecttablename'] = 'Incorrect table name';
 $string['key'] = 'Key';
+$string['keynameempty'] = 'The key name cannot be empty';
 $string['keys'] = 'Keys';
 $string['listreservedwords'] = 'List of Reserved Words<br />(used to keep <a href="http://docs.moodle.org/en/XMLDB_reserved_words" target="_blank">XMLDB_reserved_words</a> updated)';
 $string['load'] = 'Load';
@@ -131,8 +135,11 @@ $string['newkey'] = 'New key';
 $string['newtable'] = 'New table';
 $string['newtablefrommysql'] = 'New table from MySQL';
 $string['new_table_from_mysql'] = 'New table from MySQL';
+$string['nofieldsspecified'] = 'No fields specified';
 $string['nomasterprimaryuniquefound'] = 'The column(s) that you foreign key references must be included in a primary or unique KEY in the referenced table. Note, the column being in a UNIQUE INDEX is not good enough.';
 $string['nomissingindexesfound'] = 'No missing indexes have been found, your DB doesn\'t need further actions.';
+$string['noreffieldsspecified'] = 'No reference fields specified';
+$string['noreftablespecified'] = 'Specified reference table not found';
 $string['noviolatedforeignkeysfound'] = 'No violated foreign keys found';
 $string['nowrongdefaultsfound'] = 'No inconsistent default values have been found, your DB does not need further actions.';
 $string['nowrongintsfound'] = 'No wrong integers have been found, your DB doesn\'t need further actions.';
@@ -143,6 +150,7 @@ $string['pendingchanges'] = 'Note: You have performed changes to this file. They
 $string['pendingchangescannotbesaved'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server.';
 $string['pendingchangescannotbesavedreload'] = 'There are changes in this file but they cannot be saved! Please verify that both the directory and the "install.xml" within it have write permissions for the web server. Then reload this page and you should be able to save those changes.';
 $string['pluginname'] = 'XMLDB editor';
+$string['primarykeyonlyallownotnullfields'] = 'Primary keys cannot be null';
 $string['reserved'] = 'Reserved';
 $string['reservedwords'] = 'Reserved words';
 $string['revert'] = 'Revert';
@@ -178,6 +186,7 @@ $string['wrong'] = 'Wrong';
 $string['wrongdefaults'] = 'Wrong defaults found';
 $string['wrongints'] = 'Wrong integers found';
 $string['wronglengthforenum'] = 'Incorrect length for enum field';
+$string['wrongnumberofreffields'] = 'Wrong number of reference fields';
 $string['wrongreservedwords'] = 'Currently used reserved words<br />(note that table names aren\'t important if using $CFG->prefix)';
 $string['wrongoraclesemantics'] = 'Wrong Oracle BYTE semantics found';
 $string['yesmissingindexesfound'] = 'Some missing indexes have been found in your DB. Here are their details and the needed SQL statements to be executed with your favourite SQL interface to create all them (don\'t forget to backup your data before doing that).<br /><br />After doing that, it\'s highly recommended to execute this utility again to check that no more missing indexes are found.';
index 441ed10..642708f 100644 (file)
@@ -109,8 +109,8 @@ abstract class backup implements checksumable {
     const OPERATION_RESTORE ='restore';// We are performing one restore
 
     // Version (to keep CFG->backup_version (and release) updated automatically)
-    const VERSION = 2011063000;
-    const RELEASE = '2.1';
+    const VERSION = 2012061800;
+    const RELEASE = '2.3';
 }
 
 /*
index d088cad..ef0a897 100644 (file)
@@ -62,7 +62,7 @@ switch ($type) {
     case backup::TYPE_1SECTION :
         $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id);
         require_capability('moodle/backup:backupsection', $coursecontext);
-        if (!empty($section->name)) {
+        if ((string)$section->name !== '') {
             $sectionname = format_string($section->name, true, array('context' => $coursecontext));
             $heading = get_string('backupsection', 'backup', $sectionname);
             $PAGE->navbar->add($sectionname);
index 3e01adf..962c46b 100644 (file)
@@ -8,7 +8,9 @@ defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.');
 require_once($CFG->dirroot.'/backup/bb/xsl_emulate_xslt.inc');
 
 function get_subdirs($directory){
-    $opendirectory = opendir( $directory );
+    if (!$opendirectory = opendir( $directory )) {
+        return array();
+    }
     while(false !== ($filename = readdir($opendirectory))) {
         if (is_dir($directory.$filename) and $filename != ".." and $filename != "."){
             $subdirs[] = $filename;
index cf73d77..013d891 100644 (file)
@@ -71,6 +71,11 @@ class cc2moodle {
             return false;
         }
 
+        // Before iterate over directories, try to find one manifest at top level
+        if (file_exists($folder . '/imsmanifest.xml')) {
+            return $folder . '/imsmanifest.xml';
+        }
+
         $result = false;
         try {
             $dirIter = new RecursiveDirectoryIterator($folder, RecursiveDirectoryIterator::KEY_AS_PATHNAME);
index 377011e..3f627bf 100644 (file)
@@ -135,7 +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}');
+        $rules[] = new restore_log_rule('course', 'view section', 'view.php?id={course}&sectionid={course_section}', '{course_section}');
 
         // module 'user' rules
         $rules[] = new restore_log_rule('user', 'view', 'view.php?id={user}&course={course}', '{user}');
index d87cdb9..60a4086 100644 (file)
@@ -207,21 +207,25 @@ class restore_gradebook_structure_step extends restore_structure_step {
         global $DB;
 
         $data = (object)$data;
-        $oldid = $data->id;
+        $olduserid = $data->userid;
 
         $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);
+        $data->userid = $this->get_mappingid('user', $data->userid, null);
+        if (!empty($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 user id '{$olduserid}', grade item id '{$data->itemid}'");
+        }
     }
+
     protected function process_grade_category($data) {
         global $DB;
 
@@ -1047,7 +1051,6 @@ 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;
 
@@ -1080,7 +1083,7 @@ class restore_section_structure_step extends restore_structure_step {
         // Section exists, update non-empty information
         } else {
             $section->id = $secrec->id;
-            if (empty($secrec->name)) {
+            if ((string)$secrec->name === '') {
                 $section->name = $data->name;
             }
             if (empty($secrec->summary)) {
@@ -1100,12 +1103,10 @@ 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, $restorefiles);
 
         // set the new course_section id in the task
         $this->task->set_sectionid($newitemid);
@@ -1126,10 +1127,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() {
@@ -1142,16 +1152,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;
@@ -2357,18 +2371,24 @@ class restore_activity_grades_structure_step extends restore_structure_step {
 
     protected function process_grade_grade($data) {
         $data = (object)($data);
-
+        $olduserid = $data->userid;
         unset($data->id);
+
         $data->itemid = $this->get_new_parentid('grade_item');
-        $data->userid = $this->get_mappingid('user', $data->userid);
-        $data->usermodified = $this->get_mappingid('user', $data->usermodified);
-        $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
-        // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
-        $data->overridden = $this->apply_date_offset($data->overridden);
 
-        $grade = new grade_grade($data, false);
-        $grade->insert('restore');
-        // no need to save any grade_grade mapping
+        $data->userid = $this->get_mappingid('user', $data->userid, null);
+        if (!empty($data->userid)) {
+            $data->usermodified = $this->get_mappingid('user', $data->usermodified, null);
+            $data->rawscaleid = $this->get_mappingid('scale', $data->rawscaleid);
+            // TODO: Ask, all the rest of locktime/exported... work with time... to be rolled?
+            $data->overridden = $this->apply_date_offset($data->overridden);
+
+            $grade = new grade_grade($data, false);
+            $grade->insert('restore');
+            // no need to save any grade_grade mapping
+        } else {
+            debugging("Mapped user id not found for user id '{$olduserid}', grade item id '{$data->itemid}'");
+        }
     }
 
     /**
@@ -2544,7 +2564,6 @@ 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();
@@ -2571,7 +2590,6 @@ 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, $sectionrec->section, $restorefiles);
         }
         $data->groupingid= $this->get_mappingid('grouping', $data->groupingid);      // grouping
         if (!$CFG->enablegroupmembersonly) {                                         // observe groupsmemberonly
index 0b47c5f..94674e7 100644 (file)
@@ -125,5 +125,7 @@ abstract class restore_controller_dbops extends restore_dbops {
             $table = new xmldb_table($targettablename);
             $dbman->drop_table($table); // And drop it
         }
+        // Invalidate the backup_ids caches.
+        restore_dbops::reset_backup_ids_cached();
     }
 }
index 81dbaf6..4254e69 100644 (file)
@@ -312,6 +312,31 @@ abstract class restore_dbops {
         }
     }
 
+    /**
+     * Reset the ids caches completely
+     *
+     * Any destructive operation (partial delete, truncate, drop or recreate) performed
+     * with the backup_ids table must cause the backup_ids caches to be
+     * invalidated by calling this method. See MDL-33630.
+     *
+     * Note that right now, the only operation of that type is the recreation
+     * (drop & restore) of the table that may happen once the prechecks have ended. All
+     * the rest of operations are always routed via {@link set_backup_ids_record()}, 1 by 1,
+     * keeping the caches on sync.
+     *
+     * @todo MDL-25290 static should be replaced with MUC code.
+     */
+    public static function reset_backup_ids_cached() {
+        // Reset the ids cache.
+        $cachetoadd = count(self::$backupidscache);
+        self::$backupidscache = array();
+        self::$backupidscachesize = self::$backupidscachesize + $cachetoadd;
+        // Reset the exists cache.
+        $existstoadd = count(self::$backupidsexist);
+        self::$backupidsexist = array();
+        self::$backupidsexistsize = self::$backupidsexistsize + $existstoadd;
+    }
+
     /**
      * 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)
index dc02ad9..e088084 100644 (file)
@@ -26,10 +26,104 @@ defined('MOODLE_INTERNAL') || die();
 // Include all the needed stuff
 global $CFG;
 require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
+require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
 
+/**
+ * Restore dbops tests (all).
+ */
+class restore_dbops_testcase extends advanced_testcase {
+
+    /**
+     * Verify the xxx_ids_cached (in-memory backup_ids cache) stuff works as expected.
+     *
+     * Note that those private implementations are tested here by using the public
+     * backup_ids API and later performing low-level tests.
+     */
+    public function test_backup_ids_cached() {
+        global $DB;
+        $dbman = $DB->get_manager(); // We are going to use database_manager services.
+
+        $this->resetAfterTest(true); // Playing with temp tables, better reset once finished.
+
+        // Some variables and objects for testing.
+        $restoreid = 'testrestoreid';
+
+        $mapping = new stdClass();
+        $mapping->itemname = 'user';
+        $mapping->itemid = 1;
+        $mapping->newitemid = 2;
+        $mapping->parentitemid = 3;
+        $mapping->info = 'info';
+
+        // Create the backup_ids temp tables used by restore.
+        restore_controller_dbops::create_restore_temp_tables($restoreid);
+
+        // Send one mapping using the public api with defaults.
+        restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+        // Get that mapping and verify everything is returned as expected.
+        $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+        $this->assertSame($mapping->itemname, $result->itemname);
+        $this->assertSame($mapping->itemid, $result->itemid);
+        $this->assertSame(0, $result->newitemid);
+        $this->assertSame(null, $result->parentitemid);
+        $this->assertSame(null, $result->info);
+
+        // Drop the backup_xxx_temp temptables manually, so memory cache won't be invalidated.
+        $dbman->drop_table(new xmldb_table('backup_ids_temp'));
+        $dbman->drop_table(new xmldb_table('backup_files_temp'));
+
+        // Verify the mapping continues returning the same info,
+        // now from cache (the table does not exist).
+        $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+        $this->assertSame($mapping->itemname, $result->itemname);
+        $this->assertSame($mapping->itemid, $result->itemid);
+        $this->assertSame(0, $result->newitemid);
+        $this->assertSame(null, $result->parentitemid);
+        $this->assertSame(null, $result->info);
 
-/*
- * dbops tests (all)
+        // Recreate the temp table, just to drop it using the restore API in
+        // order to check that, then, the cache becomes invalid for the same request.
+        restore_controller_dbops::create_restore_temp_tables($restoreid);
+        restore_controller_dbops::drop_restore_temp_tables($restoreid);
+
+        // No cached info anymore, so the mapping request will arrive to
+        // DB leading to error (temp table does not exist).
+        try {
+            $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+            $this->fail('Expecting an exception, none occurred');
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof dml_exception);
+            $this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage());
+        }
+
+        // Create the backup_ids temp tables once more.
+        restore_controller_dbops::create_restore_temp_tables($restoreid);
+
+        // Send one mapping using the public api with complete values.
+        restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid,
+                $mapping->newitemid, $mapping->parentitemid, $mapping->info);
+        // Get that mapping and verify everything is returned as expected.
+        $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+        $this->assertSame($mapping->itemname, $result->itemname);
+        $this->assertSame($mapping->itemid, $result->itemid);
+        $this->assertSame($mapping->newitemid, $result->newitemid);
+        $this->assertSame($mapping->parentitemid, $result->parentitemid);
+        $this->assertSame($mapping->info, $result->info);
+
+        // Finally, drop the temp tables properly and get the DB error again (memory caches empty).
+        restore_controller_dbops::drop_restore_temp_tables($restoreid);
+        try {
+            $result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
+            $this->fail('Expecting an exception, none occurred');
+        } catch (Exception $e) {
+            $this->assertTrue($e instanceof dml_exception);
+            $this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage());
+        }
+    }
+}
+
+/**
+ * Backup dbops tests (all).
  */
 class backup_dbops_testcase extends advanced_testcase {
 
index a24905c..e2143cd 100644 (file)
@@ -139,7 +139,7 @@ abstract class backup_factory {
             throw new backup_task_exception('section_task_section_not_found', $sectionid);
         }
 
-        return new backup_section_task(empty($section->name) ? $section->section : $section->name, $sectionid);
+        return new backup_section_task((string)$section->name === '' ? $section->section : $section->name, $sectionid);
     }
 
     /**
index 94a7a87..65e56ee 100644 (file)
@@ -350,13 +350,13 @@ abstract class backup_cron_automated_helper {
 
             $bc->execute_plan();
             $results = $bc->get_results();
-            $file = $results['backup_destination'];
+            $file = $results['backup_destination']; // may be empty if file already moved to target location
             $dir = $config->backup_auto_destination;
             $storage = (int)$config->backup_auto_storage;
             if (!file_exists($dir) || !is_dir($dir) || !is_writable($dir)) {
                 $dir = null;
             }
-            if (!empty($dir) && $storage !== 0) {
+            if ($file && !empty($dir) && $storage !== 0) {
                 $filename = backup_plan_dbops::get_default_backup_filename($format, $type, $course->id, $users, $anonymised, !$config->backup_shortname);
                 $outcome = $file->copy_content_to($dir.'/'.$filename);
                 if ($outcome && $storage === 1) {
@@ -461,6 +461,11 @@ abstract class backup_cron_automated_helper {
         $storage =  $config->backup_auto_storage;
         $dir =      $config->backup_auto_destination;
 
+        if ($keep == 0) {
+            // means keep all backup files
+            return true;
+        }
+
         $backupword = str_replace(' ', '_', textlib::strtolower(get_string('backupfilename')));
         $backupword = trim(clean_filename($backupword), '_');
 
index ff5dc89..df3c8d9 100644 (file)
@@ -79,7 +79,9 @@ abstract class backup_general_helper extends backup_helper {
             return array();
         }
 
-        $dir = opendir($path);
+        if (!$dir = opendir($path)) {
+            return array();
+        }
         while (false !== ($file = readdir($dir))) {
             if ($file == '.' || $file == '..') { // Skip dots
                 continue;
index 67bd78e..5904751 100644 (file)
@@ -176,8 +176,17 @@ abstract class backup_helper {
     /**
      * Given one backupid and the (FS) final generated file, perform its final storage
      * into Moodle file storage. For stored files it returns the complete file_info object
+     *
+     * Note: the $filepath is deleted if the backup file is created successfully
+     *
+     * @param int $backupid
+     * @param string $filepath zip file containing the backup
+     * @return stored_file if created, null otherwise
+     *
+     * @throws moodle_exception in case of any problems
      */
     static public function store_backup_file($backupid, $filepath) {
+        global $CFG;
 
         // First of all, get some information from the backup_controller to help us decide
         list($dinfo, $cinfo, $sinfo) = backup_controller_dbops::get_moodle_backup_information($backupid);
@@ -191,6 +200,7 @@ abstract class backup_helper {
         $userid    = $dinfo[0]->userid;                // User->id executing the backup
         $id        = $dinfo[0]->id;                    // Id of activity/section/course (depends of type)
         $courseid  = $dinfo[0]->courseid;              // Id of the course
+        $format    = $dinfo[0]->format;                // Type of backup file
 
         // Quick hack. If for any reason, filename is blank, fix it here.
         // TODO: This hack will be out once MDL-22142 - P26 gets fixed
@@ -200,7 +210,13 @@ abstract class backup_helper {
 
         // Backups of type IMPORT aren't stored ever
         if ($backupmode == backup::MODE_IMPORT) {
-            return false;
+            return null;
+        }
+
+        if (!is_readable($filepath)) {
+            // we have a problem if zip file does not exist
+            throw new coding_exception('backup_helper::store_backup_file() expects valid $filepath parameter');
+
         }
 
         // Calculate file storage options of id being backup
@@ -232,6 +248,25 @@ abstract class backup_helper {
         if ($backupmode == backup::MODE_AUTOMATED) {
             // Automated backups have there own special area!
             $filearea  = 'automated';
+
+            // If we're keeping the backup only in a chosen path, just move it there now
+            // this saves copying from filepool to here later and filling trashdir.
+            $config = get_config('backup');
+            $dir = $config->backup_auto_destination;
+            if ($config->backup_auto_storage == 1 and $dir and is_dir($dir) and is_writable($dir)) {
+                $filedest = $dir.'/'.backup_plan_dbops::get_default_backup_filename($format, $backuptype, $courseid, $hasusers, $isannon, !$config->backup_shortname);
+                // first try to move the file, if it is not possible copy and delete instead
+                if (@rename($filepath, $filedest)) {
+                    return null;
+                }
+                umask(0000);
+                if (copy($filepath, $filedest)) {
+                    @chmod($filedest, $CFG->filepermissions); // may fail because the permissions may not make sense outside of dataroot
+                    unlink($filepath);
+                    return null;
+                }
+                // bad luck, try to deal with the file the old way - keep backup in file area if we can not copy to ext system
+            }
         }
 
         // Backups of type HUB (by definition never have user info)
@@ -275,7 +310,9 @@ abstract class backup_helper {
             $sf = $fs->get_file_by_hash($pathnamehash);
             $sf->delete();
         }
-        return $fs->create_file_from_pathname($fr, $filepath);
+        $file = $fs->create_file_from_pathname($fr, $filepath);
+        unlink($filepath);
+        return $file;
     }
 
     /**
index 3db20a1..c18d463 100644 (file)
@@ -129,7 +129,7 @@ class base_setting_ui {
      * @param string $label
      */
     public function set_label($label) {
-        if (empty($label) || $label !== clean_param($label, PARAM_TEXT)) {
+        if ((string)$label === '' || $label !== clean_param($label, PARAM_TEXT)) {
             throw new base_setting_ui_exception('setting_invalid_ui_label');
         }
         $this->label = $label;
index e82d7bd..eb0ab29 100644 (file)
@@ -234,6 +234,10 @@ $CFG->admin = 'admin';
 //         '/filedir'   => '/var/www/moodle/filedir',  // for custom $CFG->filedir locations
 //     );
 //
+// YUI caching may be sometimes improved by slasharguments:
+//     $CFG->yuislasharguments = 1;
+// Some servers may need a special rewrite rule to work around internal path length limitations:
+// RewriteRule (^.*/theme/yui_combo\.php)(/.*) $1?file=$2
 //
 //
 // This setting will prevent the 'My Courses' page being displayed when a student
index 9b7c4e4..4651120 100644 (file)
@@ -53,7 +53,7 @@ M.course_dndupload = {
     // The classes that an element must have to be identified as a course section
     sectionclasses: ['section', 'main'],
     // The ID of the main content area of the page (for adding the 'status' div)
-    pagecontentid: 'page-content',
+    pagecontentid: 'page',
     // The selector identifying the list of modules within a section (note changing this may require
     // changes to the get_mods_element function)
     modslistselector: 'ul.section',
@@ -92,7 +92,9 @@ M.course_dndupload = {
             this.init_events(el);
         }, this);
 
-        this.add_status_div();
+        if (options.showstatus) {
+            this.add_status_div();
+        }
     },
 
     /**
@@ -100,14 +102,18 @@ M.course_dndupload = {
      * is available (or to explain why it is not available)
      */
     add_status_div: function() {
-        var div = document.createElement('div');
-        div.id = 'dndupload-status';
         var coursecontents = document.getElementById(this.pagecontentid);
-        if (coursecontents) {
-            coursecontents.insertBefore(div, coursecontents.firstChild);
+        if (!coursecontents) {
+            return;
         }
-        div = this.Y.one(div);
 
+        var div = document.createElement('div');
+        div.id = 'dndupload-status';
+        div.style.opacity = 0.0;
+        coursecontents.insertBefore(div, coursecontents.firstChild);
+
+        var Y = this.Y;
+        div = Y.one(div);
         var handlefile = (this.handlers.filehandlers.length > 0);
         var handletext = false;
         var handlelink = false;
@@ -134,6 +140,25 @@ M.course_dndupload = {
             $msgident += 'link';
         }
         div.setContent(M.util.get_string($msgident, 'moodle'));
+
+        var fadeanim = new Y.Anim({
+            node: '#dndupload-status',
+            from: {
+                opacity: 0.0,
+                top: '-30px'
+            },
+
+            to: {
+                opacity: 1.0,
+                top: '0px'
+            },
+            duration: 0.5
+        });
+        fadeanim.once('end', function(e) {
+            this.set('reverse', 1);
+            Y.later(3000, this, 'run', null, false);
+        });
+        fadeanim.run();
     },
 
     /**
index c0ed633..816924b 100644 (file)
@@ -39,6 +39,8 @@ require_once($CFG->dirroot.'/course/lib.php');
 function dndupload_add_to_course($course, $modnames) {
     global $CFG, $PAGE;
 
+    $showstatus = optional_param('notifyeditingon', false, PARAM_BOOL);
+
     // Get all handlers.
     $handler = new dndupload_handler($course, $modnames);
     $jsdata = $handler->get_js_data();
@@ -65,12 +67,13 @@ function dndupload_add_to_course($course, $modnames) {
             array('upload', 'moodle'),
             array('cancel', 'moodle')
         ),
-        'requires' => array('node', 'event', 'panel', 'json')
+        'requires' => array('node', 'event', 'panel', 'json', 'anim')
     );
     $vars = array(
         array('courseid' => $course->id,
               'maxbytes' => get_max_upload_file_size($CFG->maxbytes, $course->maxbytes),
-              'handlers' => $handler->get_js_data())
+              'handlers' => $handler->get_js_data(),
+              'showstatus' => $showstatus)
     );
 
     $PAGE->requires->js_init_call('M.course_dndupload.init', $vars, true, $jsmodule);
@@ -668,4 +671,4 @@ class dndupload_ajax_processor {
         echo json_encode($resp);
         die();
     }
-}
\ No newline at end of file
+}
index 5017145..e58d5cd 100644 (file)
@@ -66,7 +66,7 @@ class core_course_external extends external_api {
      * @return array
      * @since Moodle 2.2
      */
-    public static function get_course_contents($courseid, $options) {
+    public static function get_course_contents($courseid, $options = array()) {
         global $CFG, $DB;
         require_once($CFG->dirroot . "/course/lib.php");
 
@@ -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(
@@ -274,7 +276,7 @@ class core_course_external extends external_api {
      * @return array
      * @since Moodle 2.2
      */
-    public static function get_courses($options) {
+    public static function get_courses($options = array()) {
         global $CFG, $DB;
         require_once($CFG->dirroot . "/course/lib.php");
 
@@ -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;
 
@@ -698,7 +701,7 @@ class core_course_external extends external_api {
      * @return array New course info
      * @since Moodle 2.3
      */
-    public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible, $options) {
+    public static function duplicate_course($courseid, $fullname, $shortname, $categoryid, $visible = 1, $options = array()) {
         global $CFG, $USER, $DB;
         require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
         require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
@@ -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'),
@@ -1188,11 +1188,14 @@ class core_course_external extends external_api {
                             array(
                                 'name' => new external_value(PARAM_TEXT, 'new category name'),
                                 'parent' => new external_value(PARAM_INT,
-                                        'the parent category id inside which the new category will be created'),
+                                        'the parent category id inside which the new category will be created
+                                         - set to 0 for a root category',
+                                        VALUE_DEFAULT, 0),
                                 'idnumber' => new external_value(PARAM_RAW,
                                         '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),
@@ -1232,17 +1235,6 @@ class core_course_external extends external_api {
             self::validate_context($context);
             require_capability('moodle/category:manage', $context);
 
-            // Check id number.
-            if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
-                if (textlib::strlen($category['idnumber'])>100) {
-                    throw new moodle_exception('idnumbertoolong');
-                }
-                if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
-                    if ($existing->id) {
-                        throw new moodle_exception('idnumbertaken');
-                    }
-                }
-            }
             // Check name.
             if (textlib::strlen($category['name'])>255) {
                 throw new moodle_exception('categorytoolong');
@@ -1251,16 +1243,27 @@ class core_course_external extends external_api {
             $newcategory = new stdClass();
             $newcategory->name = $category['name'];
             $newcategory->parent = $category['parent'];
-            $newcategory->idnumber = $category['idnumber'];
             $newcategory->sortorder = 999; // Same as in the course/editcategory.php .
             // Format the description.
             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'];
             }
+            // Check id number.
+            if (!empty($category['idnumber'])) { // Same as in course/editcategory_form.php .
+                if (textlib::strlen($category['idnumber'])>100) {
+                    throw new moodle_exception('idnumbertoolong');
+                }
+                if ($existing = $DB->get_record('course_categories', array('idnumber' => $category['idnumber']))) {
+                    if ($existing->id) {
+                        throw new moodle_exception('idnumbertaken');
+                    }
+                }
+                $newcategory->idnumber = $category['idnumber'];
+            }
 
             $newcategory = create_course_category($newcategory);
             // Populate special fields.
@@ -1308,6 +1311,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 +1360,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 e8632fc..3728ab2 100644 (file)
@@ -59,7 +59,7 @@ function callback_topics_definition() {
 
 function callback_topics_get_section_name($course, $section) {
     // We can't add a node without any text
-    if (!empty($section->name)) {
+    if ((string)$section->name !== '') {
         return format_string($section->name, true, array('context' => get_context_instance(CONTEXT_COURSE, $course->id)));
     } else if ($section->section == 0) {
         return get_string('section0name', 'format_topics');
index a1e1ea9..fe6fd6b 100644 (file)
@@ -66,7 +66,7 @@ function callback_weeks_definition() {
  */
 function callback_weeks_get_section_name($course, $section) {
     // We can't add a node without text
-    if (!empty($section->name)) {
+    if ((string)$section->name !== '') {
         // Return the name the user set.
         return format_string($section->name, true, array('context' => context_course::instance($course->id)));
     } else if ($section->section == 0) {
index a5566d6..26fea72 100644 (file)
@@ -2147,6 +2147,11 @@ function get_course_category_tree($id = 0, $depth = 0) {
         return array($categories, $categoryids);
     }
 
+    if (empty($categoryids)) {
+        // No categories available (probably all hidden).
+        return array();
+    }
+
     // The depth is 0 this function has just been called so we can finish it off
 
     list($ccselect, $ccjoin) = context_instance_preload_sql('c.id', CONTEXT_COURSE, 'ctx');
@@ -2948,8 +2953,11 @@ function delete_mod_from_section($mod, $section) {
  * @param int $section Section number (not id!!!)
  * @param int $move (-1 or 1)
  * @return boolean true if section moved successfully
+ * @todo MDL-33379 remove this function in 2.5
  */
 function move_section($course, $section, $move) {
+    debugging('This function will be removed before 2.5 is released please use move_section_to', DEBUG_DEVELOPER);
+
 /// Moves a whole course section up and down within the course
     global $USER, $DB;
 
@@ -2963,41 +2971,12 @@ function move_section($course, $section, $move) {
         return false;
     }
 
-    if (!$sectionrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$section))) {
-        return false;
+    $retval = move_section_to($course, $section, $sectiondest);
+    // If section moved, then rebuild course cache.
+    if ($retval) {
+        rebuild_course_cache($course->id, true);
     }
-
-    if (!$sectiondestrecord = $DB->get_record("course_sections", array("course"=>$course->id, "section"=>$sectiondest))) {
-        return false;
-    }
-
-    // Three-step change ensures that the section always remains unique (there is
-    // a unique index now)
-    $DB->set_field("course_sections", "section", -$sectiondest, array("id"=>$sectionrecord->id));
-    $DB->set_field("course_sections", "section", $section, array("id"=>$sectiondestrecord->id));
-    $DB->set_field("course_sections", "section", $sectiondest, array("id"=>$sectionrecord->id));
-
-    // Update highlighting if the move affects highlighted section
-    if ($course->marker == $section) {
-        course_set_marker($course->id, $sectiondest);
-    } elseif ($course->marker == $sectiondest) {
-        course_set_marker($course->id, $section);
-    }
-
-
-    // Fix order if needed. The database prevents duplicate sections, but it is
-    // possible there could be a gap in the numbering.
-    $sections = $DB->get_records('course_sections', array('course'=>$course->id), 'section ASC');
-    $n = 0;
-    foreach ($sections as $section) {
-        if ($section->section != $n) {
-            $DB->set_field('course_sections', 'section', $n, array('id'=>$section->id));
-        }
-        $n++;
-    }
-    // After moving section, rebuild course cache.
-    rebuild_course_cache($course->id, true);
-    return true;
+    return $retval;
 }
 
 /**
@@ -3018,7 +2997,7 @@ function move_section_to($course, $section, $destination) {
         return true;
     }
 
-    if ($destination > $course->numsections) {
+    if (($destination > $course->numsections) || ($destination < 1)) {
         return false;
     }
 
@@ -3107,6 +3086,10 @@ function reorder_sections($sections, $origin_position, $target_position) {
             unset($sections[$id]);
         }
         if ($position == $target_position) {
+            if ($target_position < $origin_position) {
+                $append_array[$id] = $position;
+                unset($sections[$id]);
+            }
             $found = true;
         }
     }
index 61ab633..ef59eb8 100644 (file)
@@ -81,7 +81,7 @@ if ($backup->get_stage() !== backup_ui::STAGE_COMPLETE) {
         echo $renderer->dependency_notification(get_string('dependenciesenforced', 'backup'));
     }
     echo $renderer->progress_bar($backup->get_progress_bar());
-    echo $backup->display();
+    echo $backup->display($renderer);
     echo $OUTPUT->footer();
     die();
 }
index 2831fed..d43d15c 100644 (file)
@@ -168,7 +168,7 @@ class core_course_renderer extends plugin_renderer_base {
 
         // Add the header
         $header = html_writer::tag('div', get_string('addresourceoractivity', 'moodle'),
-                array('id' => 'choosertitle', 'class' => 'hd'));
+                array('class' => 'hd choosertitle'));
 
         $formcontent = html_writer::start_tag('form', array('action' => new moodle_url('/course/jumpto.php'),
                 'id' => 'chooserform', 'method' => 'post'));
@@ -188,15 +188,7 @@ class core_course_renderer extends plugin_renderer_base {
         // Put all options into one tag 'alloptions' to allow us to handle scrolling
         $formcontent .= html_writer::start_tag('div', array('class' => 'alloptions'));
 
-        // First display Resources
-        $resources = array_filter($modules,
-                create_function('$mod', 'return ($mod->archetype === MOD_CLASS_RESOURCE);'));
-        if (count($resources)) {
-            $formcontent .= $this->course_modchooser_title('resources');
-            $formcontent .= $this->course_modchooser_module_types($resources);
-        }
-
-        // Then activities
+        // Activities
         $activities = array_filter($modules,
                 create_function('$mod', 'return ($mod->archetype !== MOD_CLASS_RESOURCE);'));
         if (count($activities)) {
@@ -204,6 +196,14 @@ class core_course_renderer extends plugin_renderer_base {
             $formcontent .= $this->course_modchooser_module_types($activities);
         }
 
+        // Resources
+        $resources = array_filter($modules,
+                create_function('$mod', 'return ($mod->archetype === MOD_CLASS_RESOURCE);'));
+        if (count($resources)) {
+            $formcontent .= $this->course_modchooser_title('resources');
+            $formcontent .= $this->course_modchooser_module_types($resources);
+        }
+
         $formcontent .= html_writer::end_tag('div'); // modoptions
         $formcontent .= html_writer::end_tag('div'); // types
 
@@ -221,8 +221,8 @@ class core_course_renderer extends plugin_renderer_base {
         // Put all of the content together
         $content = $formcontent;
 
-        $content = html_writer::tag('div', $content, array('id' => 'choosercontainer'));
-        return $header . html_writer::tag('div', $content, array('id' => 'chooserdialogue'));
+        $content = html_writer::tag('div', $content, array('class' => 'choosercontainer'));
+        return $header . html_writer::tag('div', $content, array('class' => 'chooserdialoguebody'));
     }
 
     /**
index f7f4f80..2274e6d 100644 (file)
@@ -52,6 +52,16 @@ class courselib_testcase extends advanced_testcase {
         $this->assertEquals($oldsections[5], $neworder[5]);
         $this->assertEquals($oldsections[6], $neworder[6]);
 
+        $neworder = reorder_sections($sections, 4, 2);
+        $neworder = array_keys($neworder);
+        $this->assertEquals($oldsections[0], $neworder[0]);
+        $this->assertEquals($oldsections[1], $neworder[1]);
+        $this->assertEquals($oldsections[2], $neworder[3]);
+        $this->assertEquals($oldsections[3], $neworder[4]);
+        $this->assertEquals($oldsections[4], $neworder[2]);
+        $this->assertEquals($oldsections[5], $neworder[5]);
+        $this->assertEquals($oldsections[6], $neworder[6]);
+
         $neworder = reorder_sections(1, 2, 4);
         $this->assertFalse($neworder);
     }
index de37981..e0f295b 100644 (file)
@@ -14,6 +14,7 @@
     $hide        = optional_param('hide', 0, PARAM_INT);
     $show        = optional_param('show', 0, PARAM_INT);
     $idnumber    = optional_param('idnumber', '', PARAM_RAW);
+    $sectionid   = optional_param('sectionid', 0, PARAM_INT);
     $section     = optional_param('section', 0, PARAM_INT);
     $move        = optional_param('move', 0, PARAM_INT);
     $marker      = optional_param('marker',-1 , PARAM_INT);
     $course = $DB->get_record('course', $params, '*', MUST_EXIST);
 
     $urlparams = array('id' => $course->id);
+
+    // Sectionid should get priority over section number
+    if ($sectionid) {
+        $section = $DB->get_field('course_sections', 'section', array('id' => $sectionid, 'course' => $course->id), MUST_EXIST);
+    }
     if ($section) {
         $urlparams['section'] = $section;
     }
     $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;
-    }
+        $coursesections = $DB->get_record('course_sections', $sectionparams, 'id', MUST_EXIST);
+        $infoid = $coursesections->id;
+        $logparam .= '&sectionid='. $infoid;
     }
     add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
 
             if ($course->id == SITEID) {
                 redirect($CFG->wwwroot .'/?redirect=0');
             } else {
-                redirect($PAGE->url);
+                $url = new moodle_url($PAGE->url, array('notifyeditingon' => 1));
+                redirect($url);
             }
         } else if (($edit == 0) and confirm_sesskey()) {
             $USER->editing = 0;
 
             if (!empty($section)) {
                 if (!empty($move) and confirm_sesskey()) {
-                    if (move_section($course, $section, $move)) {
+                    $destsection = $section + $move;
+                    if (move_section_to($course, $section, $destsection)) {
+                        // Rebuild course cache, after moving section
+                        rebuild_course_cache($course->id, true);
                         if ($course->id == SITEID) {
                             redirect($CFG->wwwroot . '/?redirect=0');
                         } else {
index 080cace..2b492ad 100644 (file)
@@ -135,12 +135,10 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             var dragnodeid = Number(this.get_section_id(dragnode));
             var dropnodeid = Number(this.get_section_id(dropnode));
 
-            var targetoffset = 0;
             var loopstart = dragnodeid;
             var loopend = dropnodeid;
 
             if (this.goingup) {
-                targetoffset = 1;
                 loopstart = dropnodeid;
                 loopend = dragnodeid;
             }
@@ -166,7 +164,7 @@ YUI.add('moodle-course-dragdrop', function(Y) {
             params['class'] = 'section';
             params.field = 'move';
             params.id = dragnodeid;
-            params.value = dropnodeid - targetoffset;
+            params.value = dropnodeid;
 
             // Do AJAX request
             var uri = M.cfg.wwwroot + this.get('ajaxurl');
index 5d95a02..4a691a2 100644 (file)
@@ -21,13 +21,12 @@ YUI.add('moodle-course-modchooser', function(Y) {
         jumplink : null,
 
         initializer : function(config) {
-            var dialogue = Y.one('#chooserdialogue');
-            var header = Y.one('#choosertitle');
+            var dialogue = Y.one('.chooserdialoguebody');
+            var header = Y.one('.choosertitle');
             var params = {
                 width: '540px'
             };
             this.setup_chooser_dialogue(dialogue, header, params);
-            this.overlay.get('boundingBox').addClass('modchooser');
 
             this.jumplink = this.container.one('#jump');
 
index 08438e1..147383c 100644 (file)
@@ -152,7 +152,7 @@ class core_enrol_external extends external_api {
      *                               }
      * @return array An array of users
      */
-    public static function get_enrolled_users($courseid, $options) {
+    public static function get_enrolled_users($courseid, $options = array()) {
         global $CFG, $USER, $DB;
         require_once($CFG->dirroot . "/user/lib.php");
 
@@ -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 f002f54..9e30ce1 100644 (file)
@@ -72,9 +72,10 @@ class enrol_mnet_addinstance_form extends moodleform {
      * Do not allow multiple instances for single remote host
      *
      * @param array $data raw form data
-     * @return array
+     * @param array $files
+     * @return array of errors
      */
-    function validation($data) {
+    function validation($data, $files) {
         global $DB;
 
         $errors = array();
index c09cd7e..2fb785c 100644 (file)
@@ -146,6 +146,7 @@ class enrol_mnet_mnetservice_enrol {
             $user = mnet_strip_user((object)$userdata, mnet_fields_to_import($client));
             $user->mnethostid = $client->id;
             $user->auth = 'mnet';
+            $user->confirmed = 1;
             try {
                 $user->id = $DB->insert_record('user', $user);
             } catch (Exception $e) {
index 1c7e6da..352dd6e 100644 (file)
@@ -73,6 +73,11 @@ Y.extend(DIALOGUE, Y.Overlay, {
                 if (this.get('center') && !e.prevVal && e.newVal) {
                     this.centerDialogue();
                 }
+                if (this.get('draggable')) {
+                    var titlebar = '#' + this.get('id') + ' .' + CSS.HEADER;
+                    this.plug(Y.Plugin.Drag, {handles : [titlebar]});
+                    Y.one(titlebar).setStyle('cursor', 'move');
+                }
                 break;
         }
     },
@@ -110,6 +115,10 @@ Y.extend(DIALOGUE, Y.Overlay, {
         center : {
             validator : Y.Lang.isBoolean,
             value : true
+        },
+        draggable : {
+            validator : Y.Lang.isBoolean,
+            value : false
         }
     }
 });
@@ -372,4 +381,4 @@ M.core.confirm = CONFIRM;
 M.core.exception = EXCEPTION;
 M.core.ajaxException = AJAXEXCEPTION;
 
-}, '@VERSION@', {requires:['base','node','overlay','event-key', 'moodle-enrol-notification-skin']});
\ No newline at end of file
+}, '@VERSION@', {requires:['base','node','overlay','event-key', 'moodle-enrol-notification-skin', 'dd-plugin']});
index ec16724..f6001d2 100644 (file)
@@ -52,7 +52,8 @@ class core_files_external extends external_api {
                 'filearea'  => new external_value(PARAM_TEXT, 'file area'),
                 'itemid'    => new external_value(PARAM_INT, 'associated id'),
                 'filepath'  => new external_value(PARAM_PATH, 'file path'),
-                'filename'  => new external_value(PARAM_FILE, 'file name')
+                'filename'  => new external_value(PARAM_FILE, 'file name'),
+                'modified' => new external_value(PARAM_INT, 'timestamp to return files changed after this time.', VALUE_DEFAULT, null)
             )
         );
     }
@@ -66,12 +67,15 @@ class core_files_external extends external_api {
      * @param int $itemid item id
      * @param string $filepath file path
      * @param string $filename file name
+     * @param int $modified timestamp to return files changed after this time.
      * @return array
      * @since Moodle 2.2
      */
-    public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename) {
+    public static function get_files($contextid, $component, $filearea, $itemid, $filepath, $filename, $modified = null) {
         global $CFG, $USER, $OUTPUT;
-        $fileinfo = self::validate_parameters(self::get_files_parameters(), array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename));
+        $fileinfo = self::validate_parameters(self::get_files_parameters(), array(
+                    'contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea,
+                    'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename, 'modified'=>$modified));
 
         $browser = get_file_browser();
 
@@ -99,7 +103,10 @@ class core_files_external extends external_api {
         $return = array();
         $return['parents'] = array();
         $return['files'] = array();
-        if ($file = $browser->get_file_info($context, $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'], $fileinfo['filepath'], $fileinfo['filename'])) {
+        $list = array();
+        if ($file = $browser->get_file_info(
+            $context, $fileinfo['component'], $fileinfo['filearea'], $fileinfo['itemid'],
+                $fileinfo['filepath'], $fileinfo['filename'])) {
             $level = $file->get_parent();
             while ($level) {
                 $params = $level->get_params();
@@ -107,36 +114,42 @@ class core_files_external extends external_api {
                 array_unshift($return['parents'], $params);
                 $level = $level->get_parent();
             }
-            $list = array();
             $children = $file->get_children();
             foreach ($children as $child) {
 
                 $params = $child->get_params();
+                $timemodified = $child->get_timemodified();
 
                 if ($child->is_directory()) {
-                    $node = array(
-                        'contextid' => $params['contextid'],
-                        'component' => $params['component'],
-                        'filearea'  => $params['filearea'],
-                        'itemid'    => $params['itemid'],
-                        'filepath'  => $params['filepath'],
-                        'filename'  => $child->get_visible_name(),
-                        'url'       => null,
-                        'isdir'     => true
-                    );
-                    $list[] = $node;
+                    if ((is_null($modified)) or ($modified < $timemodified)) {
+                        $node = array(
+                            'contextid' => $params['contextid'],
+                            'component' => $params['component'],
+                            'filearea'  => $params['filearea'],
+                            'itemid'    => $params['itemid'],
+                            'filepath'  => $params['filepath'],
+                            'filename'  => $child->get_visible_name(),
+                            'url'       => null,
+                            'isdir'     => true,
+                            'timemodified' => $timemodified
+                           );
+                           $list[] = $node;
+                    }
                 } else {
-                    $node = array(
-                        'contextid' => $params['contextid'],
-                        'component' => $params['component'],
-                        'filearea'  => $params['filearea'],
-                        'itemid'    => $params['itemid'],
-                        'filepath'  => $params['filepath'],
-                        'filename'  => $child->get_visible_name(),
-                        'url'       => $child->get_url(),
-                        'isdir'     => false
-                    );
-                    $list[] = $node;
+                    if ((is_null($modified)) or ($modified < $timemodified)) {
+                        $node = array(
+                            'contextid' => $params['contextid'],
+                            'component' => $params['component'],
+                            'filearea'  => $params['filearea'],
+                            'itemid'    => $params['itemid'],
+                            'filepath'  => $params['filepath'],
+                            'filename'  => $child->get_visible_name(),
+                            'url'       => $child->get_url(),
+                            'isdir'     => false,
+                            'timemodified' => $timemodified
+                        );
+                           $list[] = $node;
+                    }
                 }
             }
         }
@@ -176,6 +189,7 @@ class core_files_external extends external_api {
                             'filename' => new external_value(PARAM_FILE, ''),
                             'isdir'    => new external_value(PARAM_BOOL, ''),
                             'url'      => new external_value(PARAM_TEXT, ''),
+                            'timemodified' => new external_value(PARAM_INT, ''),
                         )
                     )
                 )
@@ -219,12 +233,14 @@ class core_files_external extends external_api {
     public static function upload($contextid, $component, $filearea, $itemid, $filepath, $filename, $filecontent) {
         global $USER, $CFG;
 
-        $fileinfo = self::validate_parameters(self::upload_parameters(), array('contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid, 'filepath'=>$filepath, 'filename'=>$filename, 'filecontent'=>$filecontent));
+        $fileinfo = self::validate_parameters(self::upload_parameters(), array(
+            'contextid'=>$contextid, 'component'=>$component, 'filearea'=>$filearea, 'itemid'=>$itemid,
+            'filepath'=>$filepath, 'filename'=>$filename, 'filecontent'=>$filecontent));
 
         if (!isset($fileinfo['filecontent'])) {
             throw new moodle_exception('nofile');
         }
-        // saving file
+        // Saving file.
         $dir = make_temp_directory('wsupload');
 
         if (empty($fileinfo['filename'])) {
@@ -239,7 +255,6 @@ class core_files_external extends external_api {
             $savedfilepath = $dir.$filename;
         }
 
-
         file_put_contents($savedfilepath, base64_decode($fileinfo['filecontent']));
         unset($fileinfo['filecontent']);
 
@@ -250,7 +265,7 @@ class core_files_external extends external_api {
         }
 
         if (isset($fileinfo['itemid'])) {
-            // TODO MDL-31116 in user private area, itemid is always 0
+            // TODO MDL-31116 in user private area, itemid is always 0.
             $itemid = 0;
         } else {
             throw new coding_exception('itemid cannot be empty');
@@ -265,19 +280,19 @@ class core_files_external extends external_api {
         if (!($fileinfo['component'] == 'user' and $fileinfo['filearea'] == 'private')) {
             throw new coding_exception('File can be uploaded to user private area only');
         } else {
-            // TODO MDL-31116 hard-coded to use user_private area
+            // TODO MDL-31116 hard-coded to use user_private area.
             $component = 'user';
             $filearea = 'private';
         }
 
         $browser = get_file_browser();
 
-        // check existing file
+        // Check existing file.
         if ($file = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, $filename)) {
             throw new moodle_exception('fileexist');
         }
 
-        // move file to filepool
+        // Move file to filepool.
         if ($dir = $browser->get_file_info($context, $component, $filearea, $itemid, $filepath, '.')) {
             $info = $dir->create_file_from_pathname($filename, $savedfilepath);
             $params = $info->get_params();
index 3728a23..2f5f5d4 100644 (file)
@@ -301,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);
@@ -382,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>
@@ -401,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);
@@ -515,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">
@@ -523,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>
@@ -534,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>
@@ -690,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);
@@ -736,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>
@@ -771,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);
     }
 
@@ -817,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);
     }
@@ -860,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>
@@ -896,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 a133b4b..ff2e86d 100644 (file)
@@ -56,7 +56,7 @@ class gradingform_guide_editguide extends moodleform {
 
         // Description.
         $options = gradingform_guide_controller::description_form_field_options($this->_customdata['context']);
-        $form->addElement('editor', 'description_editor', get_string('descriptionstudents', 'gradingform_guide'), null, $options);
+        $form->addElement('editor', 'description_editor', get_string('description'), null, $options);
         $form->setType('description_editor', PARAM_RAW);
 
         // Guide completion status.
index 11ae292..74b8c56 100644 (file)
@@ -125,7 +125,9 @@ if ($formdata = $mform->get_data()) {
     fwrite($fp,$text);
     fclose($fp);
 
-    $fp = fopen($filename, "r");
+    if (!$fp = fopen($filename, "r")) {
+        print_error('cannotopenfile');
+    }
 
     // --- get header (field names) ---
     $header = explode($csv_delimiter, fgets($fp, GRADE_CSV_LINE_LENGTH));
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
index c50bbe7..350e39c 100644 (file)
 
 defined('MOODLE_INTERNAL') || die();
 
+$string['clianswerno'] = 'n';
+$string['cliansweryes'] = 's';
+$string['cliincorrectvalueerror'] = 'Error, valor incorrecto  "{$a->value}" para "{$a->option}"';
+$string['cliincorrectvalueretry'] = 'Valor incorrecto, por favor, inténtelo de nuevo';
+$string['clitypevalue'] = 'valor del tipo';
+$string['clitypevaluedefault'] = 'valor del tipo, pulse Enter para utilizar el valor por defecto ({$a})';
+$string['cliunknowoption'] = 'Opciones no reconocidas:
+{$a}
+Por favor, utilice la opción Ayuda.';
+$string['cliyesnoprompt'] = 'escriba s (sí) o n (no)';
 $string['environmentrequireinstall'] = 'debe estar instalado y activado';
+$string['environmentrequireversion'] = 'versión {$a->needed} es obligatoria y está ejecutando {$a->current}';
diff --git a/install/lang/sv_fi/install.php b/install/lang/sv_fi/install.php
new file mode 100644 (file)
index 0000000..2b93e10
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Automatically generated strings for Moodle 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['databasename'] = 'Databasens namn';
index 6dbb859..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';
@@ -846,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.';
@@ -925,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';
@@ -1031,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 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 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 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..b034760 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)
@@ -703,11 +711,13 @@ function notify_login_failures() {
             //emailing the admins directly rather than putting these through the messaging system
             email_to_user($admin,get_admin(), $subject, $body);
         }
-
-        // Update lastnotifyfailure with current time
-        set_config('lastnotifyfailure', time());
     }
 
+    // Update lastnotifyfailure with current time
+    set_config('lastnotifyfailure', time());
+
     // 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 d4b7077..2cbd0e9 100644 (file)
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<XMLDB PATH="lib/db" VERSION="20120521" COMMENT="XMLDB file for core Moodle tables"
+<XMLDB PATH="lib/db" VERSION="20120531" COMMENT="XMLDB file for core Moodle tables"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:noNamespaceSchemaLocation="../../lib/xmldb/xmldb.xsd"
 >
         <FIELD NAME="repositoryid" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="false" PREVIOUS="id" NEXT="lastsync"/>
         <FIELD NAME="lastsync" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="Last time the proxy file was synced with repository" PREVIOUS="repositoryid" NEXT="lifetime"/>
         <FIELD NAME="lifetime" TYPE="int" LENGTH="10" NOTNULL="false" SEQUENCE="false" COMMENT="How often do we have to sync proxy file with repository" PREVIOUS="lastsync" NEXT="reference"/>
-        <FIELD NAME="reference" TYPE="text" NOTNULL="false" SEQUENCE="false" PREVIOUS="lifetime"/>
+        <FIELD NAME="reference" TYPE="text" NOTNULL="false" SEQUENCE="false" COMMENT="Identification of the external file. Repository plugins are interpreting it to locate the external file." PREVIOUS="lifetime" NEXT="referencehash"/>
+        <FIELD NAME="referencehash" TYPE="char" LENGTH="40" NOTNULL="true" SEQUENCE="false" COMMENT="Internal implementation detail, contains SHA1 hash of the reference field. Can be indexed and used for comparison. Not meant to be used by a non-core code." PREVIOUS="reference"/>
       </FIELDS>
       <KEYS>
         <KEY NAME="primary" TYPE="primary" FIELDS="id" NEXT="repositoryid"/>
         <KEY NAME="repositoryid" TYPE="foreign" FIELDS="repositoryid" REFTABLE="repository_instances" REFFIELDS="id" PREVIOUS="primary"/>
       </KEYS>
+      <INDEXES>
+        <INDEX NAME="uq_external_file" UNIQUE="true" FIELDS="repositoryid, referencehash" COMMENT="The combination of repositoryid and reference field is supposed to be a unique identification of an external file. Because the reference is a TEXT field, we can't use to compose the index. So we use the referencehash instead and the file API is responsible to keep it up-to-date"/>
+      </INDEXES>
     </TABLE>
     <TABLE NAME="repository" COMMENT="This table contains one entry for every configured external repository instance." PREVIOUS="files_reference" NEXT="repository_instances">
       <FIELDS>
index f0475f6..94abc81 100644 (file)
@@ -38,7 +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'=>'view section', 'mtable'=>'course_sections', 'field'=>'name'),
     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 ba53f99..f0c8ba1 100644 (file)
@@ -769,5 +769,66 @@ function xmldb_main_upgrade($oldversion) {
         upgrade_main_savepoint(true, 2012052900.05);
     }
 
+    if ($oldversion < 2012060600.01) {
+        // Add field referencehash to files_reference
+        $table = new xmldb_table('files_reference');
+        $field = new xmldb_field('referencehash', XMLDB_TYPE_CHAR, '40', null, XMLDB_NOTNULL, null, null, 'reference');
+        if (!$dbman->field_exists($table, $field)) {
+            $dbman->add_field($table, $field);
+        }
+        upgrade_main_savepoint(true, 2012060600.01);
+    }
+
+    if ($oldversion < 2012060600.02) {
+        // Populate referencehash field with SHA1 hash of the reference - this shoudl affect only 2.3dev sites
+        // that were using the feature for testing. Production sites have the table empty.
+        $rs = $DB->get_recordset('files_reference', null, '', 'id, reference');
+        foreach ($rs as $record) {
+            $hash = sha1($record->reference);
+            $DB->set_field('files_reference', 'referencehash', $hash, array('id' => $record->id));
+        }
+        $rs->close();
+
+        upgrade_main_savepoint(true, 2012060600.02);
+    }
+
+    if ($oldversion < 2012060600.03) {
+        // Merge duplicate records in files_reference that were created during the development
+        // phase at 2.3dev sites. This is needed so we can create the unique index over
+        // (repositoryid, referencehash) fields.
+        $sql = "SELECT repositoryid, referencehash, MIN(id) AS minid
+                  FROM {files_reference}
+              GROUP BY repositoryid, referencehash
+                HAVING COUNT(*) > 1";
+        $duprs = $DB->get_recordset_sql($sql);
+        foreach ($duprs as $duprec) {
+            // get the list of all ids in {files_reference} that need to be remapped
+            $dupids = $DB->get_records_select('files_reference', "repositoryid = ? AND referencehash = ? AND id > ?",
+                array($duprec->repositoryid, $duprec->referencehash, $duprec->minid), '', 'id');
+            $dupids = array_keys($dupids);
+            // relink records in {files} that are now referring to a duplicate record
+            // in {files_reference} to refer to the first one
+            list($subsql, $subparams) = $DB->get_in_or_equal($dupids);
+            $DB->set_field_select('files', 'referencefileid', $duprec->minid, "referencefileid $subsql", $subparams);
+            // and finally remove all orphaned records from {files_reference}
+            $DB->delete_records_list('files_reference', 'id', $dupids);
+        }
+        $duprs->close();
+
+        upgrade_main_savepoint(true, 2012060600.03);
+    }
+
+    if ($oldversion < 2012060600.04) {
+        // Add a unique index over repositoryid and referencehash fields in files_reference table
+        $table = new xmldb_table('files_reference');
+        $index = new xmldb_index('uq_external_file', XMLDB_INDEX_UNIQUE, array('repositoryid', 'referencehash'));
+
+        if (!$dbman->index_exists($table, $index)) {
+            $dbman->add_index($table, $index);
+        }
+
+        upgrade_main_savepoint(true, 2012060600.04);
+    }
+
     return true;
-}
\ No newline at end of file
+}
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..da21ace 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(PARAM_INT, $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 2f3ea57..6eb760f 100644 (file)
@@ -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 7aa8841..62a21df 100644 (file)
@@ -613,7 +613,7 @@ function file_get_drafarea_files($draftitemid, $filepath = '/') {
             } else {
                 // do NOT use file browser here!
                 $item->mimetype = get_mimetype_description($file);
-                if (file_mimetype_in_typegroup($item->mimetype, 'archive')) {
+                if (file_extension_in_typegroup($file->get_filename(), 'archive')) {
                     $item->type = 'zip';
                 } else {
                     $item->type = 'file';
@@ -2535,7 +2535,9 @@ function fulldelete($location) {
         return false;
     }
     if (is_dir($location)) {
-        $currdir = opendir($location);
+        if (!$currdir = opendir($location)) {
+            return false;
+        }
         while (false !== ($file = readdir($currdir))) {
             if ($file <> ".." && $file <> ".") {
                 $fullfile = $location."/".$file;
index 68c01de..425e304 100644 (file)
@@ -116,23 +116,31 @@ class file_pool_content_exception extends file_exception {
     }
 }
 
+
 /**
- * Exception related to external file support
+ * Problem with records in the {files_reference} table
  *
  * @package   core_files
- * @category  files
- * @copyright 2012 Dongsheng Cai {@link http://dongsheng.org}
+ * @catehory  files
+ * @copyright 2012 David Mudrak <david@moodle.com>
  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  */
-class external_file_exception extends file_exception {
+class file_reference_exception extends file_exception {
     /**
      * Constructor
      *
-     * @param string   $errorcode error code
-     * @param stdClass $a extra information
-     * @param string   $debuginfo extra debug info
+     * @param int $repositoryid the id of the repository that provides the referenced file
+     * @param string $reference the information for the repository to locate the file
+     * @param int|null $referencefileid the id of the record in {files_reference} if known
+     * @param int|null $fileid the id of the referrer's record in {files} if known
+     * @param string|null $debuginfo extra debug info
      */
-    public function __construct($errorcode, $a = null, $debuginfo = null) {
-        parent::__construct($errorcode, '', '', $a, $debuginfo);
+    function __construct($repositoryid, $reference, $referencefileid=null, $fileid=null, $debuginfo=null) {
+        $a = new stdClass();
+        $a->repositoryid = $repositoryid;
+        $a->reference = $reference;
+        $a->referencefileid = $referencefileid;
+        $a->fileid = $fileid;
+        parent::__construct('filereferenceproblem', $a, $debuginfo);
     }
 }
index d5ce7c1..203337a 100644 (file)
@@ -859,19 +859,13 @@ class file_storage {
             return $this->get_file_instance($newrecord);
         }
 
+        // note: referencefileid is copied from the original file so that
+        // creating a new file from an existing alias creates new alias implicitly.
+        // here we just check the database consistency.
         if (!empty($newrecord->repositoryid)) {
-            try {
-                $referencerecord = new stdClass;
-                $referencerecord->repositoryid = $newrecord->repositoryid;
-                $referencerecord->reference = $newrecord->reference;
-                $referencerecord->lastsync  = $newrecord->referencelastsync;
-                $referencerecord->lifetime  = $newrecord->referencelifetime;
-                $referencerecord->id = $DB->insert_record('files_reference', $referencerecord);
-            } catch (dml_exception $e) {
-                throw new stored_file_creation_exception($newrecord->contextid, $newrecord->component, $newrecord->filearea, $newrecord->itemid,
-                                                         $newrecord->filepath, $newrecord->filename, $e->debuginfo);
+            if ($newrecord->referencefileid != $this->get_referencefileid($newrecord->repositoryid, $newrecord->reference, MUST_EXIST)) {
+                throw new file_reference_exception($newrecord->repositoryid, $newrecord->reference, $newrecord->referencefileid);
             }
-            $newrecord->referencefileid = $referencerecord->id;
         }
 
         try {
@@ -1180,12 +1174,12 @@ class file_storage {
     }
 
     /**
-     * Create a moodle file from file reference information
+     * Create a new alias/shortcut file from file reference information
      *
-     * @param stdClass $filerecord
-     * @param int $repositoryid
-     * @param string $reference
-     * @param array $options options for creating external file
+     * @param stdClass|array $filerecord object or array describing the new file
+     * @param int $repositoryid the id of the repository that provides the original file
+     * @param string $reference the information required by the repository to locate the original file
+     * @param array $options options for creating the new file
      * @return stored_file
      */
     public function create_file_from_reference($filerecord, $repositoryid, $reference, $options = array()) {
@@ -1268,20 +1262,13 @@ class file_storage {
 
         $transaction = $DB->start_delegated_transaction();
 
-        // Insert file reference record.
         try {
-            $referencerecord = new stdClass;
-            $referencerecord->repositoryid = $repositoryid;
-            $referencerecord->reference = $reference;
-            $referencerecord->lastsync = $filerecord->referencelastsync;
-            $referencerecord->lifetime = $filerecord->referencelifetime;
-            $referencerecord->id = $DB->insert_record('files_reference', $referencerecord);
-        } catch (dml_exception $e) {
-            throw $e;
+            $filerecord->referencefileid = $this->get_or_create_referencefileid($repositoryid, $reference,
+                $filerecord->referencelastsync, $filerecord->referencelifetime);
+        } catch (Exception $e) {
+            throw new file_reference_exception($repositoryid, $reference, null, null, $e->getMessage());
         }
 
-        $filerecord->referencefileid = $referencerecord->id;
-
         // External file doesn't have content in moodle.
         // So we create an empty file for it.
         list($filerecord->contenthash, $filerecord->filesize, $newfile) = $this->add_string_to_pool(null);
@@ -1672,59 +1659,74 @@ class file_storage {
     }
 
     /**
-     * Search references by providing reference content
+     * Returns all aliases that link to an external file identified by the given reference
      *
-     * @param string $str
-     * @return array
+     * Aliases in user draft areas are excluded from the returned list.
+     *
+     * @param string $reference identification of the referenced file
+     * @return array of stored_file indexed by its pathnamehash
      */
-    public function search_references($str) {
+    public function search_references($reference) {
         global $DB;
+
+        if (is_null($reference)) {
+            throw new coding_exception('NULL is not a valid reference to an external file');
+        }
+
+        $referencehash = sha1($reference);
+
         $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
                   FROM {files} f
-             LEFT JOIN {files_reference} r
-                       ON f.referencefileid = r.id
-                 WHERE ".$DB->sql_compare_text('r.reference').' = '.$DB->sql_compare_text('?')."
-                 AND (f.component <> ? OR f.filearea <> ?)";
+                  JOIN {files_reference} r ON f.referencefileid = r.id
+                  JOIN {repository_instances} ri ON r.repositoryid = ri.id
+                 WHERE r.referencehash = ?
+                       AND (f.component <> ? OR f.filearea <> ?)";
 
-        $rs = $DB->get_recordset_sql($sql, array($str, 'user', 'draft'));
+        $rs = $DB->get_recordset_sql($sql, array($referencehash, 'user', 'draft'));
         $files = array();
         foreach ($rs as $filerecord) {
-            $file = $this->get_file_instance($filerecord);
-            if ($file->is_external_file()) {
-                $files[$filerecord->pathnamehash] = $file;
-            }
+            $files[$filerecord->pathnamehash] = $this->get_file_instance($filerecord);
         }
 
         return $files;
     }
 
     /**
-     * Search references count by providing reference content
+     * Returns the number of aliases that link to an external file identified by the given reference
      *
-     * @param string $str
+     * Aliases in user draft areas are not counted.
+     *
+     * @param string $reference identification of the referenced file
      * @return int
      */
-    public function search_references_count($str) {
+    public function search_references_count($reference) {
         global $DB;
+
+        if (is_null($reference)) {
+            throw new coding_exception('NULL is not a valid reference to an external file');
+        }
+
+        $referencehash = sha1($reference);
+
         $sql = "SELECT COUNT(f.id)
                   FROM {files} f
-             LEFT JOIN {files_reference} r
-                       ON f.referencefileid = r.id
-                 WHERE ".$DB->sql_compare_text('r.reference').' = '.$DB->sql_compare_text('?')."
-                 AND (f.component <> ? OR f.filearea <> ?)";
+                  JOIN {files_reference} r ON f.referencefileid = r.id
+                  JOIN {repository_instances} ri ON r.repositoryid = ri.id
+                 WHERE r.referencehash = ?
+                       AND (f.component <> ? OR f.filearea <> ?)";
 
-        $count = $DB->count_records_sql($sql, array($str, 'user', 'draft'));
-        return $count;
+        return $DB->count_records_sql($sql, array($referencehash, 'user', 'draft'));
     }
 
     /**
-     * Return all files referring to provided stored_file instance
-     * This won't work for draft files
+     * Returns all aliases that link to the given stored_file
+     *
+     * Aliases in user draft areas are excluded from the returned list.
      *
      * @param stored_file $storedfile
-     * @return array
+     * @return array of stored_file
      */
-    public function get_references_by_storedfile($storedfile) {
+    public function get_references_by_storedfile(stored_file $storedfile) {
         global $DB;
 
         $params = array();
@@ -1734,37 +1736,19 @@ class file_storage {
         $params['itemid']    = $storedfile->get_itemid();
         $params['filename']  = $storedfile->get_filename();
         $params['filepath']  = $storedfile->get_filepath();
-        $params['userid']    = $storedfile->get_userid();
-
-        $reference = self::pack_reference($params);
 
-        $sql = "SELECT ".self::instance_sql_fields('f', 'r')."
-                  FROM {files} f
-             LEFT JOIN {files_reference} r
-                       ON f.referencefileid = r.id
-                 WHERE ".$DB->sql_compare_text('r.reference').' = '.$DB->sql_compare_text('?')."
-                   AND (f.component <> ? OR f.filearea <> ?)";
-
-        $rs = $DB->get_recordset_sql($sql, array($reference, 'user', 'draft'));
-        $files = array();
-        foreach ($rs as $filerecord) {
-            $file = $this->get_file_instance($filerecord);
-            if ($file->is_external_file()) {
-                $files[$filerecord->pathnamehash] = $file;
-            }
-        }
-
-        return $files;
+        return $this->search_references(self::pack_reference($params));
     }
 
     /**
-     * Return the count files referring to provided stored_file instance
-     * This won't work for draft files
+     * Returns the number of aliases that link to the given stored_file
+     *
+     * Aliases in user draft areas are not counted.
      *
      * @param stored_file $storedfile
      * @return int
      */
-    public function get_references_count_by_storedfile($storedfile) {
+    public function get_references_count_by_storedfile(stored_file $storedfile) {
         global $DB;
 
         $params = array();
@@ -1774,19 +1758,8 @@ class file_storage {
         $params['itemid']    = $storedfile->get_itemid();
         $params['filename']  = $storedfile->get_filename();
         $params['filepath']  = $storedfile->get_filepath();
-        $params['userid']    = $storedfile->get_userid();
-
-        $reference = self::pack_reference($params);
-
-        $sql = "SELECT COUNT(f.id)
-                  FROM {files} f
-             LEFT JOIN {files_reference} r
-                       ON f.referencefileid = r.id
-                 WHERE ".$DB->sql_compare_text('r.reference').' = '.$DB->sql_compare_text('?')."
-                 AND (f.component <> ? OR f.filearea <> ?)";
 
-        $count = $DB->count_records_sql($sql, array($reference, 'user', 'draft'));
-        return $count;
+        return $this->search_references_count(self::pack_reference($params));
     }
 
     /**
@@ -1795,7 +1768,7 @@ class file_storage {
      * @param stored_file $storedfile a stored_file instances
      * @return stored_file stored_file
      */
-    public function import_external_file($storedfile) {
+    public function import_external_file(stored_file $storedfile) {
         global $CFG;
         require_once($CFG->dirroot.'/repository/lib.php');
         // sync external file
@@ -1925,5 +1898,60 @@ class file_storage {
 
         return implode(', ', $fields);
     }
-}
 
+    /**
+     * Returns the id of the record in {files_reference} that matches the passed repositoryid and reference
+     *
+     * If the record already exists, its id is returned. If there is no such record yet,
+     * new one is created (using the lastsync and lifetime provided, too) and its id is returned.
+     *
+     * @param int $repositoryid
+     * @param string $reference
+     * @return int
+     */
+    private function get_or_create_referencefileid($repositoryid, $reference, $lastsync = null, $lifetime = null) {
+        global $DB;
+
+        $id = $this->get_referencefileid($repositoryid, $reference, IGNORE_MISSING);
+
+        if ($id !== false) {
+            // bah, that was easy
+            return $id;
+        }
+
+        // no such record yet, create one
+        try {
+            $id = $DB->insert_record('files_reference', array(
+                'repositoryid'  => $repositoryid,
+                'reference'     => $reference,
+                'referencehash' => sha1($reference),
+                'lastsync'      => $lastsync,
+                'lifetime'      => $lifetime));
+        } catch (dml_exception $e) {
+            // if inserting the new record failed, chances are that the race condition has just
+            // occured and the unique index did not allow to create the second record with the same
+            // repositoryid + reference combo
+            $id = $this->get_referencefileid($repositoryid, $reference, MUST_EXIST);
+        }
+
+        return $id;
+    }
+
+    /**
+     * Returns the id of the record in {files_reference} that matches the passed parameters
+     *
+     * Depending on the required strictness, false can be returned. The behaviour is consistent
+     * with standard DML methods.
+     *
+     * @param int $repositoryid
+     * @param string $reference
+     * @param int $strictness either {@link IGNORE_MISSING}, {@link IGNORE_MULTIPLE} or {@link MUST_EXIST}
+     * @return int|bool
+     */
+    private function get_referencefileid($repositoryid, $reference, $strictness) {
+        global $DB;
+
+        return $DB->get_field('files_reference', 'id',
+            array('repositoryid' => $repositoryid, 'referencehash' => sha1($reference)), $strictness);
+    }
+}
index c936716..4b7bf09 100644 (file)
@@ -142,8 +142,10 @@ class stored_file {
                     }
                 }
 
-                if ($field == 'referencefileid' or $field == 'referencelastsync' or $field == 'referencelifetime') {
-                    $value = clean_param($value, PARAM_INT);
+                if ($field === 'referencefileid' or $field === 'referencelastsync' or $field === 'referencelifetime') {
+                    if (!is_null($value) and !is_number($value)) {
+                        throw new file_exception('storedfileproblem', 'Invalid reference info');
+                    }
                 }
 
                 // adding the field
@@ -161,7 +163,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);
@@ -196,36 +198,46 @@ class stored_file {
     }
 
     /**
-     * Delete file reference
+     * Unlink the stored file from the referenced file
      *
+     * This methods destroys the link to the record in files_reference table. This effectively
+     * turns the stored file from being an alias to a plain copy. However, the caller has
+     * to make sure that the actual file's content has beed synced prior to calling this method.
      */
     public function delete_reference() {
         global $DB;
 
-        // Remove repository info.
-        $this->repository = null;
+        if (!$this->is_external_file()) {
+            throw new coding_exception('An attempt to unlink a non-reference file.');
+        }
 
         $transaction = $DB->start_delegated_transaction();
 
-        // Remove reference info from DB.
-        $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid));
+        // Are we the only one referring to the original file? If so, delete the
+        // referenced file record. Note we do not use file_storage::search_references_count()
+        // here because we want to count draft files too and we are at a bit lower access level here.
+        $countlinks = $DB->count_records('files',
+            array('referencefileid' => $this->file_record->referencefileid));
+        if ($countlinks == 1) {
+            $DB->delete_records('files_reference', array('id' => $this->file_record->referencefileid));
+        }
 
-        // Must refresh $this->file_record form DB
-        $filerecord = $DB->get_record('files', array('id'=>$this->get_id()));
-        // Update DB
-        $filerecord->referencelastsync = null;
-        $filerecord->referencelifetime = null;
-        $filerecord->referencefileid = null;
-        $this->update($filerecord);
+        // Update the underlying record in the database.
+        $update = new stdClass();
+        $update->referencefileid = null;
+        $update->referencelastsync = null;
+        $update->referencelifetime = null;
+        $this->update($update);
 
         $transaction->allow_commit();
 
-        // unset object variable
-        unset($this->file_record->repositoryid);
-        unset($this->file_record->reference);
-        unset($this->file_record->referencelastsync);
-        unset($this->file_record->referencelifetime);
-        unset($this->file_record->referencefileid);
+        // Update our properties and the record in the memory.
+        $this->repository = null;
+        $this->file_record->repositoryid = null;
+        $this->file_record->reference = null;
+        $this->file_record->referencefileid = null;
+        $this->file_record->referencelastsync = null;
+        $this->file_record->referencelifetime = null;
     }
 
     /**
@@ -254,15 +266,20 @@ class stored_file {
 
         $transaction = $DB->start_delegated_transaction();
 
-        // If other files referring to this file, we need convert them
+        // If there are other files referring to this file, convert them to copies.
         if ($files = $this->fs->get_references_by_storedfile($this)) {
             foreach ($files as $file) {
                 $this->fs->import_external_file($file);
             }
         }
-        // Now delete file records in DB
+
+        // If this file is a reference (alias) to another file, unlink it first.
+        if ($this->is_external_file()) {
+            $this->delete_reference();
+        }
+
+        // Now delete the file record.
         $DB->delete_records('files', array('id'=>$this->file_record->id));
-        $DB->delete_records('files_reference', array('id'=>$this->file_record->referencefileid));
 
         $transaction->allow_commit();
 
index 41feea1..818d47b 100644 (file)
@@ -297,7 +297,7 @@ class filestoragelib_testcase extends advanced_testcase {
             $this->assertEquals($key, $file->get_pathnamehash());
         }
 
-        // Get area files ordered by id (was breaking on oracle).
+        // Get area files ordered by id.
         $filesbyid  = $fs->get_area_files($user->ctxid, 'user', 'private', false, 'id', false);
         // Should be the two files without folder.
         $this->assertEquals(3, count($filesbyid));
@@ -405,8 +405,6 @@ class filestoragelib_testcase extends advanced_testcase {
         $fs = get_file_storage();
 
         $areafiles = $fs->get_area_files($user->ctxid, 'user', 'private');
-        //Test get_file_by_hash
-
         $testfile = reset($areafiles);
         $references = $fs->get_references_by_storedfile($testfile);
         // TODO MDL-33368 Verify result!!
@@ -420,9 +418,57 @@ class filestoragelib_testcase extends advanced_testcase {
         $userrepository = reset($repos);
         $this->assertInstanceOf('repository', $userrepository);
 
-        // This should break on oracle.
-        $fs->get_external_files($userrepository->id, 'id');
-        // TODO MDL-33368 Verify result!!
+        // no aliases yet
+        $exfiles = $fs->get_external_files($userrepository->id, 'id');
+        $this->assertEquals(array(), $exfiles);
+
+        // create three aliases linking the same original: $aliasfile1 and $aliasfile2 are
+        // created via create_file_from_reference(), $aliasfile3 created from $aliasfile2
+        $originalfile = null;
+        foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
+            if (!$areafile->is_directory()) {
+                $originalfile = $areafile;
+                break;
+            }
+        }
+        $this->assertInstanceOf('stored_file', $originalfile);
+        $originalrecord = array(
+            'contextid' => $originalfile->get_contextid(),
+            'component' => $originalfile->get_component(),
+            'filearea'  => $originalfile->get_filearea(),
+            'itemid'    => $originalfile->get_itemid(),
+            'filepath'  => $originalfile->get_filepath(),
+            'filename'  => $originalfile->get_filename(),
+        );
+
+        $aliasrecord = $this->generate_file_record();
+        $aliasrecord->filepath = '/foo/';
+        $aliasrecord->filename = 'one.txt';
+
+        $ref = $fs->pack_reference($originalrecord);
+        $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
+
+        $aliasrecord->filepath = '/bar/';
+        $aliasrecord->filename = 'uno.txt';
+        // change the order of the items in the array to make sure that it does not matter
+        ksort($originalrecord);
+        $ref = $fs->pack_reference($originalrecord);
+        $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $userrepository->id, $ref);
+
+        $aliasrecord->filepath = '/bar/';
+        $aliasrecord->filename = 'jedna.txt';
+        $aliasfile3 = $fs->create_file_from_storedfile($aliasrecord, $aliasfile2);
+
+        // make sure we get three aliases now
+        $exfiles = $fs->get_external_files($userrepository->id, 'id');
+        $this->assertEquals(3, count($exfiles));
+        foreach ($exfiles as $exfile) {
+            $this->assertTrue($exfile->is_external_file());
+        }
+        // make sure they all link the same original (thence that all are linked with the same
+        // record in {files_reference})
+        $this->assertEquals($aliasfile1->get_referencefileid(), $aliasfile2->get_referencefileid());
+        $this->assertEquals($aliasfile3->get_referencefileid(), $aliasfile2->get_referencefileid());
     }
 
     public function test_create_directory_contextid_negative() {
@@ -1166,4 +1212,90 @@ class filestoragelib_testcase extends advanced_testcase {
         $this->setExpectedException('stored_file_creation_exception');
         $file2 = $fs->create_file_from_pathname($filerecord, $path);
     }
+
+    /**
+     * Calling stored_file::delete_reference() on a non-reference file throws coding_exception
+     */
+    public function test_delete_reference_on_nonreference() {
+
+        $this->resetAfterTest(true);
+        $user = $this->setup_three_private_files();
+        $fs = get_file_storage();
+        $repos = repository::get_instances(array('type'=>'user'));
+        $repo = reset($repos);
+
+        $file = null;
+        foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
+            if (!$areafile->is_directory()) {
+                $file = $areafile;
+                break;
+            }
+        }
+        $this->assertInstanceOf('stored_file', $file);
+        $this->assertFalse($file->is_external_file());
+
+        $this->setExpectedException('coding_exception');
+        $file->delete_reference();
+    }
+
+    /**
+     * Calling stored_file::delete_reference() on a reference file does not affect other
+     * symlinks to the same original
+     */
+    public function test_delete_reference_one_symlink_does_not_rule_them_all() {
+
+        $this->resetAfterTest(true);
+        $user = $this->setup_three_private_files();
+        $fs = get_file_storage();
+        $repos = repository::get_instances(array('type'=>'user'));
+        $repo = reset($repos);
+
+        // create two aliases linking the same original
+
+        $originalfile = null;
+        foreach ($fs->get_area_files($user->ctxid, 'user', 'private') as $areafile) {
+            if (!$areafile->is_directory()) {
+                $originalfile = $areafile;
+                break;
+            }
+        }
+        $this->assertInstanceOf('stored_file', $originalfile);
+
+        // calling delete_reference() on a non-reference file
+
+        $originalrecord = array(
+            'contextid' => $originalfile->get_contextid(),
+            'component' => $originalfile->get_component(),
+            'filearea'  => $originalfile->get_filearea(),
+            'itemid'    => $originalfile->get_itemid(),
+            'filepath'  => $originalfile->get_filepath(),
+            'filename'  => $originalfile->get_filename(),
+        );
+
+        $aliasrecord = $this->generate_file_record();
+        $aliasrecord->filepath = '/A/';
+        $aliasrecord->filename = 'symlink.txt';
+
+        $ref = $fs->pack_reference($originalrecord);
+        $aliasfile1 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
+
+        $aliasrecord->filepath = '/B/';
+        $aliasrecord->filename = 'symlink.txt';
+        $ref = $fs->pack_reference($originalrecord);
+        $aliasfile2 = $fs->create_file_from_reference($aliasrecord, $repo->id, $ref);
+
+        // refetch A/symlink.txt
+        $symlink1 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
+            $aliasrecord->filearea, $aliasrecord->itemid, '/A/', 'symlink.txt');
+        $this->assertTrue($symlink1->is_external_file());
+
+        // unlink the A/symlink.txt
+        $symlink1->delete_reference();
+        $this->assertFalse($symlink1->is_external_file());
+
+        // make sure that B/symlink.txt has not been affected
+        $symlink2 = $fs->get_file($aliasrecord->contextid, $aliasrecord->component,
+            $aliasrecord->filearea, $aliasrecord->itemid, '/B/', 'symlink.txt');
+        $this->assertTrue($symlink2->is_external_file());
+    }
 }
index f36580b..0f054a5 100644 (file)
@@ -52,7 +52,8 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
 
     /** @var array options provided to initalize filepicker */
     protected $_options    = array('subdirs'=>0, 'maxbytes'=>0, 'maxfiles'=>0, 'changeformat'=>0,
-                                   'context'=>null, 'noclean'=>0, 'trusttext'=>0);
+                                   'context'=>null, 'noclean'=>0, 'trusttext'=>0, 'return_types'=>7);
+    // $_options['return_types'] = FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE
 
     /** @var array values for editor */
     protected $_values     = array('text'=>null, 'format'=>null, 'itemid'=>null);
@@ -303,7 +304,7 @@ class MoodleQuickForm_editor extends HTML_QuickForm_element {
             $args = new stdClass();
             // need these three to filter repositories list
             $args->accepted_types = array('web_image');
-            $args->return_types = (FILE_INTERNAL | FILE_EXTERNAL | FILE_REFERENCE);
+            $args->return_types = $this->_options['return_types'];
             $args->context = $ctx;
             $args->env = 'filepicker';
             // advimage plugin
index 6627c2a..3b2bd3e 100644 (file)
@@ -100,9 +100,8 @@ M.form_filemanager.init = function(Y, options) {
                 this.pathbar.removeChild(this.pathnode);
             }
             // initialize 'select file' panel
-            this.selectnode = Y.Node.create(M.form_filemanager.templates.fileselectlayout);
+            this.selectnode = Y.Node.createWithFilesSkin(M.form_filemanager.templates.fileselectlayout);
             this.selectnode.generateID();
-            Y.one(document.body).appendChild(this.selectnode);
             this.selectui = new Y.Panel({
                 srcNode      : this.selectnode,
                 zIndex       : 600000,
@@ -234,27 +233,27 @@ M.form_filemanager.init = function(Y, options) {
                 header = M.str.moodle.info;
             }
             if (!this.msg_dlg) {
-                var node = Y.Node.create(M.form_filemanager.templates.message);
-                this.filemanager.appendChild(node);
+                this.msg_dlg_node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.message);
+                var nodeid = this.msg_dlg_node.generateID();
 
                 this.msg_dlg = new Y.Panel({
-                    srcNode      : node,
+                    srcNode      : this.msg_dlg_node,
                     zIndex       : 800000,
                     centered     : true,
                     modal        : true,
                     visible      : false,
                     render       : true
                 });
-                this.msg_dlg.plug(Y.Plugin.Drag,{handles:['.yui3-widget-hd']});
-                node.one('.fp-msg-butok').on('click', function(e) {
+                this.msg_dlg.plug(Y.Plugin.Drag,{handles:['#'+nodeid+' .yui3-widget-hd']});
+                this.msg_dlg_node.one('.fp-msg-butok').on('click', function(e) {
                     e.preventDefault();
                     this.msg_dlg.hide();
                 }, this);
             }
 
             this.msg_dlg.set('headerContent', header);
-            this.filemanager.one('.fp-msg').removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
-            this.filemanager.one('.fp-msg .fp-msg-text').setContent(msg);
+            this.msg_dlg_node.removeClass('fp-msg-info').removeClass('fp-msg-error').addClass('fp-msg-'+type)
+            this.msg_dlg_node.one('.fp-msg-text').setContent(msg);
             this.msg_dlg.show();
         },
         setup_buttons: function() {
@@ -302,8 +301,7 @@ M.form_filemanager.init = function(Y, options) {
                         });
                     }
                     if (!this.mkdir_dialog) {
-                        var node = Y.Node.create(M.form_filemanager.templates.mkdir);
-                        this.filemanager.appendChild(node);
+                        var node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.mkdir);
                         this.mkdir_dialog = new Y.Panel({
                             srcNode      : node,
                             zIndex       : 800000,
@@ -679,10 +677,9 @@ M.form_filemanager.init = function(Y, options) {
         show_confirm_dialog: function(dialog_options) {
             // instead of M.util.show_confirm_dialog(e, dialog_options);
             if (!this.confirm_dlg) {
-                this.confirm_dlg_node = Y.Node.create(M.form_filemanager.templates.confirmdialog);
+                this.confirm_dlg_node = Y.Node.createWithFilesSkin(M.form_filemanager.templates.confirmdialog);
                 var node = this.confirm_dlg_node;
                 node.generateID();
-                Y.one(document.body).appendChild(node);
                 this.confirm_dlg = new Y.Panel({
                     srcNode      : node,
                     zIndex       : 800000,
@@ -867,7 +864,7 @@ M.form_filemanager.init = function(Y, options) {
             this.selectui.fileinfo = node;
             selectnode.one('.fp-saveas input').set('value', node.fullname);
             var foldername = this.get_parent_folder_name(node);
-            selectnode.all('.fp-author input').set('value', node.author);
+            selectnode.all('.fp-author input').set('value', node.author ? node.author : '');
             selectnode.all('.fp-license select option[selected]').set('selected', false);
             selectnode.all('.fp-license select option[value='+node.license+']').set('selected', true);
             selectnode.all('.fp-path select option[selected]').set('selected', false);
index 5994dd3..fe2b933 100644 (file)
@@ -56,6 +56,15 @@ class google_docs {
      */
     public function __construct(google_oauth $googleoauth) {
         $this->googleoauth = $googleoauth;
+        $this->reset_curl_state();
+    }
+
+    /**
+     * Resets state on oauth curl object and set GData protocol
+     * version
+     */
+    private function reset_curl_state() {
+        $this->googleoauth->reset_state();
         $this->googleoauth->setHeader('GData-Version: 3.0');
     }
 
@@ -147,7 +156,7 @@ class google_docs {
         }
 
         // Reset the curl object for actually sending the file.
-        $this->googleoauth->clear_headers();
+        $this->reset_curl_state();
         $this->googleoauth->setHeader("Content-Length: ". $file->get_filesize());
         $this->googleoauth->setHeader("Content-Type: ". $file->get_mimetype());
 
@@ -163,6 +172,8 @@ class google_docs {
         unlink($tmpfilepath);
 
         if ($this->googleoauth->info['http_code'] === 201) {
+            // Clear headers for further requests.
+            $this->reset_curl_state();
             return true;
         } else {
             return false;
@@ -398,9 +409,10 @@ class google_oauth extends oauth2_client {
     }
 
     /**
-     * Clear any headers in the curl object
+     * Resets headers and response for multiple requests
      */
-    public function clear_headers() {
+    public function reset_state() {
         $this->header = array();
+        $this->response = array();
     }
 }
index 0dd6edf..f4674ce 100644 (file)
@@ -389,7 +389,7 @@ function message_get_providers_for_user($userid) {
 
     $systemcontext = get_context_instance(CONTEXT_SYSTEM);
 
-    $providers = $DB->get_records('message_providers', null, 'name');
+    $providers = get_message_providers();
 
     // Remove all the providers we aren't allowed to see now
     foreach ($providers as $providerid => $provider) {
index 23ee2f5..266a67d 100644 (file)
@@ -132,6 +132,14 @@ define('PARAM_FILE',   'file');
 
 /**
  * PARAM_FLOAT - a real/floating point number.
+ *
+ * Note that you should not use PARAM_FLOAT for numbers typed in by the user.
+ * It does not work for languages that use , as a decimal separator.
+ * Instead, do something like
+ *     $rawvalue = required_param('name', PARAM_RAW);
+ *     // ... other code including require_login, which sets current lang ...
+ *     $realvalue = unformat_float($rawvalue);
+ *     // ... then use $realvalue
  */
 define('PARAM_FLOAT',  'float');
 
@@ -1127,15 +1135,39 @@ function fix_utf8($value) {
             // shortcut
             return $value;
         }
-        // lower error reporting because glibc throws bogus notices
+
+        // Lower error reporting because glibc throws bogus notices.
         $olderror = error_reporting();
         if ($olderror & E_NOTICE) {
             error_reporting($olderror ^ E_NOTICE);
         }
-        $result = iconv('UTF-8', 'UTF-8//IGNORE', $value);
+
+        // Note: this duplicates min_fix_utf8() intentionally.
+        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;
 
     } else if (is_array($value)) {
@@ -2013,8 +2045,17 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
 
     $timezone = get_user_timezone_offset($timezone);
 
+    // If we are running under Windows convert to windows encoding and then back to UTF-8
+    // (because it's impossible to specify UTF-8 to fetch locale info in Win32)
+
     if (abs($timezone) > 13) {   /// Server time
-        $datestring = strftime($format, $date);
+        if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
+            $format = textlib::convert($format, 'utf-8', $localewincharset);
+            $datestring = strftime($format, $date);
+            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
+        } else {
+            $datestring = strftime($format, $date);
+        }
         if ($fixday) {
             $daystring  = ltrim(str_replace(array(' 0', ' '), '', strftime(' %d', $date)));
             $datestring = str_replace('DD', $daystring, $datestring);
@@ -2023,9 +2064,16 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
             $hourstring = ltrim(str_replace(array(' 0', ' '), '', strftime(' %I', $date)));
             $datestring = str_replace('HH', $hourstring, $datestring);
         }
+
     } else {
         $date += (int)($timezone * 3600);
-        $datestring = gmstrftime($format, $date);
+        if ($CFG->ostype == 'WINDOWS' and ($localewincharset = get_string('localewincharset', 'langconfig'))) {
+            $format = textlib::convert($format, 'utf-8', $localewincharset);
+            $datestring = gmstrftime($format, $date);
+            $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
+        } else {
+            $datestring = gmstrftime($format, $date);
+        }
         if ($fixday) {
             $daystring  = ltrim(str_replace(array(' 0', ' '), '', gmstrftime(' %d', $date)));
             $datestring = str_replace('DD', $daystring, $datestring);
@@ -2036,15 +2084,6 @@ function userdate($date, $format = '', $timezone = 99, $fixday = true, $fixhour
         }
     }
 
-/// If we are running under Windows convert from windows encoding to UTF-8
-/// (because it's impossible to specify UTF-8 to fetch locale info in Win32)
-
-   if ($CFG->ostype == 'WINDOWS') {
-       if ($localewincharset = get_string('localewincharset', 'langconfig')) {
-           $datestring = textlib::convert($datestring, $localewincharset, 'utf-8');
-       }
-    }
-
     return $datestring;
 }
 
@@ -8005,7 +8044,10 @@ function get_list_of_plugins($directory='mod', $exclude='', $basedir='') {
     }
 
     if (file_exists($basedir) && filetype($basedir) == 'dir') {
-        $dirhandle = opendir($basedir);
+        if (!$dirhandle = opendir($basedir)) {
+            debugging("Directory permission error for plugin ({$directory}). Directory exists but cannot be read.", DEBUG_DEVELOPER);
+            return array();
+        }
         while (false !== ($dir = readdir($dirhandle))) {
             $firstchar = substr($dir, 0, 1);
             if ($firstchar === '.' or $dir === 'CVS' or $dir === '_vti_cnf' or $dir === 'simpletest' or $dir === 'yui' or $dir === 'phpunit' or $dir === $exclude) {
@@ -10201,7 +10243,7 @@ function apd_get_profiling() {
 }
 
 /**
- * Delete directory or only it's content
+ * Delete directory or only its content
  *
  * @param string $dir directory path
  * @param bool $content_only
@@ -10212,7 +10254,9 @@ function remove_dir($dir, $content_only=false) {
         // nothing to do
         return true;
     }
-    $handle = opendir($dir);
+    if (!$handle = opendir($dir)) {
+        return false;
+    }
     $result = true;
     while (false!==($item = readdir($handle))) {
         if($item != '.' && $item != '..') {
index d24b735..54ab6cc 100644 (file)
@@ -150,7 +150,7 @@ class navigation_node implements renderable {
                 $this->shorttext = $properties['shorttext'];
             }
             if (!array_key_exists('icon', $properties)) {
-                $properties['icon'] = new pix_icon('i/navigationitem', 'moodle');
+                $properties['icon'] = new pix_icon('i/navigationitem', '');
             }
             $this->icon = $properties['icon'];
             if ($this->icon instanceof pix_icon) {
@@ -3308,6 +3308,9 @@ class settings_navigation extends navigation_node {
             $this->add(get_string('returntooriginaluser', 'moodle', fullname($realuser, true)), $url, self::TYPE_SETTING, null, null, new pix_icon('t/left', ''));
         }
 
+        // At this point we give any local plugins the ability to extend/tinker with the navigation settings.
+        $this->load_local_plugin_settings();
+
         foreach ($this->children as $key=>$node) {
             if ($node->nodetype != self::NODETYPE_BRANCH || $node->children->count()===0) {
                 $node->remove();
@@ -4392,6 +4395,17 @@ class settings_navigation extends navigation_node {
         return $frontpage;
     }
 
+    /**
+     * This function gives local plugins an opportunity to modify the settings navigation.
+     */
+    protected function load_local_plugin_settings() {
+        // Get all local plugins with an extend_settings_navigation function in their lib.php file
+        foreach (get_plugin_list_with_function('local', 'extends_settings_navigation') as $function) {
+            // Call each function providing this (the settings navigation) and the current context.
+            $function($this, $this->context);
+        }
+    }
+
     /**
      * This function marks the cache as volatile so it is cleared during shutdown
      */
index b4aa15b..587ba19 100644 (file)
@@ -368,8 +368,8 @@ abstract class oauth2_client extends curl {
     private $clientid = '';
     /** var string The client secret. */
     private $clientsecret = '';
-    /** var string URL to return to after authenticating */
-    private $returnurl = '';
+    /** var moodle_url URL to return to after authenticating */
+    private $returnurl = null;
     /** var string scope of the authentication request */
     private $scope = '';
     /** var stdClass access token object */
@@ -392,10 +392,10 @@ abstract class oauth2_client extends curl {
      *
      * @param string $clientid
      * @param string $clientsecret
-     * @param string $returnurl
+     * @param moodle_url $returnurl
      * @param string $scope
      */
-    public function __construct($clientid, $clientsecret, $returnurl, $scope) {
+    public function __construct($clientid, $clientsecret, moodle_url $returnurl, $scope) {
         parent::__construct();
         $this->clientid = $clientid;
         $this->clientsecret = $clientsecret;
@@ -425,7 +425,7 @@ abstract class oauth2_client extends curl {
 
         // If we've been passed then authorization code generated by the
         // authorization server try and upgrade the token to an access token.
-        $code = optional_param('code', null, PARAM_RAW);
+        $code = optional_param('oauth2code', null, PARAM_RAW);
         if ($code && $this->upgrade_token($code)) {
             return true;
         }
@@ -456,7 +456,7 @@ abstract class oauth2_client extends curl {
                         array('client_id' => $this->clientid,
                               'response_type' => 'code',
                               'redirect_uri' => $callbackurl->out(false),
-                              'state' => $this->returnurl,
+                              'state' => $this->returnurl->out_as_local_url(false),
                               'scope' => $this->scope,
                           ));
 
index aa8755e..c4fdd8f 100644 (file)
@@ -152,7 +152,8 @@ class page_requirements_manager {
     public function __construct() {
         global $CFG;
 
-        $sep = empty($CFG->slasharguments) ? '?' : '/';
+        // You may need to set up URL rewrite rule because oversized URLs might not be allowed by web server.
+        $sep = empty($CFG->yuislasharguments) ? '?' : '/';
 
         require_once("$CFG->libdir/yui/phploader/phploader/loader.php");
 
index 3c191dc..d28de8d 100644 (file)
@@ -63,10 +63,12 @@ function phpunit_bootstrap_error($errorcode, $text = '') {
             $text = "Moodle PHPUnit environment configuration warning:\n".$text;
             break;
         case PHPUNIT_EXITCODE_INSTALL:
-            $text = "Moodle PHPUnit environment is not initialised, please use:\n php admin/tool/phpunit/cli/init.php";
+            $path = phpunit_bootstrap_cli_argument_path('/admin/tool/phpunit/cli/init.php');
+            $text = "Moodle PHPUnit environment is not initialised, please use:\n php $path";
             break;
         case PHPUNIT_EXITCODE_REINSTALL:
-            $text = "Moodle PHPUnit environment was initialised for different version, please use:\n php admin/tool/phpunit/cli/init.php";
+            $path = phpunit_bootstrap_cli_argument_path('/admin/tool/phpunit/cli/init.php');
+            $text = "Moodle PHPUnit environment was initialised for different version, please use:\n php $path";
             break;
         default:
             $text = empty($text) ? '' : ': '.$text;
@@ -79,6 +81,32 @@ function phpunit_bootstrap_error($errorcode, $text = '') {
     exit($errorcode);
 }
 
+/**
+ * Returns relative path against current working directory,
+ * to be used for shell execution hints.
+ * @param string $moodlepath starting with "/", ex: "/admin/tool/cli/init.php"
+ * @return string path relative to current directory or absolute path
+ */
+function phpunit_bootstrap_cli_argument_path($moodlepath) {
+    global $CFG;
+
+    if (isset($CFG->admin) and $CFG->admin !== 'admin') {
+        $moodlepath = preg_replace('|^/admin/|', "/$CFG->admin/", $moodlepath);
+    }
+
+    $cwd = getcwd();
+    if (substr($cwd, -1) !== DIRECTORY_SEPARATOR) {
+        $cwd .= DIRECTORY_SEPARATOR;
+    }
+    $path = realpath($CFG->dirroot.$moodlepath);
+
+    if (strpos($path, $cwd) === 0) {
+        return substr($path, strlen($cwd));
+    }
+
+    return $path;
+}
+
 /**
  * Mark empty dataroot to be used for testing.
  * @param string $dataroot The dataroot directory
index 9cd8585..a700157 100644 (file)
@@ -34,4 +34,5 @@ require_once(__DIR__.'/classes/database_driver_testcase.php');
 require_once(__DIR__.'/classes/arraydataset.php');
 require_once(__DIR__.'/classes/advanced_testcase.php');
 require_once(__DIR__.'/classes/unittestcase.php');
+require_once(__DIR__.'/classes/hint_resultprinter.php'); // Loaded here because phpunit.xml does not support relative links for printerFile
 
index 20f8a34..75a8186 100644 (file)
@@ -506,7 +506,7 @@ class plugin_manager {
             ),
 
             'repository' => array(
-                'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'filesystem',
+                'alfresco', 'boxnet', 'coursefiles', 'dropbox', 'equella', 'filesystem',
                 'flickr', 'flickr_public', 'googledocs', 'local', 'merlot',
                 'picasa', 'recent', 's3', 'upload', 'url', 'user', 'webdav',
                 'wikimedia', 'youtube'
index 01ac161..7c33d91 100644 (file)
@@ -110,7 +110,10 @@ function rss_delete_file($componentname, $instance) {
 
     $dirpath = "$CFG->cachedir/rss/$componentname";
     if (is_dir($dirpath)) {
-        $dh  = opendir($dirpath);
+        if (!$dh = opendir($dirpath)) {
+            error_log("Directory permission error. RSS directory store for component '{$componentname}' exists but cannot be opened.", DEBUG_DEVELOPER);
+            return;
+        }
         while (false !== ($filename = readdir($dh))) {
             if ($filename!='.' && $filename!='..') {
                 if (preg_match("/{$instance->id}_/", $filename)) {
index 23cf9e1..74f6520 100644 (file)
@@ -488,8 +488,6 @@ if (PHPUNIT_TEST and !PHPUNIT_UTIL) {
         if ($dbhash) {
             // we ned to reinit if reset fails
             $DB->set_field('config', 'value', 'na', array('name'=>'phpunittest'));
-        } else {
-            throw $e;
         }
     }
     unset($dbhash);
index 2c471f6..4fec92d 100644 (file)
@@ -133,7 +133,7 @@ class moodle_exception extends Exception {
         $this->module    = $module;
         $this->link      = $link;
         $this->a         = $a;
-        $this->debuginfo = $debuginfo;
+        $this->debuginfo = is_null($debuginfo) ? null : (string)$debuginfo;
 
         if (get_string_manager()->string_exists($errorcode, $module)) {
             $message = get_string($errorcode, $module, $a);
@@ -486,7 +486,7 @@ function get_exception_info($ex) {
         $module = 'error';
         $a = $ex->getMessage();
         $link = '';
-        $debuginfo = null;
+        $debuginfo = '';
     }
 
     // Append the error code to the debug info to make grepping and googling easier
index e991bea..b810564 100644 (file)
@@ -131,17 +131,6 @@ class flexible_table {
         );
     }
 
-    /**
-     * Backwards-compatible constructor, so that legacy code subclassing
-     * flexible_table does not break.
-     * @deprecated since Moodle 2.0. Will be removed in Moodle 2.1.
-     */
-    function flexible_table($uniqueid) {
-        debugging('Please update your code to user PHP5-style parent::__construct(...), ' .
-                'not parent::flexible_table(...).');
-        $this->__construct($uniqueid);
-    }
-
     /**
      * Call this to pass the download type. Use :
      *         $download = optional_param('download', '', PARAM_ALPHA);
diff --git a/lib/tests/configonlylib_test.php b/lib/tests/configonlylib_test.php
new file mode 100644 (file)
index 0000000..dc97baa
--- /dev/null
@@ -0,0 +1,144 @@
+<?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/>.
+
+/**
+ * Unit tests for config only library functions-
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+// Global CFG not used here intentionally to make sure it is not required inside the lib.
+require_once(__DIR__ . '/../configonlylib.php');
+
+
+/**
+ * Unit tests for config only library functions.
+ *
+ * @package    core
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class core_configonlylib_testcase extends advanced_testcase {
+
+    /**
+     * Test cleaning of invalid utf-8 entities.
+     */
+    public function test_min_fix_utf8() {
+        $this->assertSame('abc', min_fix_utf8('abc'));
+        $this->assertSame("žlutý koníček přeskočil potůček \n\t\r\0", min_fix_utf8("žlutý koníček přeskočil potůček \n\t\r\0"));
+        $this->assertSame('aš', min_fix_utf8('a'.chr(130).'š'), 'This fails with buggy iconv() when mbstring extenstion is not available as fallback.');
+    }
+
+    /**