Merge branch 'MDL-37413-m23' of git://github.com/andrewnicols/moodle into MOODLE_23_S...
authorDan Poltawski <dan@moodle.com>
Tue, 15 Jan 2013 02:58:47 +0000 (10:58 +0800)
committerDan Poltawski <dan@moodle.com>
Tue, 15 Jan 2013 02:58:47 +0000 (10:58 +0800)
80 files changed:
admin/plugins.php
admin/tool/uploaduser/index.php
auth/shibboleth/index_form.html
auth/shibboleth/lang/en/auth_shibboleth.php
backup/backupfilesedit.php
backup/converter/moodle1/lib.php
backup/converter/moodle1/tests/lib_test.php
backup/moodle2/restore_stepslib.php
blocks/rss_client/backup/moodle2/restore_rss_client_stepslib.php
blocks/tags/edit_form.php
blocks/tags/lang/en/block_tags.php
blog/rsslib.php
calendar/renderer.php
comment/comment_post.php
course/dndupload.js
course/dnduploadlib.php
course/edit_form.php
course/editsection.php
course/editsection_form.php
course/format/renderer.php
course/switchrole.php
course/yui/modchooser/modchooser.js
enrol/database/tests/adodb_test.php
enrol/otherusers.php
files/renderer.php
grade/edit/tree/lib.php
group/externallib.php
group/lib.php
install/lang/nl/install.php
lang/en/error.php
lang/en/moodle.php
lang/en/question.php
lang/en/repository.php
lib/adminlib.php
lib/ddl/postgres_sql_generator.php
lib/dml/pgsql_native_moodle_database.php
lib/editor/tinymce/tiny_mce/3.5.1.1/plugins/spellchecker/classes/GoogleSpell.php
lib/filebrowser/file_info_context_course.php
lib/filebrowser/file_info_context_coursecat.php
lib/filestorage/zip_archive.php
lib/form/filemanager.js
lib/moodlelib.php
lib/rsslib.php
lib/tests/textlib_test.php
lib/textlib.class.php
lib/weblib.php
lib/yui/formautosubmit/formautosubmit.js
mnet/lib.php
mod/assign/locallib.php
mod/assign/submission/comments/lib.php
mod/book/tool/importhtml/locallib.php
mod/forum/lib.php
mod/forum/rsslib.php
mod/forum/subscribe.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/quiz/lang/en/quiz.php
mod/quiz/mod_form.php
mod/quiz/settings.php
mod/scorm/report/reportlib.php
mod/wiki/filesedit.php
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
question/format/learnwise/format.php
question/format/learnwise/lang/en/qformat_learnwise.php
question/preview.php
question/previewlib.php
question/type/shortanswer/question.php
question/type/shortanswer/tests/question_test.php
report/outline/index.php
repository/draftfiles_ajax.php
repository/draftfiles_manager.php
repository/s3/lib.php
tag/coursetags_add.php
theme/base/style/core.css
theme/base/style/filemanager.css
user/files.php
user/message.html
user/messageselect.php
user/view.php
version.php

index 943f23a..02eeb6d 100644 (file)
@@ -28,8 +28,8 @@ require_once(dirname(dirname(__FILE__)) . '/config.php');
 require_once($CFG->libdir . '/adminlib.php');
 require_once($CFG->libdir . '/pluginlib.php');
 
-require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
 admin_externalpage_setup('pluginsoverview');
+require_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM));
 
 $fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
 
index 0d97168..911d26a 100644 (file)
@@ -285,7 +285,11 @@ if ($formdata = $mform2->is_cancelled()) {
             $userserrors++;
             continue;
         }
-
+        if ($user->username !== clean_param($user->username, PARAM_USERNAME)) {
+            $upt->track('status', get_string('invalidusername', 'error', 'username'), 'error');
+            $upt->track('username', $errorstr, 'error');
+            $userserrors++;
+        }
         if ($existinguser = $DB->get_record('user', array('username'=>$user->username, 'mnethostid'=>$CFG->mnet_localhost_id))) {
             $upt->track('id', $existinguser->id, 'normal', false);
         }
index 1e93cf2..29943d6 100644 (file)
@@ -40,8 +40,7 @@ if ($show_instructions) {
             </form>
             <p>
             <?php
-                print_string("auth_shibboleth_contact_administrator", "auth_shibboleth");
-                echo '<a href="mailto:'.get_admin()->email.'"> Moodle Administrator</a>.';
+                print_string("auth_shib_contact_administrator", "auth_shibboleth", get_admin()->email);
             ?>
             </p>
           </div>
index aace9a1..a043403 100644 (file)
@@ -25,6 +25,7 @@
 
 $string['auth_shib_auth_method'] = 'Authentication method name';
 $string['auth_shib_auth_method_description'] = 'Provide a name for the Shibboleth authentication method that is familiar to your users. This could be the name of your Shibboleth federation, e.g. <tt>SWITCHaai Login</tt> or <tt>InCommon Login</tt> or similar.';
+$string['auth_shib_contact_administrator'] = 'In case you are not associated with the given organizations and you need access to a course on this server, please contact the <a href="mailto:{$a}">Moodle Administrator</a>.';
 $string['auth_shibboleth_contact_administrator'] = 'In case you are not associated with the given organizations and you need access to a course on this server, please contact the';
 $string['auth_shibbolethdescription'] = 'Using this method users are created and authenticated using <a href="http://shibboleth.internet2.edu/">Shibboleth</a>.<br />Be sure to read the <a href="../auth/shibboleth/README.txt">README</a> for Shibboleth on how to set up your Moodle with Shibboleth';
 $string['auth_shibboleth_errormsg'] = 'Please select the organization you are member of!';
index 0059bbc..67e72a0 100644 (file)
@@ -33,7 +33,7 @@ $currentcontext = required_param('currentcontext', PARAM_INT);
 // file parameters
 $component  = optional_param('component', null, PARAM_COMPONENT);
 $filearea   = optional_param('filearea', null, PARAM_AREA);
-$returnurl  = optional_param('returnurl', null, PARAM_URL);
+$returnurl  = optional_param('returnurl', null, PARAM_LOCALURL);
 
 list($context, $course, $cm) = get_context_info_array($currentcontext);
 $filecontext = get_context_instance_by_id($contextid);
index 9edda48..3b3fd4a 100644 (file)
@@ -641,7 +641,9 @@ class moodle1_converter extends base_converter {
         }
         foreach ($matches[2] as $match) {
             $file = str_replace(array('$@FILEPHP@$', '$@SLASH@$', '$@FORCEDOWNLOAD@$'), array('', '/', ''), $match);
-            $files[] = rawurldecode($file);
+            if ($file === clean_param($file, PARAM_PATH)) {
+                $files[] = rawurldecode($file);
+            }
         }
 
         return array_unique($files);
@@ -1209,6 +1211,10 @@ class moodle1_file_manager implements loggable {
 
         $sourcefullpath = $this->basepath.'/'.$sourcepath;
 
+        if ($sourcefullpath !== clean_param($sourcefullpath, PARAM_PATH)) {
+            throw new moodle1_convert_exception('file_invalid_path', $sourcefullpath);
+        }
+
         if (!is_readable($sourcefullpath)) {
             throw new moodle1_convert_exception('file_not_readable', $sourcefullpath);
         }
index bf4935c..154b6c8 100644 (file)
@@ -273,6 +273,15 @@ class moodle1_converter_testcase extends advanced_testcase {
         $fileids = $fileman->get_fileids();
         $this->assertEquals(gettype($fileids), 'array');
         $this->assertEquals(0, count($fileids));
+        // try to migrate an invalid file
+        $fileman->itemid = 1;
+        $thrown = false;
+        try {
+            $fileman->migrate_file('/../../../../../../../../../../../../../../etc/passwd');
+        } catch (moodle1_convert_exception $e) {
+            $thrown = true;
+        }
+        $this->assertTrue($thrown);
         // migrate a single file
         $fileman->itemid = 4;
         $fileman->migrate_file('moddata/unittest/4/icon.gif');
@@ -435,6 +444,8 @@ class moodle1_converter_testcase extends advanced_testcase {
 
         $text = 'This is a text containing links to file.php
 as it is parsed from the backup file. <br /><br /><img border="0" width="110" vspace="0" hspace="0" height="92" title="News" alt="News" src="$@FILEPHP@$$@SLASH@$pics$@SLASH@$news.gif" /><a href="$@FILEPHP@$$@SLASH@$pics$@SLASH@$news.gif$@FORCEDOWNLOAD@$">download image</a><br />
+    <div><a href=\'$@FILEPHP@$/../../../../../../../../../../../../../../../etc/passwd\'>download passwords</a></div>
+    <div><a href=\'$@FILEPHP@$$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$etc$@SLASH@$shadow\'>download shadows</a></div>
     <br /><a href=\'$@FILEPHP@$$@SLASH@$MANUAL.DOC$@FORCEDOWNLOAD@$\'>download manual</a><br />';
 
         $files = moodle1_converter::find_referenced_files($text);
@@ -446,6 +457,8 @@ as it is parsed from the backup file. <br /><br /><img border="0" width="110" vs
         $text = moodle1_converter::rewrite_filephp_usage($text, array('/pics/news.gif', '/another/file/notused.txt'));
         $this->assertEquals($text, 'This is a text containing links to file.php
 as it is parsed from the backup file. <br /><br /><img border="0" width="110" vspace="0" hspace="0" height="92" title="News" alt="News" src="@@PLUGINFILE@@/pics/news.gif" /><a href="@@PLUGINFILE@@/pics/news.gif?forcedownload=1">download image</a><br />
+    <div><a href=\'$@FILEPHP@$/../../../../../../../../../../../../../../../etc/passwd\'>download passwords</a></div>
+    <div><a href=\'$@FILEPHP@$$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$..$@SLASH@$etc$@SLASH@$shadow\'>download shadows</a></div>
     <br /><a href=\'$@FILEPHP@$$@SLASH@$MANUAL.DOC$@FORCEDOWNLOAD@$\'>download manual</a><br />');
     }
 
index 3247a82..d2b1c3a 100644 (file)
@@ -297,8 +297,13 @@ class restore_gradebook_structure_step extends restore_structure_step {
 
         $data->courseid = $this->get_courseid();
 
-        $newitemid = $DB->insert_record('grade_settings', $data);
-        //$this->set_mapping('grade_setting', $oldid, $newitemid);
+        if (!$DB->record_exists('grade_settings', array('courseid' => $data->courseid, 'name' => $data->name))) {
+            $newitemid = $DB->insert_record('grade_settings', $data);
+        } else {
+            $newitemid = $data->id;
+        }
+
+        $this->set_mapping('grade_setting', $oldid, $newitemid);
     }
 
     /**
@@ -838,20 +843,12 @@ class restore_groups_structure_step extends restore_structure_step {
     }
 
     public function process_grouping_group($data) {
-        global $DB;
-
-        $data = (object)$data;
-
-        $data->groupingid = $this->get_new_parentid('grouping'); // Use new parentid
-        $data->groupid    = $this->get_mappingid('group', $data->groupid); // Get from mappings
+        global $CFG;
 
-        $params = array();
-        $params['groupingid'] = $data->groupingid;
-        $params['groupid']    = $data->groupid;
+        require_once($CFG->dirroot.'/group/lib.php');
 
-        if (!$DB->record_exists('groupings_groups', $params)) {
-            $DB->insert_record('groupings_groups', $data);  // No need to set this mapping (no child info nor files)
-        }
+        $data = (object)$data;
+        groups_assign_grouping($this->get_new_parentid('grouping'), $this->get_mappingid('group', $data->groupid), $data->timeadded);
     }
 
     protected function after_execute() {
index 8e28ac2..c23d7b9 100644 (file)
@@ -78,6 +78,9 @@ class restore_rss_client_block_structure_step extends restore_structure_step {
         $configdata = $DB->get_field('block_instances', 'configdata', array('id' => $this->task->get_blockid()));
         // Extract configdata
         $config = unserialize(base64_decode($configdata));
+        if (empty($config)) {
+            $config = new stdClass();
+        }
         // Set array of used rss feeds
         $config->rssid = $feedsarr;
         // Serialize back the configdata
index eec3202..c1b4d5d 100644 (file)
@@ -34,7 +34,7 @@ class block_tags_edit_form extends block_edit_form {
         // Fields for editing HTML block title and contents.
         $mform->addElement('header', 'configheader', get_string('blocksettings', 'block'));
 
-        $mform->addElement('text', 'config_title', get_string('pluginname', 'block_tags'));
+        $mform->addElement('text', 'config_title', get_string('configtitle', 'block_tags'));
         $mform->setType('config_title', PARAM_MULTILANG);
         $mform->setDefault('config_title', get_string('pluginname', 'block_tags'));
 
index 265cd5a..4998f68 100644 (file)
@@ -31,6 +31,7 @@ $string['arrowtitle'] = 'Click here to enter the suggested text (grey letters).'
 $string['communitytags'] = 'Community tags:';
 $string['communitytags1'] = 'community tags';
 $string['communitytags2'] = 'Show all user created course tags';
+$string['configtitle'] = 'Block title';
 $string['coursetags'] = 'Course tags:';
 $string['coursetags1'] = 'course tags';
 $string['coursetags2'] = 'Show tags for this course';
index 770fb06..a3904c8 100644 (file)
@@ -158,11 +158,23 @@ function blog_rss_get_params($filters) {
 function blog_rss_get_feed($context, $args) {
     global $CFG, $SITE, $DB;
 
+    if (empty($CFG->bloglevel)) {
+        debugging('Blogging disabled on this site, RSS feeds are not available');
+        return null;
+    }
+
     if (empty($CFG->enablerssfeeds)) {
         debugging('Sorry, RSS feeds are disabled on this site');
         return '';
     }
 
+    if ($CFG->bloglevel == BLOG_SITE_LEVEL) {
+        if (isguestuser()) {
+            debugging(get_string('nopermissiontoshow','error'));
+            return '';
+        }
+    }
+
     $sitecontext = get_context_instance(CONTEXT_SYSTEM);
     if (!has_capability('moodle/blog:view', $sitecontext)) {
         return null;
index 396e91d..f3404c6 100644 (file)
@@ -460,7 +460,7 @@ class core_calendar_renderer extends plugin_renderer_base {
         // Paddding (the first week may have blank days in the beginning)
         for($i = $display->minwday; $i < $startwday; ++$i) {
             $cell = new html_table_cell('&nbsp;');
-            $cell->attributes = array('class'=>'nottoday');
+            $cell->attributes = array('class'=>'nottoday dayblank');
             $row->cells[] = $cell;
         }
 
@@ -519,10 +519,10 @@ class core_calendar_renderer extends plugin_renderer_base {
             }
 
             // Special visual fx for today
-            if($display->thismonth && $calendar->day == $calendar->day) {
-                $cellclasses[] = 'today';
+            if ($display->thismonth && $calendar->day == $date['mday']) {
+                $cellclasses[] = 'day today';
             } else {
-                $cellclasses[] = 'nottoday';
+                $cellclasses[] = 'day nottoday';
             }
             $cell->attributes = array('class'=>join(' ',$cellclasses));
 
@@ -553,7 +553,7 @@ class core_calendar_renderer extends plugin_renderer_base {
         // Paddding (the last week may have blank days at the end)
         for($i = $dayweek; $i <= $display->maxwday; ++$i) {
             $cell = new html_table_cell('&nbsp;');
-            $cell->attributes = array('class'=>'nottoday');
+            $cell->attributes = array('class'=>'nottoday dayblank');
             $row->cells[] = $cell;
         }
         $table->data[] = $row;
index b7a39a8..a960293 100644 (file)
@@ -38,7 +38,7 @@ $action    = optional_param('action',    '',  PARAM_ALPHA);
 $area      = optional_param('area',      '',  PARAM_AREA);
 $content   = optional_param('content',   '',  PARAM_RAW);
 $itemid    = optional_param('itemid',    '',  PARAM_INT);
-$returnurl = optional_param('returnurl', '/', PARAM_URL);
+$returnurl = optional_param('returnurl', '/', PARAM_LOCALURL);
 $component = optional_param('component', '',  PARAM_COMPONENT);
 
 // Currently this script can only add comments
index 2dcaa13..fda43af 100644 (file)
@@ -728,6 +728,9 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+                            if (!parseInt(result.visible, 10)) {
+                                resel.a.className = 'dimmed';
+                            }
 
                             if (result.groupingname) {
                                 resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
@@ -915,6 +918,9 @@ M.course_dndupload = {
                             resel.icon.src = result.icon;
                             resel.a.href = result.link;
                             resel.namespan.innerHTML = result.name;
+                            if (!parseInt(result.visible, 10)) {
+                                resel.a.className = 'dimmed';
+                            }
 
                             if (result.groupingname) {
                                 resel.groupingspan.innerHTML = '(' + result.groupingname + ')';
index 038c09b..10cedba 100644 (file)
@@ -555,6 +555,15 @@ class dndupload_ajax_processor {
         $this->cm->groupmode = $this->course->groupmode;
         $this->cm->groupingid = $this->course->defaultgroupingid;
 
+        // Set the correct default for completion tracking.
+        $this->cm->completion = COMPLETION_TRACKING_NONE;
+        $completion = new completion_info($this->course);
+        if ($completion->is_enabled()) {
+            if (plugin_supports('mod', $this->cm->modulename, FEATURE_MODEDIT_DEFAULT_COMPLETION, true)) {
+                $this->cm->completion = COMPLETION_TRACKING_MANUAL;
+            }
+        }
+
         if (!$this->cm->id = add_course_module($this->cm)) {
             throw new coding_exception("Unable to create the course module");
         }
@@ -611,12 +620,18 @@ class dndupload_ajax_processor {
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
 
+        // Note the section visibility
+        $visible = get_fast_modinfo($this->course)->get_section_info($this->section)->visible;
+
         $DB->set_field('course_modules', 'instance', $instanceid, array('id' => $this->cm->id));
 
         $sectionid = add_mod_to_section($this->cm);
         $DB->set_field('course_modules', 'section', $sectionid, array('id' => $this->cm->id));
 
-        set_coursemodule_visible($this->cm->id, true);
+        set_coursemodule_visible($this->cm->id, $visible);
+        if (!$visible) {
+            $DB->set_field('course_modules', 'visibleold', 1, array('id' => $this->cm->id));
+        }
 
         // Rebuild the course cache and retrieve the final info about this module.
         rebuild_course_cache($this->course->id, true);
@@ -627,7 +642,7 @@ class dndupload_ajax_processor {
             delete_course_module($this->cm->id);
             throw new moodle_exception('errorcreatingactivity', 'moodle', '', $this->module->name);
         }
-        $mod = $info->cms[$this->cm->id];
+        $mod = $info->get_cm($this->cm->id);
         $mod->groupmodelink = $this->cm->groupmodelink;
         $mod->groupmode = $this->cm->groupmode;
 
@@ -666,6 +681,7 @@ class dndupload_ajax_processor {
         $resp->elementid = 'module-'.$mod->id;
         $resp->commands = make_editing_buttons($mod, true, true, 0, $mod->sectionnum);
         $resp->onclick = $mod->get_on_click();
+        $resp->visible = $mod->visible;
 
         // if using groupings, then display grouping name
         if (!empty($mod->groupingid) && has_capability('moodle/course:managegroups', $this->context)) {
index bcb237a..7d22c94 100644 (file)
@@ -257,7 +257,7 @@ class course_edit_form extends moodleform {
                 array(0=>get_string('completiondisabled','completion'), 1=>get_string('completionenabled','completion')));
             $mform->setDefault('enablecompletion', $courseconfig->enablecompletion);
 
-            $mform->addElement('checkbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
+            $mform->addElement('advcheckbox', 'completionstartonenrol', get_string('completionstartonenrol', 'completion'));
             $mform->setDefault('completionstartonenrol', $courseconfig->completionstartonenrol);
             $mform->disabledIf('completionstartonenrol', 'enablecompletion', 'eq', 0);
         } else {
index c307a6d..2ec254c 100644 (file)
@@ -57,7 +57,7 @@ if (!empty($CFG->enableavailability)) {
 }
 
 $mform = new editsection_form($PAGE->url, array('course' => $course, 'editoroptions' => $editoroptions,
-        'cs' => $section, 'showavailability' => $section->showavailability));
+        'cs' => $section));
 $mform->set_data($section); // set current value
 
 $returnurl = course_get_url($course, $sectionreturn);
index 73723af..870f76c 100644 (file)
@@ -54,11 +54,19 @@ class editsection_form extends moodleform {
                 $mform->addHelpButton('groupingid', 'groupingsection', 'group');
             }
 
-            // Date and time conditions
+            // Available from/to defaults to midnight because then the display
+            // will be nicer where it tells users when they can access it (it
+            // shows only the date and not time).
+            $date = usergetdate(time());
+            $midnight = make_timestamp($date['year'], $date['mon'], $date['mday']);
+
+            // Date and time conditions.
             $mform->addElement('date_time_selector', 'availablefrom',
-                    get_string('availablefrom', 'condition'), array('optional' => true));
+                    get_string('availablefrom', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
             $mform->addElement('date_time_selector', 'availableuntil',
-                    get_string('availableuntil', 'condition'), array('optional' => true));
+                    get_string('availableuntil', 'condition'),
+                    array('optional' => true, 'defaulttime' => $midnight));
 
             // Conditions based on grades
             $gradeoptions = array();
@@ -162,8 +170,6 @@ class editsection_form extends moodleform {
                 CONDITION_STUDENTVIEW_HIDE => get_string('showavailabilitysection_hide', 'condition'));
             $mform->addElement('select', 'showavailability',
                     get_string('showavailabilitysection', 'condition'), $showhide);
-
-            $mform->setDefault('showavailability', $this->_customdata['showavailability']);
         }
 
         $this->add_action_buttons();
index d832388..3fbb59b 100644 (file)
@@ -175,7 +175,8 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         }
         $o.= html_writer::end_tag('div');
 
-        $o .= $this->section_availability_message($section);
+        $o .= $this->section_availability_message($section,
+                has_capability('moodle/course:viewhiddensections', $context));
 
         return $o;
     }
@@ -305,7 +306,9 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
         $o.= html_writer::end_tag('div');
         $o.= $this->section_activity_summary($section, $course, $mods);
 
-        $o.= $this->section_availability_message($section);
+        $context = context_course::instance($course->id);
+        $o .= $this->section_availability_message($section,
+                has_capability('moodle/course:viewhiddensections', $context));
 
         $o .= html_writer::end_tag('div');
         $o .= html_writer::end_tag('li');
@@ -388,22 +391,38 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
     }
 
     /**
-     * If section is not visible to current user, display the message about that
-     * ('Not available until...', that sort of thing). Otherwise, returns blank.
+     * If section is not visible, display the message about that ('Not available
+     * until...', that sort of thing). Otherwise, returns blank.
+     *
+     * For users with the ability to view hidden sections, it shows the
+     * information even though you can view the section and also may include
+     * slightly fuller information (so that teachers can tell when sections
+     * are going to be unavailable etc). This logic is the same as for
+     * activities.
      *
      * @param stdClass $section The course_section entry from DB
+     * @param bool $canviewhidden True if user can view hidden sections
      * @return string HTML to output
      */
-    protected function section_availability_message($section) {
+    protected function section_availability_message($section, $canviewhidden) {
+        global $CFG;
         $o = '';
-        if (!$section->uservisible || $section->availableinfo) {
+        if (!$section->uservisible) {
             $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
-            if (!empty($section->availableinfo)) {
-                $o .= $section->availableinfo;
-            } else {
-                $o .= get_string('notavailable');
-            }
+            // Note: We only get to this function if availableinfo is non-empty,
+            // so there is definitely something to print.
+            $o .= $section->availableinfo;
             $o .= html_writer::end_tag('div');
+        } else if ($canviewhidden && !empty($CFG->enableavailability) && $section->visible) {
+            $ci = new condition_info_section($section);
+            $fullinfo = $ci->get_full_information();
+            if ($fullinfo) {
+                $o .= html_writer::start_tag('div', array('class' => 'availabilityinfo'));
+                $o .= get_string(
+                        ($section->showavailability ? 'userrestriction_visible' : 'userrestriction_hidden'),
+                        'condition', $fullinfo);
+                $o .= html_writer::end_tag('div');
+            }
         }
         return $o;
     }
@@ -677,9 +696,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 $thissection->showavailability = 0;
             }
             // Show the section if the user is permitted to access it, OR if it's not available
-            // but showavailability is turned on
+            // but showavailability is turned on (and there is some available info text).
             $showsection = $thissection->uservisible ||
-                    ($thissection->visible && !$thissection->available && $thissection->showavailability);
+                    ($thissection->visible && !$thissection->available && $thissection->showavailability
+                    && !empty($thissection->availableinfo));
             if (!$showsection) {
                 // Hidden section message is overridden by 'unavailable' control
                 // (showavailability option).
@@ -716,7 +736,7 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                     continue;
                 }
                 echo $this->stealth_section_header($section);
-                print_section($course, $thissection, $mods, $modnamesused, true, "100%", false, $displaysection);
+                print_section($course, $thissection, $mods, $modnamesused, true, "100%", false, 0);
                 echo $this->stealth_section_footer();
             }
 
index 12cba20..dc387fb 100644 (file)
@@ -35,7 +35,7 @@ require_once($CFG->dirroot.'/course/lib.php');
 
 $id         = required_param('id', PARAM_INT);
 $switchrole = optional_param('switchrole',-1, PARAM_INT);
-$returnurl  = optional_param('returnurl', false, PARAM_URL);
+$returnurl  = optional_param('returnurl', false, PARAM_LOCALURL);
 
 $PAGE->set_url('/course/switchrole.php', array('id'=>$id));
 
@@ -86,4 +86,4 @@ if ($returnurl === false) {
     $returnurl = new moodle_url('/course/view.php', array('id' => $course->id));
 }
 
-redirect($returnurl);
\ No newline at end of file
+redirect($returnurl);
index 28b9081..f7ff2c6 100644 (file)
@@ -152,6 +152,9 @@ YUI.add('moodle-course-modchooser', function(Y) {
     {
         NAME : MODCHOOSERNAME,
         ATTRS : {
+            maxheight : {
+                value : 800
+            }
         }
     });
     M.course = M.course || {};
index 7f30c07..7c1393a 100644 (file)
@@ -67,7 +67,11 @@ class core_adodb_testcase extends advanced_testcase {
 
             case 'pgsql_native_moodle_database':
                 set_config('dbtype', 'postgres7', 'enrol_database');
-                set_config('dbsetupsql', 'SET NAMES \'UTF-8\'', 'enrol_database');
+                $setupsql = "SET NAMES 'UTF-8'";
+                if (!empty($CFG->dboptions['dbschema'])) {
+                    $setupsql .= "; SET search_path = '".$CFG->dboptions['dbschema']."'";
+                }
+                set_config('dbsetupsql', $setupsql, 'enrol_database');
                 set_config('dbsybasequoting', '0', 'enrol_database');
                 if (!empty($CFG->dboptions['dbsocket']) and ($CFG->dbhost === 'localhost' or $CFG->dbhost === '127.0.0.1')) {
                     if (strpos($CFG->dboptions['dbsocket'], '/') !== false) {
index 52b34bf..2665b2f 100644 (file)
@@ -47,6 +47,7 @@ $PAGE->set_pagelayout('admin');
 $manager = new course_enrolment_manager($PAGE, $course, $filter);
 $table = new course_enrolment_other_users_table($manager, $PAGE);
 $PAGE->set_url('/enrol/otherusers.php', $manager->get_url_params()+$table->get_url_params());
+navigation_node::override_active_url(new moodle_url('/enrol/otherusers.php', array('id' => $id)));
 
 $userdetails = array (
     'picture' => false,
index 3c5d013..4c6dc8b 100644 (file)
@@ -188,7 +188,7 @@ class core_files_renderer extends plugin_renderer_base {
         $strdroptoupload = get_string('droptoupload', 'moodle');
         $icon_progress = $OUTPUT->pix_icon('i/loading_small', $strloading).'';
         $restrictions = $this->fm_print_restrictions($fm);
-        $strdndenabled = get_string('dndenabled_insentence', 'moodle').$OUTPUT->help_icon('dndenabled');
+        $strdndnotsupported = get_string('dndnotsupported_insentence', 'moodle').$OUTPUT->help_icon('dndnotsupported');
         $strdndenabledinbox = get_string('dndenabled_inbox', 'moodle');
         $loading = get_string('loading', 'repository');
 
@@ -196,7 +196,7 @@ class core_files_renderer extends plugin_renderer_base {
 <div id="filemanager-'.$client_id.'" class="filemanager fm-loading">
     <div class="fp-restrictions">
         '.$restrictions.'
-        <span class="dndupload-message"> - '.$strdndenabled.' </span>
+        <span class="dnduploadnotsupported-message"> - '.$strdndnotsupported.' </span>
     </div>
     <div class="fp-navbar">
         <div class="filemanager-toolbar">
index 2bc2dc7..df0638c 100644 (file)
@@ -273,12 +273,6 @@ class grade_edit_tree {
                 $root = true;
             }
 
-            $row_count_offset = 0;
-
-            if (empty($category_total_item) && !$this->moving) {
-                $row_count_offset = -1;
-            }
-
             $levelclass = "level$level";
 
             $courseclass = '';
@@ -297,7 +291,7 @@ class grade_edit_tree {
             $headercell->scope = 'row';
             $headercell->attributes['title'] = $object->stripped_name;
             $headercell->attributes['class'] = 'cell rowspan ' . $levelclass;
-            $headercell->rowspan = $row_count+1+$row_count_offset;
+            $headercell->rowspan = $row_count + 1;
             $row->cells[] = $headercell;
 
             foreach ($this->columns as $column) {
index 39db087..c12d5a5 100644 (file)
@@ -576,7 +576,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, 'grouping description text'),
-                            'descriptionformat' => new external_format_value('descripiton', VALUE_DEFAULT)
+                            'descriptionformat' => new external_format_value('description', VALUE_DEFAULT)
                         )
                     ), 'List of grouping object. A grouping has a courseid, a name and a description.'
                 )
index 024e81b..c9e2cc0 100644 (file)
@@ -634,9 +634,10 @@ function groups_parse_name($format, $groupnumber) {
  *
  * @param int groupingid
  * @param int groupid
+ * @param int $timeadded  The time the group was added to the grouping.
  * @return bool true or exception
  */
-function groups_assign_grouping($groupingid, $groupid) {
+function groups_assign_grouping($groupingid, $groupid, $timeadded = null) {
     global $DB;
 
     if ($DB->record_exists('groupings_groups', array('groupingid'=>$groupingid, 'groupid'=>$groupid))) {
@@ -645,7 +646,11 @@ function groups_assign_grouping($groupingid, $groupid) {
     $assign = new stdClass();
     $assign->groupingid = $groupingid;
     $assign->groupid    = $groupid;
-    $assign->timeadded  = time();
+    if ($timeadded != null) {
+        $assign->timeadded = (integer)$timeadded;
+    } else {
+        $assign->timeadded = time();
+    }
     $DB->insert_record('groupings_groups', $assign);
 
     return true;
index 347e437..e90362c 100644 (file)
@@ -45,7 +45,7 @@ $string['datarootpermission'] = 'Toestemming datamappen';
 $string['dbprefix'] = 'Tabelvoorvoegsel';
 $string['dirroot'] = 'Moodle-map';
 $string['environmenthead'] = 'Omgeving controleren ...';
-$string['environmentsub2'] = 'Elke Moodleversie vraagt een minimum PHP-versie en een aantal vereiste PHP-extenties.
+$string['environmentsub2'] = 'Elke Moodleversie vraagt een minimale PHP-versie en een aantal vereiste PHP-extenties.
 De volledige installatie-omgeving wordt gecontroleerd voor elke installatie en upgrade. Contacteer je server beheerder als je niet weet hoe je de juiste PHP-versie moet installeren of PHP-extenties moet inschakelen.';
 $string['errorsinenvironment'] = 'Fouten in je omgeving!';
 $string['installation'] = 'Installatie';
index 9640c74..06fe926 100644 (file)
@@ -329,6 +329,7 @@ $string['invalidurl'] = 'Invalid URL';
 $string['invaliduser'] = 'Invalid user';
 $string['invaliduserid'] = 'Invalid user id';
 $string['invaliduserfield'] = 'Invalid user field: {$a}';
+$string['invalidusername'] = 'The given username contains invalid characters';
 $string['invalidxmlfile'] = '"{$a}" is not a valid XML file';
 $string['iplookupfailed'] = 'Cannot find geo information about this IP address {$a}';
 $string['iplookupprivate'] = 'Cannot display lookup of private IP address';
index c594038..b556ece 100644 (file)
@@ -467,6 +467,9 @@ $string['dndenabled'] = 'Drag and drop available';
 $string['dndenabled_help'] = 'You can drag one or more files from your desktop and drop them onto the box below to upload them.<br />Note: this may not work with other web browsers';
 $string['dndenabled_insentence'] = 'drag and drop available';
 $string['dndenabled_inbox'] = 'You can drag and drop files here to add them.';
+$string['dndnotsupported'] = 'Drag and drop upload not supported';
+$string['dndnotsupported_help'] = 'Your browser does not support drag and drop upload.<br />This feature is available in all recent versions of Chrome, Firefox and Safari, as well as Internet Explorer v10 and above.';
+$string['dndnotsupported_insentence'] = 'drag and drop not supported';
 $string['dnduploadwithoutcontent'] = 'This upload does not have any content';
 $string['dndworkingfiletextlink'] = 'Drag and drop files, text or links onto course sections to upload them';
 $string['dndworkingfilelink'] = 'Drag and drop files or links onto course sections to upload them';
index dfa1aeb..56d1a07 100644 (file)
@@ -306,6 +306,8 @@ $string['cannotloadquestion'] = 'Could not load question';
 $string['cannotpreview'] = 'You can\'t preview these questions!';
 $string['category'] = 'Category';
 $string['changeoptions'] = 'Change options';
+$string['attemptoptions'] = 'Attempt options';
+$string['displayoptions'] = 'Display options';
 $string['check'] = 'Check';
 $string['clearwrongparts'] = 'Clear incorrect responses';
 $string['closepreview'] = 'Close preview';
@@ -384,6 +386,7 @@ $string['requiresgrading'] = 'Requires grading';
 $string['responsehistory'] = 'Response history';
 $string['restart'] = 'Start again';
 $string['restartwiththeseoptions'] = 'Start again with these options';
+$string['updatedisplayoptions'] = 'Update display options';
 $string['rightanswer'] = 'Right answer';
 $string['rightanswer_help'] = 'an automatically generated summary of the correct response. This can be limited, so you may wish to consider explaining the correct solution in the general feedback for the question, and turning this option off.';
 $string['saved'] = 'Saved: {$a}';
index 8682378..5a4a965 100644 (file)
@@ -100,6 +100,7 @@ $string['error'] = 'An unknown error occurred!';
 $string['errornotyourfile'] = 'You cannot pick file which is not added by your';
 $string['erroruniquename'] = 'Repository instance name should be unique';
 $string['errorpostmaxsize'] = 'The uploaded file may exceed max_post_size directive in php.ini.';
+$string['errorwhilecommunicatingwith'] = 'Error while communicating with the repository \'{$a}\'.';
 $string['errorwhiledownload'] = 'An error occured while downloading the file: {$a}';
 $string['existingrepository'] = 'This repository already exists';
 $string['federatedsearch'] = 'Federated search';
index 2812fef..d638e50 100644 (file)
@@ -123,6 +123,9 @@ define('INSECURE_DATAROOT_ERROR', 2);
 function uninstall_plugin($type, $name) {
     global $CFG, $DB, $OUTPUT;
 
+    // This may take a long time.
+    @set_time_limit(0);
+
     // recursively uninstall all module subplugins first
     if ($type === 'mod') {
         if (file_exists("$CFG->dirroot/mod/$name/db/subplugins.php")) {
index 8d41086..1427141 100644 (file)
@@ -414,9 +414,11 @@ class postgres_sql_generator extends sql_generator {
         $tablename = $this->getTableName($xmldb_table);
         $sequencename = $tablename . '_id_seq';
 
-        if (!$this->mdb->get_record_sql("SELECT *
-                                           FROM pg_class
-                                          WHERE relname = ? AND relkind = 'S'",
+        if (!$this->mdb->get_record_sql("SELECT c.*
+                                           FROM pg_catalog.pg_class c
+                                           JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
+                                          WHERE c.relname = ? AND c.relkind = 'S'
+                                                AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())",
                                         array($sequencename))) {
             $sequencename = false;
         }
@@ -442,9 +444,11 @@ class postgres_sql_generator extends sql_generator {
             case 'ix':
             case 'uix':
             case 'seq':
-                if ($check = $this->mdb->get_records_sql("SELECT relname
-                                                            FROM pg_class
-                                                           WHERE lower(relname) = ?", array(strtolower($object_name)))) {
+                if ($check = $this->mdb->get_records_sql("SELECT c.relname
+                                                                FROM pg_class c
+                                                                JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
+                                                               WHERE lower(c.relname) = ?
+                                                                     AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())", array(strtolower($object_name)))) {
                     return true;
                 }
                 break;
@@ -452,9 +456,11 @@ class postgres_sql_generator extends sql_generator {
             case 'uk':
             case 'fk':
             case 'ck':
-                if ($check = $this->mdb->get_records_sql("SELECT conname
-                                                            FROM pg_constraint
-                                                           WHERE lower(conname) = ?", array(strtolower($object_name)))) {
+                if ($check = $this->mdb->get_records_sql("SELECT c.conname
+                                                                FROM pg_constraint c
+                                                                JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.connamespace
+                                                               WHERE lower(c.conname) = ?
+                                                                     AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())", array(strtolower($object_name)))) {
                     return true;
                 }
                 break;
index 0c9b18e..f47ebd7 100644 (file)
@@ -181,6 +181,11 @@ class pgsql_native_moodle_database extends moodle_database {
             $sql = "SET bytea_output = 'escape'; ";
         }
 
+        // Select schema if specified, otherwise the first one wins.
+        if (!empty($this->dboptions['dbschema'])) {
+            $sql .= "SET search_path = '".$this->dboptions['dbschema']."'; ";
+        }
+
         // Find out the bytea oid.
         $sql .= "SELECT oid FROM pg_type WHERE typname = 'bytea'";
         $this->query_start($sql, null, SQL_QUERY_AUX);
@@ -292,21 +297,12 @@ class pgsql_native_moodle_database extends moodle_database {
         }
         $this->tables = array();
         $prefix = str_replace('_', '|_', $this->prefix);
-        if ($this->is_min_version('9.1')) {
-            // Use ANSI standard information_schema in recent versions where it is fast enough.
-            $sql = "SELECT table_name
-                      FROM information_schema.tables
-                     WHERE table_name LIKE '$prefix%' ESCAPE '|'
-                       AND table_type IN ('BASE TABLE', 'LOCAL TEMPORARY')";
-        } else {
-            // information_schema is horribly slow in <= 9.0, so use pg internals.
-            // Note the pg_is_other_temp_schema. We only want temp objects from our own session.
-            $sql = "SELECT c.relname
-                      FROM pg_class c
-                     WHERE c.relname LIKE '$prefix%' ESCAPE '|'
+        $sql = "SELECT c.relname
+                  FROM pg_catalog.pg_class c
+                  JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
+                 WHERE c.relname LIKE '$prefix%' ESCAPE '|'
                        AND c.relkind = 'r'
-                       AND NOT pg_is_other_temp_schema(c.relnamespace)";
-        }
+                       AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())";
         $this->query_start($sql, null, SQL_QUERY_AUX);
         $result = pg_query($this->pgsql, $sql);
         $this->query_end($result);
@@ -336,9 +332,11 @@ class pgsql_native_moodle_database extends moodle_database {
         $indexes = array();
         $tablename = $this->prefix.$table;
 
-        $sql = "SELECT *
-                  FROM pg_catalog.pg_indexes
-                 WHERE tablename = '$tablename'";
+        $sql = "SELECT i.*
+                  FROM pg_catalog.pg_indexes i
+                  JOIN pg_catalog.pg_namespace as ns ON ns.nspname = i.schemaname
+                 WHERE i.tablename = '$tablename'
+                       AND (i.schemaname = current_schema() OR ns.oid = pg_my_temp_schema())";
 
         $this->query_start($sql, null, SQL_QUERY_AUX);
         $result = pg_query($this->pgsql, $sql);
@@ -379,10 +377,12 @@ class pgsql_native_moodle_database extends moodle_database {
 
         $sql = "SELECT a.attnum, a.attname AS field, t.typname AS type, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, d.adsrc
                   FROM pg_catalog.pg_class c
+                  JOIN pg_catalog.pg_namespace as ns ON ns.oid = c.relnamespace
                   JOIN pg_catalog.pg_attribute a ON a.attrelid = c.oid
                   JOIN pg_catalog.pg_type t ON t.oid = a.atttypid
              LEFT JOIN pg_catalog.pg_attrdef d ON (d.adrelid = c.oid AND d.adnum = a.attnum)
                  WHERE relkind = 'r' AND c.relname = '$tablename' AND c.reltype > 0 AND a.attnum > 0
+                       AND (ns.nspname = current_schema() OR ns.oid = pg_my_temp_schema())
               ORDER BY a.attnum";
 
         $this->query_start($sql, null, SQL_QUERY_AUX);
index f96d4a9..5edf76a 100644 (file)
@@ -128,6 +128,7 @@ class GoogleSpell extends SpellChecker {
        }\r
 \r
        function _unhtmlentities($string) {\r
+        return textlib::entities_to_utf8($string); // Moodle hack\r
                $string = preg_replace('~&#x([0-9a-f]+);~ei', 'chr(hexdec("\\1"))', $string);\r
                $string = preg_replace('~&#([0-9]+);~e', 'chr(\\1)', $string);\r
 \r
index 5e61f56..dae2d8b 100644 (file)
@@ -813,7 +813,7 @@ class file_info_area_backup_section extends file_info {
             'component' => 'backup',
             'filearea' => 'section',
             'emptyfilename' => '.');
-        $sql1 = "SELECT DISTINCT cs.id sectionid FROM {files} f, {course_sections} cs
+        $sql1 = "SELECT DISTINCT cs.id AS sectionid FROM {files} f, {course_sections} cs
             WHERE cs.course = :courseid
             AND f.contextid = :contextid
             AND f.component = :component
index e8d542c..d3e55fb 100644 (file)
@@ -204,7 +204,7 @@ class file_info_context_coursecat extends file_info {
             return $cnt;
         }
 
-        $rs = $DB->get_recordset_sql('SELECT ctx.id contextid, c.visible
+        $rs = $DB->get_recordset_sql('SELECT ctx.id AS contextid, c.visible
                 FROM {context} ctx, {course} c
                 WHERE ctx.instanceid = c.id
                 AND ctx.contextlevel = :courselevel
@@ -226,7 +226,7 @@ class file_info_context_coursecat extends file_info {
             return $cnt;
         }
 
-        $rs = $DB->get_recordset_sql('SELECT ctx.id contextid, cat.visible
+        $rs = $DB->get_recordset_sql('SELECT ctx.id AS contextid, cat.visible
                 FROM {context} ctx, {course_categories} cat
                 WHERE ctx.instanceid = cat.id
                 AND ctx.contextlevel = :catlevel
index 9acd1d3..985c356 100644 (file)
@@ -288,7 +288,7 @@ class zip_archive extends file_archive {
         if (!isset($this->za)) {
             return false;
         }
-        $localname = ltrim($localname, '/'). '/';
+        $localname = trim($localname, '/'). '/';
         $localname = $this->mangle_pathname($localname);
 
         if ($localname === '/') {
@@ -296,7 +296,13 @@ class zip_archive extends file_archive {
             return false;
         }
 
-        return $this->za->addEmptyDir($localname);
+        if ($localname !== '') {
+            if (!$this->za->addEmptyDir($localname)) {
+                return false;
+            }
+            $this->modified = true;
+        }
+        return true;
     }
 
     /**
index cbd528a..c715811 100644 (file)
@@ -217,9 +217,9 @@ M.form_filemanager.init = function(Y, options) {
                 params: {'filepath':filepath},
                 callback: function(id, obj, args) {
                     scope.filecount = obj.filecount;
-                    scope.check_buttons();
                     scope.options = obj;
                     scope.lazyloading = {};
+                    scope.check_buttons();
                     scope.render(obj);
                 }
             }, true);
index bf3bdc1..0d298a6 100644 (file)
@@ -5125,9 +5125,8 @@ function moodle_process_email($modargs,$body) {
 /**
  * Get mailer instance, enable buffering, flush buffer or disable buffering.
  *
- * @global object
  * @param string $action 'get', 'buffer', 'close' or 'flush'
- * @return object|null mailer instance if 'get' used or nothing
+ * @return moodle_phpmailer|null mailer instance if 'get' used or nothing
  */
 function get_mailer($action='get') {
     global $CFG;
@@ -5466,7 +5465,6 @@ function email_to_user($user, $from, $subject, $messagetext, $messagehtml='', $a
 
     if ($mail->Send()) {
         set_send_count($user);
-        $mail->IsSMTP();                               // use SMTP directly
         if (!empty($mail->SMTPDebug)) {
             echo '</pre>';
         }
index dda8714..6046e66 100644 (file)
@@ -207,10 +207,21 @@ function rss_get_file_full_name($componentname, $filename) {
  *
  * @param stdClass $instance the instance of the source of the RSS feed
  * @param string $sql the SQL used to produce the RSS feed
+ * @param array $params the parameters used in the SQL query
  * @return string the name of the RSS file
  */
-function rss_get_file_name($instance, $sql) {
-    return $instance->id.'_'.md5($sql);
+function rss_get_file_name($instance, $sql, $params = array()) {
+    if ($params) {
+        // If a parameters array is passed, then we want to
+        // serialize it and then concatenate it with the sql.
+        // The reason for this is to generate a unique filename
+        // for queries using the same sql but different parameters.
+        asort($parms);
+        $serializearray = serialize($params);
+        return $instance->id.'_'.md5($sql . $serializearray);
+    } else {
+        return $instance->id.'_'.md5($sql);
+    }
 }
 
 /**
index f071531..7a6e843 100644 (file)
@@ -293,8 +293,8 @@ class core_textlib_testcase extends basic_testcase {
      * @return void
      */
     public function test_entities_to_utf8() {
-        $str = "&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&#237;&#269;ek";
-        $this->assertSame(textlib::entities_to_utf8($str), "Žluťoučký koníček");
+        $str = "&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&iacute;&#269;ek&copy;&quot;&amp;&lt;&gt;&sect;&laquo;";
+        $this->assertSame("Žluťoučký koníček©\"&<>§«", textlib::entities_to_utf8($str));
     }
 
     /**
@@ -302,10 +302,13 @@ class core_textlib_testcase extends basic_testcase {
      * @return void
      */
     public function test_utf8_to_entities() {
-        $str = "Žluťoučký koníček";
-        $this->assertSame(textlib::utf8_to_entities($str), "&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&#xed;&#x10d;ek");
-        $this->assertSame(textlib::utf8_to_entities($str, true), "&#381;lu&#357;ou&#269;k&#253; kon&#237;&#269;ek");
+        $str = "&#x17d;luťoučký kon&iacute;ček&copy;&quot;&amp;&lt;&gt;&sect;&laquo;";
+        $this->assertSame("&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&iacute;&#x10d;ek&copy;&quot;&amp;&lt;&gt;&sect;&laquo;", textlib::utf8_to_entities($str));
+        $this->assertSame("&#381;lu&#357;ou&#269;k&#253; kon&iacute;&#269;ek&copy;&quot;&amp;&lt;&gt;&sect;&laquo;", textlib::utf8_to_entities($str, true));
 
+        $str = "&#381;luťoučký kon&iacute;ček&copy;&quot;&amp;&lt;&gt;&sect;&laquo;";
+        $this->assertSame("&#x17d;lu&#x165;ou&#x10d;k&#xfd; kon&#xed;&#x10d;ek&#xa9;\"&<>&#xa7;&#xab;", textlib::utf8_to_entities($str, false, true));
+        $this->assertSame("&#381;lu&#357;ou&#269;k&#253; kon&#237;&#269;ek&#169;\"&<>&#167;&#171;", textlib::utf8_to_entities($str, true, true));
     }
 
     /**
index ab0db3c..bddcaa7 100644 (file)
@@ -441,6 +441,34 @@ class textlib {
         return $encoded;
     }
 
+    /**
+     * Returns HTML entity transliteration table.
+     * @return array with (html entity => utf-8) elements
+     */
+    protected static function get_entities_table() {
+        static $trans_tbl = null;
+
+        // Generate/create $trans_tbl
+        if (!isset($trans_tbl)) {
+            if (version_compare(phpversion(), '5.3.4') < 0) {
+                $trans_tbl = array();
+                foreach (get_html_translation_table(HTML_ENTITIES) as $val=>$key) {
+                    $trans_tbl[$key] = textlib::convert($val, 'ISO-8859-1', 'utf-8');
+                }
+
+            } else if (version_compare(phpversion(), '5.4.0') < 0) {
+                $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT, 'UTF-8');
+                $trans_tbl = array_flip($trans_tbl);
+
+            } else {
+                $trans_tbl = get_html_translation_table(HTML_ENTITIES, ENT_COMPAT | ENT_HTML401, 'UTF-8');
+                $trans_tbl = array_flip($trans_tbl);
+            }
+        }
+
+        return $trans_tbl;
+    }
+
     /**
      * Converts all the numeric entities &#nnnn; or &#xnnn; to UTF-8
      * Original from laurynas dot butkus at gmail at:
@@ -450,28 +478,24 @@ class textlib {
      * @param string $str input string
      * @param boolean $htmlent convert also html entities (defaults to true)
      * @return string encoded UTF-8 string
-     *
-     * NOTE: we could have used typo3 entities_to_utf8() here
-     *       but the direct alternative used runs 400% quicker
-     *       and uses 0.5Mb less memory, so, let's use it
-     *       (tested against 10^6 conversions)
      */
     public static function entities_to_utf8($str, $htmlent=true) {
-        static $trans_tbl; // Going to use static transliteration table
+        static $callback1 = null ;
+        static $callback2 = null ;
+
+        if (!$callback1 or !$callback2) {
+            $callback1 = create_function('$matches', 'return textlib::code2utf8(hexdec($matches[1]));');
+            $callback2 = create_function('$matches', 'return textlib::code2utf8($matches[1]);');
+        }
 
-        // Replace numeric entities
-        $result = preg_replace('~&#x([0-9a-f]+);~ei', 'textlib::code2utf8(hexdec("\\1"))', $str);
-        $result = preg_replace('~&#([0-9]+);~e', 'textlib::code2utf8(\\1)', $result);
+        $result = (string)$str;
+        $result = preg_replace_callback('/&#x([0-9a-f]+);/i', $callback1, $result);
+        $result = preg_replace_callback('/&#([0-9]+);/', $callback2, $result);
 
         // Replace literal entities (if desired)
         if ($htmlent) {
-            // Generate/create $trans_tbl
-            if (!isset($trans_tbl)) {
-                $trans_tbl = array();
-                foreach (get_html_translation_table(HTML_ENTITIES) as $val=>$key) {
-                    $trans_tbl[$key] = utf8_encode($val);
-                }
-            }
+            $trans_tbl = self::get_entities_table();
+            // It should be safe to search for ascii strings and replace them with utf-8 here.
             $result = strtr($result, $trans_tbl);
         }
         // Return utf8-ised string
@@ -487,17 +511,24 @@ class textlib {
      * @return string converted string
      */
     public static function utf8_to_entities($str, $dec=false, $nonnum=false) {
-        // Avoid some notices from Typo3 code
-        $oldlevel = error_reporting(E_PARSE);
+        static $callback = null ;
+
         if ($nonnum) {
-            $str = self::typo3()->entities_to_utf8((string)$str, true);
+            $str = self::entities_to_utf8($str, true);
         }
+
+        // Avoid some notices from Typo3 code
+        $oldlevel = error_reporting(E_PARSE);
         $result = self::typo3()->utf8_to_entities((string)$str);
+        error_reporting($oldlevel);
+
         if ($dec) {
-            $result = preg_replace('/&#x([0-9a-f]+);/ie', "'&#'.hexdec('$1').';'", $result);
+            if (!$callback) {
+                $callback = create_function('$matches', 'return \'&#\'.(hexdec($matches[1])).\';\';');
+            }
+            $result = preg_replace_callback('/&#x([0-9a-f]+);/i', $callback, $result);
         }
-        // Restore original debug level
-        error_reporting($oldlevel);
+
         return $result;
     }
 
index 84362fe..500467b 100644 (file)
@@ -1382,7 +1382,7 @@ function format_text_email($text, $format) {
         case FORMAT_WIKI:
             // there should not be any of these any more!
             $text = wikify_links($text);
-            return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
+            return textlib::entities_to_utf8(strip_tags($text), true);
             break;
 
         case FORMAT_HTML:
@@ -1393,7 +1393,7 @@ function format_text_email($text, $format) {
         case FORMAT_MARKDOWN:
         default:
             $text = wikify_links($text);
-            return strtr(strip_tags($text), array_flip(get_html_translation_table(HTML_ENTITIES)));
+            return textlib::entities_to_utf8(strip_tags($text), true);
             break;
     }
 }
index 01d6668..39525db 100644 (file)
@@ -40,9 +40,17 @@ YUI.add('moodle-core-formautosubmit',
                 }
 
                 // Assign this select items 'nothing' value and lastindex (current value)
-                var thisselect = Y.one('select#' + this.get('selectid'));
-                thisselect.setData('nothing', this.get('nothing'));
-                thisselect.setData('startindex', thisselect.get('selectedIndex'));
+                if (this.get('selectid')) {
+                    var thisselect = Y.one('select#' + this.get('selectid'));
+                    if (thisselect) {
+                        if (this.get('nothing')) {
+                            thisselect.setData('nothing', this.get('nothing'));
+                        }
+                        thisselect.setData('startindex', thisselect.get('selectedIndex'));
+                    } else {
+                        Y.log("Warning: A single_select element was renderered, but the output is not displayed on the page.");
+                    }
+                }
             },
 
             /**
index 074b86a..1705221 100644 (file)
@@ -447,6 +447,7 @@ function mnet_update_sso_access_control($username, $mnet_host_id, $accessctrl) {
                 "SSO ACL: $accessctrl user '$username' from {$mnethost->name}");
     } else {
         // insert
+        $aclrecord = new stdClass();
         $aclrecord->username = $username;
         $aclrecord->accessctrl = $accessctrl;
         $aclrecord->mnet_host_id = $mnet_host_id;
index 81b6186..f61aa40 100644 (file)
@@ -1346,6 +1346,23 @@ class assign {
         return true;
     }
 
+    /**
+     * Mark in the database that this grade record should have an update notification sent by cron.
+     *
+     * @param stdClass $grade a grade record keyed on id
+     * @return bool true for success
+     */
+    public function notify_grade_modified($grade) {
+        global $DB;
+
+        $grade->timemodified = time();
+        if ($grade->mailed != 1) {
+            $grade->mailed = 0;
+        }
+
+        return $DB->update_record('assign_grades', $grade);
+    }
+
     /**
      * Update a grade in the grade table for the assignment and in the gradebook
      *
@@ -1691,6 +1708,10 @@ class assign {
             $grade->locked = 0;
             $grade->grade = -1;
             $grade->grader = $USER->id;
+
+            // The mailed flag can be one of 3 values: 0 is unsent, 1 is sent and 2 is do not send yet.
+            // This is because students only want to be notified about certain types of update (grades and feedback).
+            $grade->mailed = 2;
             $gid = $DB->insert_record('assign_grades', $grade);
             $grade->id = $gid;
             return $grade;
@@ -2761,6 +2782,7 @@ class assign {
             }
 
             $this->update_grade($grade);
+            $this->notify_grade_modified($grade);
 
             // save outcomes
             if ($CFG->enableoutcomes) {
@@ -3406,6 +3428,7 @@ class assign {
             $grade->mailed = 0;
 
             $this->update_grade($grade);
+            $this->notify_grade_modified($grade);
 
             $user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
 
index e4745f2..5844463 100644 (file)
@@ -31,6 +31,39 @@ defined('MOODLE_INTERNAL') || die();
  * @return bool
  */
 function assignsubmission_comments_comment_validate(stdClass $options) {
+    global $USER, $CFG, $DB;
+
+    if ($options->commentarea != 'submission_comments' &&
+            $options->commentarea != 'submission_comments_upgrade') {
+        throw new comment_exception('invalidcommentarea');
+    }
+    if (!$submission = $DB->get_record('assign_submission', array('id'=>$options->itemid))) {
+        throw new comment_exception('invalidcommentitemid');
+    }
+    $context = $options->context;
+
+    require_once($CFG->dirroot . '/mod/assign/locallib.php');
+    $assignment = new assign($context, null, null);
+
+    if ($assignment->get_instance()->id != $submission->assignment) {
+        throw new comment_exception('invalidcontext');
+    }
+    if (!has_capability('mod/assign:grade', $context)) {
+        if (!has_capability('mod/assign:submit', $context)) {
+            throw new comment_exception('nopermissiontocomment');
+        } else if ($assignment->get_instance()->teamsubmission) {
+            $group = $assignment->get_submission_group($USER->id);
+            $groupid = 0;
+            if ($group) {
+                $groupid = $group->id;
+            }
+            if ($groupid != $submission->groupid) {
+                throw new comment_exception('nopermissiontocomment');
+            }
+        } else if ($submission->userid != $USER->id) {
+            throw new comment_exception('nopermissiontocomment');
+        }
+    }
 
     return true;
 }
@@ -42,6 +75,39 @@ function assignsubmission_comments_comment_validate(stdClass $options) {
  * @return array
  */
 function assignsubmission_comments_comment_permissions(stdClass $options) {
+    global $USER, $CFG, $DB;
+
+    if ($options->commentarea != 'submission_comments' &&
+            $options->commentarea != 'submission_comments_upgrade') {
+        throw new comment_exception('invalidcommentarea');
+    }
+    if (!$submission = $DB->get_record('assign_submission', array('id'=>$options->itemid))) {
+        throw new comment_exception('invalidcommentitemid');
+    }
+    $context = $options->context;
+
+    require_once($CFG->dirroot . '/mod/assign/locallib.php');
+    $assignment = new assign($context, null, null);
+
+    if ($assignment->get_instance()->id != $submission->assignment) {
+        throw new comment_exception('invalidcontext');
+    }
+    if (!has_capability('mod/assign:grade', $context)) {
+        if (!has_capability('mod/assign:submit', $context)) {
+            return array('post' => false, 'view' => false);
+        } else if ($assignment->get_instance()->teamsubmission) {
+            $group = $assignment->get_submission_group($USER->id);
+            $groupid = 0;
+            if ($group) {
+                $groupid = $group->id;
+            }
+            if ($groupid != $submission->groupid) {
+                return array('post' => false, 'view' => false);
+            }
+        } else if ($submission->userid != $USER->id) {
+            return array('post' => false, 'view' => false);
+        }
+    }
 
     return array('post' => true, 'view' => true);
 }
index b846573..8a89006 100644 (file)
@@ -54,7 +54,7 @@ function toolbook_importhtml_import_chapters($package, $type, $book, $context, $
     }
     if ($type == 0) {
         $chapterfile = reset($chapterfiles);
-        if ($file = $fs->get_file_by_hash("$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname")) {
+        if ($file = $fs->get_file_by_hash(sha1("$context->id/mod_book/importhtmltemp/0/$chapterfile->pathname"))) {
             $htmlcontent = toolbook_importhtml_fix_encoding($file->get_content());
             $htmlchapters = toolbook_importhtml_parse_headings(toolbook_importhtml_parse_body($htmlcontent));
             // TODO: process h1 as main chapter and h2 as subchapters
index b515244..4d350c7 100644 (file)
@@ -720,6 +720,11 @@ function forum_cron() {
                 $eventdata->contexturl = "{$CFG->wwwroot}/mod/forum/discuss.php?d={$discussion->id}#p{$post->id}";
                 $eventdata->contexturlname = $discussion->name;
 
+                // If forum_replytouser is not set then send mail using the noreplyaddress.
+                if (empty($CFG->forum_replytouser)) {
+                    $eventdata->userfrom->email = $CFG->noreplyaddress;
+                }
+
                 $mailresult = message_send($eventdata);
                 if (!$mailresult){
                     mtrace("Error: mod/forum/lib.php forum_cron(): Could not send out mail for id $post->id to user $userto->id".
@@ -1011,10 +1016,9 @@ function forum_cron() {
                 }
 
                 $attachment = $attachname='';
-                $usetrueaddress = true;
                 // Directly email forum digests rather than sending them via messaging, use the
                 // site shortname as 'from name', the noreply address will be used by email_to_user.
-                $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname, $usetrueaddress, $CFG->forum_replytouser);
+                $mailresult = email_to_user($userto, $site->shortname, $postsubject, $posttext, $posthtml, $attachment, $attachname);
 
                 if (!$mailresult) {
                     mtrace("ERROR!");
@@ -5655,7 +5659,7 @@ function forum_print_latest_discussions($course, $forum, $maxdiscussions=-1, $di
                     $link = true;
                 } else {
                     $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
-                    $link = forum_user_can_post($forum, $discussion, $USER, $cm, $course, $modcontext);
+                    $link = forum_user_can_see_discussion($forum, $discussion, $modcontext, $USER);
                 }
 
                 $discussion->forum = $forum->id;
index 86bf31b..4d5900b 100644 (file)
@@ -56,16 +56,10 @@ function forum_rss_get_feed($context, $args) {
     }
 
     //the sql that will retreive the data for the feed and be hashed to get the cache filename
-    $sql = forum_rss_get_sql($forum, $cm);
+    list($sql, $params) = forum_rss_get_sql($forum, $cm);
 
     // Hash the sql to get the cache file name.
-    // If the forum is Q and A then we need to cache the files per user. This can
-    // have a large impact on performance, so we want to only do it on this type of forum.
-    if ($forum->type == 'qanda') {
-        $filename = rss_get_file_name($forum, $sql . $USER->id);
-    } else {
-        $filename = rss_get_file_name($forum, $sql);
-    }
+    $filename = rss_get_file_name($forum, $sql, $params);
     $cachedfilepath = rss_get_file_full_name('mod_forum', $filename);
 
     //Is the cache out of date?
@@ -73,14 +67,15 @@ function forum_rss_get_feed($context, $args) {
     if (file_exists($cachedfilepath)) {
         $cachedfilelastmodified = filemtime($cachedfilepath);
     }
-    //if the cache is more than 60 seconds old and there's new stuff
+    // Used to determine if we need to generate a new RSS feed.
     $dontrecheckcutoff = time()-60;
-    if ( $dontrecheckcutoff > $cachedfilelastmodified && forum_rss_newstuff($forum, $cm, $cachedfilelastmodified)) {
-        //need to regenerate the cached version
-        $result = forum_rss_feed_contents($forum, $sql, $modcontext);
-        if (!empty($result)) {
-            $status = rss_save_file('mod_forum',$filename,$result);
-        }
+    // If it hasn't been generated we will need to create it, otherwise only update
+    // if there is new stuff to show and it is older than the cut off date set above.
+    if (($cachedfilelastmodified == 0) || (($dontrecheckcutoff > $cachedfilelastmodified) &&
+        forum_rss_newstuff($forum, $cm, $cachedfilelastmodified))) {
+        // Need to regenerate the cached version.
+        $result = forum_rss_feed_contents($forum, $sql, $params, $modcontext);
+        $status = rss_save_file('mod_forum', $filename, $result);
     }
 
     //return the path to the cached version
@@ -111,10 +106,9 @@ function forum_rss_delete_file($forum) {
 function forum_rss_newstuff($forum, $cm, $time) {
     global $DB;
 
-    $sql = forum_rss_get_sql($forum, $cm, $time);
+    list($sql, $params) = forum_rss_get_sql($forum, $cm, $time);
 
-    $recs = $DB->get_records_sql($sql, null, 0, 1);//limit of 1. If we get even 1 back we have new stuff
-    return ($recs && !empty($recs));
+    return $DB->record_exists_sql($sql, $params);
 }
 
 /**
@@ -126,17 +120,11 @@ function forum_rss_newstuff($forum, $cm, $time) {
  * @return string the SQL query to be used to get the Discussion/Post details from the forum table of the database
  */
 function forum_rss_get_sql($forum, $cm, $time=0) {
-    $sql = null;
-
-    if (!empty($forum->rsstype)) {
-        if ($forum->rsstype == 1) {    //Discussion RSS
-            $sql = forum_rss_feed_discussions_sql($forum, $cm, $time);
-        } else {                //Post RSS
-            $sql = forum_rss_feed_posts_sql($forum, $cm, $time);
-        }
+    if ($forum->rsstype == 1) { // Discussion RSS
+        return forum_rss_feed_discussions_sql($forum, $cm, $time);
+    } else { // Post RSS
+        return forum_rss_feed_posts_sql($forum, $cm, $time);
     }
-
-    return $sql;
 }
 
 /**
@@ -155,7 +143,7 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
     $modcontext = null;
 
     $now = round(time(), -2);
-    $params = array($cm->instance);
+    $params = array();
 
     $modcontext = context_module::instance($cm->id);
 
@@ -172,21 +160,21 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
         }
     }
 
-    //do we only want new posts?
+    // Do we only want new posts?
     if ($newsince) {
-        $newsince = " AND p.modified > '$newsince'";
+        $params['newsince'] = $newsince;
+        $newsince = " AND p.modified > :newsince";
     } else {
         $newsince = '';
     }
 
-    //get group enforcing SQL
-    $groupmode    = groups_get_activity_groupmode($cm);
+    // Get group enforcing SQL.
+    $groupmode = groups_get_activity_groupmode($cm);
     $currentgroup = groups_get_activity_group($cm);
-    $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
+    list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
 
-    if ($groupmode && $currentgroup) {
-        $params['groupid'] = $currentgroup;
-    }
+    // Add the groupparams to the params array.
+    $params = array_merge($params, $groupparams);
 
     $forumsort = "d.timemodified DESC";
     $postdata = "p.id AS postid, p.subject, p.created as postcreated, p.modified, p.discussion, p.userid, p.message as postmessage, p.messageformat AS postformat, p.messagetrust AS posttrust";
@@ -199,7 +187,7 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
              WHERE d.forum = {$forum->id} AND p.parent = 0
                    $timelimit $groupselect $newsince
           ORDER BY $forumsort";
-    return $sql;
+    return array($sql, $params);
 }
 
 /**
@@ -213,19 +201,20 @@ function forum_rss_feed_discussions_sql($forum, $cm, $newsince=0) {
 function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
     $modcontext = context_module::instance($cm->id);
 
-    //get group enforcement SQL
-    $groupmode    = groups_get_activity_groupmode($cm);
+    // Get group enforcement SQL.
+    $groupmode = groups_get_activity_groupmode($cm);
     $currentgroup = groups_get_activity_group($cm);
+    $params = array();
 
-    $groupselect = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
+    list($groupselect, $groupparams) = forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext);
 
-    if ($groupmode && $currentgroup) {
-        $params['groupid'] = $currentgroup;
-    }
+    // Add the groupparams to the params array.
+    $params = array_merge($params, $groupparams);
 
-    //do we only want new posts?
+    // Do we only want new posts?
     if ($newsince) {
-        $newsince = " AND p.modified > '$newsince'";
+        $params['newsince'] = $newsince;
+        $newsince = " AND p.modified > :newsince";
     } else {
         $newsince = '';
     }
@@ -250,7 +239,7 @@ function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
                 $groupselect
             ORDER BY p.created desc";
 
-    return $sql;
+    return array($sql, $params);
 }
 
 /**
@@ -264,6 +253,7 @@ function forum_rss_feed_posts_sql($forum, $cm, $newsince=0) {
  */
 function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=null) {
     $groupselect = '';
+    $params = array();
 
     if ($groupmode) {
         if ($groupmode == VISIBLEGROUPS or has_capability('moodle/site:accessallgroups', $modcontext)) {
@@ -272,7 +262,7 @@ function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=nul
                 $params['groupid'] = $currentgroup;
             }
         } else {
-            //seprate groups without access all
+            // Separate groups without access all.
             if ($currentgroup) {
                 $groupselect = "AND (d.groupid = :groupid OR d.groupid = -1)";
                 $params['groupid'] = $currentgroup;
@@ -282,7 +272,7 @@ function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=nul
         }
     }
 
-    return $groupselect;
+    return array($groupselect, $params);
 }
 
 /**
@@ -290,21 +280,19 @@ function forum_rss_get_group_sql($cm, $groupmode, $currentgroup, $modcontext=nul
  * It returns false if something is wrong
  *
  * @param stdClass $forum the forum object
- * @param string   $sql   The SQL used to retrieve the contents from the database
+ * @param string $sql the SQL used to retrieve the contents from the database
+ * @param array $params the SQL parameters used
  * @param object $context the context this forum relates to
  * @return bool|string false if the contents is empty, otherwise the contents of the feed is returned
  *
  * @Todo MDL-31129 implement post attachment handling
  */
 
-function forum_rss_feed_contents($forum, $sql) {
+function forum_rss_feed_contents($forum, $sql, $params, $context) {
     global $CFG, $DB, $USER;
 
-
     $status = true;
 
-    $params = array();
-    //$params['forumid'] = $forum->id;
     $recs = $DB->get_recordset_sql($sql, $params, 0, $forum->rssarticles);
 
     //set a flag. Are we displaying discussions or posts?
@@ -316,7 +304,6 @@ function forum_rss_feed_contents($forum, $sql) {
     if (!$cm = get_coursemodule_from_instance('forum', $forum->id, $forum->course)) {
         print_error('invalidcoursemodule');
     }
-    $context = context_module::instance($cm->id);
 
     $formatoptions = new stdClass();
     $items = array();
@@ -376,29 +363,17 @@ function forum_rss_feed_contents($forum, $sql) {
         }
     $recs->close();
 
-
+    // Create the RSS header.
+    $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
+                                  $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
+                                  format_string($forum->intro,true)); // TODO: fix format
+    // Now all the RSS items, if there are any.
+    $articles = '';
     if (!empty($items)) {
-        //First the RSS header
-        $header = rss_standard_header(strip_tags(format_string($forum->name,true)),
-                                      $CFG->wwwroot."/mod/forum/view.php?f=".$forum->id,
-                                      format_string($forum->intro,true)); // TODO: fix format
-        //Now all the rss items
-        if (!empty($header)) {
-            $articles = rss_add_items($items);
-        }
-        //Now the RSS footer
-        if (!empty($header) && !empty($articles)) {
-            $footer = rss_standard_footer();
-        }
-        //Now, if everything is ok, concatenate it
-        if (!empty($header) && !empty($articles) && !empty($footer)) {
-            $status = $header.$articles.$footer;
-        } else {
-            $status = false;
-        }
-    } else {
-        $status = false;
+        $articles = rss_add_items($items);
     }
+    // Create the RSS footer.
+    $footer = rss_standard_footer();
 
-    return $status;
+    return $header . $articles . $footer;
 }
index 58a5fd5..a7ea07b 100644 (file)
@@ -103,19 +103,19 @@ if (!is_null($mode) and has_capability('mod/forum:managesubscriptions', $context
     require_sesskey();
     switch ($mode) {
         case FORUM_CHOOSESUBSCRIBE : // 0
-            forum_forcesubscribe($forum->id, 0);
+            forum_forcesubscribe($forum->id, FORUM_CHOOSESUBSCRIBE);
             redirect($returnto, get_string("everyonecannowchoose", "forum"), 1);
             break;
         case FORUM_FORCESUBSCRIBE : // 1
-            forum_forcesubscribe($forum->id, 1);
+            forum_forcesubscribe($forum->id, FORUM_FORCESUBSCRIBE);
             redirect($returnto, get_string("everyoneisnowsubscribed", "forum"), 1);
             break;
         case FORUM_INITIALSUBSCRIBE : // 2
-            forum_forcesubscribe($forum->id, 2);
+            forum_forcesubscribe($forum->id, FORUM_INITIALSUBSCRIBE);
             redirect($returnto, get_string("everyoneisnowsubscribed", "forum"), 1);
             break;
         case FORUM_DISALLOWSUBSCRIBE : // 3
-            forum_forcesubscribe($forum->id, 3);
+            forum_forcesubscribe($forum->id, FORUM_DISALLOWSUBSCRIBE);
             redirect($returnto, get_string("noonecansubscribenow", "forum"), 1);
             break;
         default:
index 8014bc1..d4a384b 100644 (file)
@@ -306,7 +306,11 @@ class restore_quiz_activity_structure_step extends restore_questions_activity_st
         $data->timestart = $this->apply_date_offset($data->timestart);
         $data->timefinish = $this->apply_date_offset($data->timefinish);
         $data->timemodified = $this->apply_date_offset($data->timemodified);
-        $data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
+        if (!empty($data->timecheckstate)) {
+            $data->timecheckstate = $this->apply_date_offset($data->timecheckstate);
+        } else {
+            $data->timecheckstate = 0;
+        }
 
         // Deals with up-grading pre-2.3 back-ups to 2.3+.
         if (!isset($data->state)) {
index 629e2db..7ab6626 100644 (file)
@@ -165,6 +165,7 @@ $string['configintro'] = 'The values you set here define the default values that
 $string['configmaximumgrade'] = 'The default grade that the quiz grade is scaled to be out of.';
 $string['confignewpageevery'] = 'When adding questions to the quiz page breaks will automatically be inserted according to the setting you choose here.';
 $string['confignavmethod'] = 'In Free navigation, questions may be answered in any order using navigation. In Sequential, questions must be answered in strict sequence.';
+$string['configoutcomesadvanced'] = 'If this option is turned on, then the Outcomes on the quiz editing form are advanced settings.';
 $string['configpenaltyscheme'] = 'Penalty subtracted for each wrong response in adaptive mode.';
 $string['configpopup'] = 'Force the attempt to open in a popup window, and use JavaScript tricks to try to restrict copy and paste, etc. during quiz attempts.';
 $string['configrequirepassword'] = 'Students must enter this password before they can attempt the quiz.';
@@ -517,6 +518,7 @@ To add page breaks after particular questions, tick the checkboxes next to the q
 To arrange the questions over a number of pages, click the Repaginate button and select the desired number of questions per page.';
 $string['orderingquiz'] = 'Order and paging';
 $string['orderingquizx'] = 'Order and paging: {$a}';
+$string['outcomesadvanced'] = 'Outcomes are advanced settings';
 $string['outof'] = '{$a->grade} out of a maximum of {$a->maxgrade}';
 $string['outofpercent'] = '{$a->grade} out of a maximum of {$a->maxgrade} ({$a->percent}%)';
 $string['outofshort'] = '{$a->grade}/{$a->maxgrade}';
index 87af317..39a4860 100644 (file)
@@ -378,6 +378,9 @@ class mod_quiz_mod_form extends moodleform_mod {
         // -------------------------------------------------------------------------------
         $this->standard_coursemodule_elements();
 
+        // Check and act on whether setting outcomes is considered an advanced setting.
+        $mform->setAdvanced('modoutcomes', !empty($quizconfig->outcomes_adv));
+
         // -------------------------------------------------------------------------------
         $this->add_action_buttons();
     }
index 7652dfa..af458c2 100644 (file)
@@ -193,6 +193,13 @@ $quizsettings->add(new mod_quiz_admin_setting_browsersecurity('quiz/browsersecur
         get_string('showinsecurepopup', 'quiz'), get_string('configpopup', 'quiz'),
         array('value' => '-', 'adv' => true), null));
 
+// Allow user to specify if setting outcomes is an advanced setting
+if (!empty($CFG->enableoutcomes)) {
+    $quizsettings->add(new admin_setting_configcheckbox('quiz/outcomes_adv',
+        get_string('outcomesadvanced', 'quiz'), get_string('configoutcomesadvanced', 'quiz'),
+        '0'));
+}
+
 // Now, depending on whether any reports have their own settings page, add
 // the quiz setting page to the appropriate place in the tree.
 if (empty($reportsbyname)) {
index 8ea5525..21aa851 100644 (file)
@@ -68,14 +68,16 @@ function get_scorm_question_count($scormid) {
     $params[] = "cmi.interactions_%.id";
     $rs = $DB->get_recordset_select("scorm_scoes_track", $select, $params, 'element');
     $keywords = array("cmi.interactions_", ".id");
-    foreach ($rs as $record) {
-        $num = trim(str_ireplace($keywords, '', $record->element));
-        if (is_numeric($num) && $num > $count) {
-            $count = $num;
+    if ($rs->valid()) {
+        // Done as interactions start at 0 (do only if we have something to report).
+        $count++;
+        foreach ($rs as $record) {
+            $num = trim(str_ireplace($keywords, '', $record->element));
+            if (is_numeric($num) && $num > $count) {
+                $count = $num;
+            }
         }
     }
-    //done as interactions start at 0
-    $count++;
     $rs->close(); // closing recordset
     return $count;
 }
index e70fe95..de1eef1 100644 (file)
@@ -31,7 +31,7 @@ require_once("$CFG->dirroot/repository/lib.php");
 $subwikiid = required_param('subwiki', PARAM_INT);
 // not being used for file management, we use it to generate navbar link
 $pageid    = optional_param('pageid', 0, PARAM_INT);
-$returnurl = optional_param('returnurl', '', PARAM_URL);
+$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
 
 if (!$subwiki = wiki_get_subwiki($subwikiid)) {
     print_error('incorrectsubwikiid', 'wiki');
index d332563..a3736e7 100644 (file)
@@ -197,6 +197,7 @@ $string['saving'] = 'Saving wiki page';
 $string['savingerror'] = 'Saving error';
 $string['searchcontent'] = 'Search in page content';
 $string['searchresult'] = 'Search results:';
+$string['searchterms'] = 'Search terms';
 $string['searchwikis'] = 'Search wikis';
 $string['special'] = 'Special';
 $string['tableofcontents'] = 'Table of contents';
index 974c46d..f42f3a6 100644 (file)
@@ -474,7 +474,8 @@ function wiki_search_form($cm, $search = '') {
     $output = '<div class="wikisearch">';
     $output .= '<form method="post" action="' . $CFG->wwwroot . '/mod/wiki/search.php" style="display:inline">';
     $output .= '<fieldset class="invisiblefieldset">';
-    $output .= '<label class="accesshide" for="searchwiki">' . get_string("searchwikis", "wiki") . '</label>';
+    $output .= '<legend class="accesshide">'. get_string('searchwikis', 'wiki') .'</legend>';
+    $output .= '<label class="accesshide" for="searchwiki">' . get_string("searchterms", "wiki") . '</label>';
     $output .= '<input id="searchwiki" name="searchstring" type="text" size="18" value="' . s($search, true) . '" alt="search" />';
     $output .= '<input name="courseid" type="hidden" value="' . $cm->course . '" />';
     $output .= '<input name="cmid" type="hidden" value="' . $cm->id . '" />';
index ae80f13..b4c679a 100644 (file)
@@ -42,10 +42,14 @@ defined('MOODLE_INTERNAL') || die();
  */
 class qformat_learnwise extends qformat_default {
 
-    function provide_import() {
+    public function provide_import() {
         return true;
     }
 
+    public function export_file_extension() {
+        return '.xml';
+    }
+
     protected function readquestions($lines) {
         $questions = array();
         $currentquestion = array();
@@ -62,9 +66,9 @@ class qformat_learnwise extends qformat_default {
         return $questions;
     }
 
-    function readquestion($lines) {
+    protected function readquestion($lines) {
         $text = implode(' ', $lines);
-        $text = str_replace(array('\t','\n','\r','\''), array('','','','\\\''), $text);
+        $text = str_replace(array('\t','\n','\r'), array('','',''), $text);
 
         $startpos = strpos($text, '<question type');
         $endpos = strpos($text, '</question>');
@@ -75,8 +79,8 @@ class qformat_learnwise extends qformat_default {
         preg_match("/<question type=[\"\']([^\"\']+)[\"\']>/i", $text, $matches);
         $type = strtolower($matches[1]); // multichoice or multianswerchoice
 
-        $questiontext = $this->unhtmlentities($this->stringbetween($text, '<text>', '</text>'));
-        $questionhint = $this->unhtmlentities($this->stringbetween($text, '<hint>', '</hint>'));
+        $questiontext = textlib::entities_to_utf8($this->stringbetween($text, '<text>', '</text>'));
+        $questionhint = textlib::entities_to_utf8($this->stringbetween($text, '<hint>', '</hint>'));
         $questionaward = $this->stringbetween($text, '<award>', '</award>');
         $optionlist = $this->stringbetween($text, '<answer>', '</answer>');
 
@@ -89,10 +93,13 @@ class qformat_learnwise extends qformat_default {
 
         if ($type == 'multichoice') {
             foreach ($optionlist as $option) {
+                if (trim($option) === '') {
+                    continue;
+                }
                 $correct = $this->stringbetween($option, ' correct="', '">');
                 $answer = $this->stringbetween($option, '">', '</option>');
                 $optionscorrect[$n] = $correct;
-                $optionstext[$n] = $this->unhtmlentities($answer);
+                $optionstext[$n] = textlib::entities_to_utf8($answer);
                 ++$n;
             }
         } else if ($type == 'multianswerchoice') {
@@ -102,6 +109,9 @@ class qformat_learnwise extends qformat_default {
             $optionsaward = array();
 
             foreach ($optionlist as $option) {
+                if (trim($option) === '') {
+                    continue;
+                }
                 preg_match("/correct=\"([^\"]*)\"/i", $option, $correctmatch);
                 preg_match("/award=\"([^\"]*)\"/i", $option, $awardmatch);
 
@@ -115,7 +125,7 @@ class qformat_learnwise extends qformat_default {
                 $answer = $this->stringbetween($option, '">', '</option>');
 
                 $optionscorrect[$n] = $correct;
-                $optionstext[$n] = $this->unhtmlentities($answer);
+                $optionstext[$n] = textlib::entities_to_utf8($answer);
                 $optionsaward[$n] = $award;
                 ++$n;
             }
@@ -127,22 +137,25 @@ class qformat_learnwise extends qformat_default {
         $question = $this->defaultquestion();
         $question->qtype = MULTICHOICE;
         $question->name = $this->create_default_question_name($questiontext, get_string('questionname', 'question'));
+        $this->add_blank_combined_feedback($question);
 
         $question->questiontext = $questiontext;
+        $question->questiontextformat = FORMAT_HTML;
         $question->single = ($type == 'multichoice') ? 1 : 0;
-        $question->feedback[] = '';
 
         $question->fraction = array();
         $question->answer = array();
         for ($n = 0; $n < count($optionstext); ++$n) {
             if ($optionstext[$n]) {
-                if (!isset($numcorrect)) { // single answer
+                if (!isset($numcorrect)) {
+                    // Single answer.
                     if ($optionscorrect[$n] == 'yes') {
                         $fraction = (int) $questionaward;
                     } else {
                         $fraction = 0;
                     }
-                } else { // mulitple answers
+                } else {
+                    // Multiple answers.
                     if ($optionscorrect[$n] == 'yes') {
                         $fraction = $optionsaward[$n] / $totalaward;
                     } else {
@@ -150,15 +163,22 @@ class qformat_learnwise extends qformat_default {
                     }
                 }
                 $question->fraction[] = $fraction;
-                $question->answer[] = $optionstext[$n];
-                $question->feedback[] = ''; // no feedback in this type
+                $question->answer[] = array('text' => $optionstext[$n], 'format' => FORMAT_HTML);
+                $question->feedback[] = array('text' => '', 'format' => FORMAT_HTML); // No feedback in this type.
             }
         }
 
         return $question;
     }
 
-    function stringbetween($text, $start, $end) {
+    /**
+     * Extract the substring of $text between $start and $end.
+     * @param string $text text to analyse.
+     * @param string $start opening delimiter.
+     * @param string $end closing delimiter.
+     * @return string the requested substring.
+     */
+    protected function stringbetween($text, $start, $end) {
         $startpos = strpos($text, $start) + strlen($start);
         $endpos = strpos($text, $end);
 
@@ -166,13 +186,4 @@ class qformat_learnwise extends qformat_default {
             return substr($text, $startpos, $endpos - $startpos);
         }
     }
-
-    function unhtmlentities($string) {
-        $transtable = get_html_translation_table(HTML_ENTITIES);
-        $transtable = array_flip($transtable);
-        return strtr($string, $transtable);
-    }
-
 }
-
-
index f94cf1d..9c2229e 100644 (file)
@@ -24,4 +24,4 @@
  */
 
 $string['pluginname'] = 'Learnwise format';
-$string['plugidnname_help'] = 'This format enables the import of multiple choice questions saved in Learnwise\'s XML format.';
+$string['pluginname_help'] = 'This format enables the import of multiple choice questions saved in Learnwise\'s XML format.';
index 7e24ba3..f7551a7 100644 (file)
@@ -124,7 +124,7 @@ $options->behaviour = $quba->get_preferred_behaviour();
 $options->maxmark = $quba->get_question_max_mark($slot);
 
 // Create the settings form, and initialise the fields.
-$optionsform = new preview_options_form(question_preview_form_url($question->id, $context),
+$optionsform = new preview_options_form(question_preview_form_url($question->id, $context, $previewid),
         array('quba' => $quba, 'maxvariant' => $maxvariant));
 $optionsform->set_data($options);
 
@@ -135,7 +135,9 @@ if ($newoptions = $optionsform->get_submitted_data()) {
     if (!isset($newoptions->variant)) {
         $newoptions->variant = $options->variant;
     }
-    restart_preview($previewid, $question->id, $newoptions, $context);
+    if (isset($newoptions->saverestart)) {
+        restart_preview($previewid, $question->id, $newoptions, $context);
+    }
 }
 
 // Prepare a URL that is used in various places.
index 16adc1c..7b7d85e 100644 (file)
@@ -44,7 +44,7 @@ class preview_options_form extends moodleform {
             question_display_options::VISIBLE => get_string('shown', 'question'),
         );
 
-        $mform->addElement('header', 'optionsheader', get_string('changeoptions', 'question'));
+        $mform->addElement('header', 'optionsheader', get_string('attemptoptions', 'question'));
 
         $behaviours = question_engine::get_behaviour_options(
                 $this->_customdata['quba']->get_preferred_behaviour());
@@ -56,6 +56,11 @@ class preview_options_form extends moodleform {
                 array('size' => '5'));
         $mform->setType('maxmark', PARAM_FLOAT);
 
+        $mform->addElement('submit', 'saverestart',
+                get_string('restartwiththeseoptions', 'question'));
+
+        $mform->addElement('header', 'optionsheader', get_string('displayoptions', 'question'));
+
         if ($this->_customdata['maxvariant'] > 1) {
             $variants = range(1, $this->_customdata['maxvariant']);
             $mform->addElement('select', 'variant', get_string('questionvariant', 'question'),
@@ -88,8 +93,8 @@ class preview_options_form extends moodleform {
         $mform->addElement('select', 'history',
                 get_string('responsehistory', 'question'), $hiddenofvisible);
 
-        $mform->addElement('submit', 'submit',
-                get_string('restartwiththeseoptions', 'question'));
+        $mform->addElement('submit', 'saveupdate',
+                get_string('updatedisplayoptions', 'question'));
     }
 }
 
@@ -286,10 +291,10 @@ function question_preview_action_url($questionid, $qubaid,
 /**
  * The the URL to use for actions relating to this preview.
  * @param int $questionid the question being previewed.
- * @param int $qubaid the id of the question usage for this preview.
- * @param question_preview_options $options the options in use.
+ * @param context $context the current moodle context.
+ * @param int $previewid optional previewid to sign post saved previewed answers.
  */
-function question_preview_form_url($questionid, $context) {
+function question_preview_form_url($questionid, $context, $previewid = null) {
     $params = array(
         'id' => $questionid,
     );
@@ -298,6 +303,9 @@ function question_preview_form_url($questionid, $context) {
     } else if ($context->contextlevel == CONTEXT_COURSE) {
         $params['courseid'] = $context->instanceid;
     }
+    if ($previewid) {
+        $params['previewid'] = $previewid;
+    }
     return new moodle_url('/question/preview.php', $params);
 }
 
@@ -333,14 +341,16 @@ function question_preview_cron() {
             'quba.component = :qubacomponent
                     AND NOT EXISTS (
                         SELECT 1
-                          FROM {question_attempts} qa
-                          JOIN {question_attempt_steps} qas ON qas.questionattemptid = qa.id
-                         WHERE qa.questionusageid = quba.id
-                           AND (qa.timemodified > :qamodifiedcutoff
-                                    OR qas.timecreated > :stepcreatedcutoff)
+                          FROM {question_attempts}      subq_qa
+                          JOIN {question_attempt_steps} subq_qas ON subq_qas.questionattemptid = subq_qa.id
+                          JOIN {question_usages}        subq_qu  ON subq_qu.id = subq_qa.questionusageid
+                         WHERE subq_qa.questionusageid = quba.id
+                           AND subq_qu.component = :qubacomponent2
+                           AND (subq_qa.timemodified > :qamodifiedcutoff
+                                    OR subq_qas.timecreated > :stepcreatedcutoff)
                     )
             ',
-            array('qubacomponent' => 'core_question_preview',
+            array('qubacomponent' => 'core_question_preview', 'qubacomponent2' => 'core_question_preview',
                 'qamodifiedcutoff' => $lastmodifiedcutoff, 'stepcreatedcutoff' => $lastmodifiedcutoff));
 
     question_engine::delete_questions_usage_by_activities($oldpreviews);
index 72f0f4a..f7070d8 100644 (file)
@@ -102,6 +102,11 @@ class qtype_shortanswer_question extends question_graded_by_strategy
             $regexp .= 'i';
         }
 
+        if (function_exists('normalizer_normalize')) {
+            $regexp = normalizer_normalize($regexp, Normalizer::FORM_C);
+            $string = normalizer_normalize($string, Normalizer::FORM_C);
+        }
+
         return preg_match($regexp, trim($string));
     }
 
index 839712c..d287ff4 100644 (file)
@@ -106,6 +106,18 @@ class qtype_shortanswer_question_test extends advanced_testcase {
         // See http://moodle.org/mod/forum/discuss.php?d=120557
         $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
                 'ITÁLIE', 'Itálie', true));
+
+        if (function_exists('normalizer_normalize')) {
+            // Test ambiguous unicode representations
+            $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                    'départ', 'DÉPART', true));
+            $this->assertFalse((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                    'départ', 'DÉPART', false));
+            $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                    'd'."\xC3\xA9".'part', 'd'."\x65\xCC\x81".'part', false));
+            $this->assertTrue((bool)qtype_shortanswer_question::compare_string_with_wildcard(
+                    'd'."\xC3\xA9".'part', 'D'."\x45\xCC\x81".'PART', true));
+        }
     }
 
     public function test_is_complete_response() {
index c7abae3..7c82e66 100644 (file)
@@ -42,7 +42,7 @@ add_to_log($course->id, 'course', 'report outline', "report/outline/index.php?id
 $showlastaccess = true;
 $hiddenfields = explode(',', $CFG->hiddenuserfields);
 
-if (array_search('lastaccess', $hiddenfields) and !has_capability('moodle/user:viewhiddendetails', $context)) {
+if (array_search('lastaccess', $hiddenfields) !== false and !has_capability('moodle/user:viewhiddendetails', $context)) {
     $showlastaccess = false;
 }
 
index 3857a0d..31b7e15 100644 (file)
@@ -220,7 +220,11 @@ switch ($action) {
 
         $parent_path = $file->get_parent_directory()->get_filepath();
 
-        if ($newfile = $zipper->archive_to_storage(array($file), $user_context->id, 'user', 'draft', $draftid, $parent_path, $filepath.'.zip', $USER->id)) {
+        $filepath = explode('/', trim($file->get_filepath(), '/'));
+        $filepath = array_pop($filepath);
+        $zipfile = repository::get_unused_filename($draftid, $parent_path, $filepath . '.zip');
+
+        if ($newfile = $zipper->archive_to_storage(array($filepath => $file), $user_context->id, 'user', 'draft', $draftid, $parent_path, $zipfile, $USER->id)) {
             $return = new stdClass();
             $return->filepath = $parent_path;
             echo json_encode($return);
@@ -242,19 +246,18 @@ switch ($action) {
 
         $stored_file = $fs->get_file($user_context->id, 'user', 'draft', $draftid, $filepath, '.');
         if ($filepath === '/') {
-            $parent_path = '/';
             $filename = get_string('files').'.zip';
         } else {
-            $parent_path = $stored_file->get_parent_directory()->get_filepath();
-            $filename = trim($filepath, '/').'.zip';
+            $filename = explode('/', trim($filepath, '/'));
+            $filename = array_pop($filename) . '.zip';
         }
 
         // archive compressed file to an unused draft area
         $newdraftitemid = file_get_unused_draft_itemid();
-        if ($newfile = $zipper->archive_to_storage(array($stored_file), $user_context->id, 'user', 'draft', $newdraftitemid, '/', $filename, $USER->id)) {
+        if ($newfile = $zipper->archive_to_storage(array('/' => $stored_file), $user_context->id, 'user', 'draft', $newdraftitemid, '/', $filename, $USER->id)) {
             $return = new stdClass();
             $return->fileurl  = moodle_url::make_draftfile_url($newdraftitemid, '/', $filename)->out();
-            $return->filepath = $parent_path;
+            $return->filepath = $filepath;
             echo json_encode($return);
         } else {
             echo json_encode(false);
index 0bbc3a3..7f4dee7 100644 (file)
@@ -137,17 +137,17 @@ case 'downloaddir':
     $zipper = new zip_packer();
 
     $file = $fs->get_file($user_context->id, 'user', 'draft', $itemid, $draftpath, '.');
-    if ($file->get_parent_directory()) {
-        $parent_path = $file->get_parent_directory()->get_filepath();
-        $filename = trim($draftpath, '/').'.zip';
-    } else {
-        $parent_path = '/';
+    if ($draftpath === '/') {
         $filename = get_string('files').'.zip';
+    } else {
+        $filename = explode('/', trim($draftpath, '/'));
+        $filename = array_pop($filename) . '.zip';
     }
 
-    if ($newfile = $zipper->archive_to_storage(array($file), $user_context->id, 'user', 'draft', $itemid, $parent_path, $filename, $USER->id)) {
-        $fileurl = moodle_url::make_draftfile_url($itemid, '/', $filename)->out();
-        header('Location: ' . $fileurl );
+    $newdraftitemid = file_get_unused_draft_itemid();
+    if ($newfile = $zipper->archive_to_storage(array('/' => $file), $user_context->id, 'user', 'draft', $newdraftitemid, '/', $filename, $USER->id)) {
+        $fileurl = moodle_url::make_draftfile_url($newdraftitemid, '/', $filename)->out();
+        header('Location: ' . $fileurl);
     } else {
         print_error('cannotdownloaddir', 'repository');
     }
@@ -159,14 +159,17 @@ case 'zip':
     $file = $fs->get_file($user_context->id, 'user', 'draft', $itemid, $draftpath, '.');
     if (!$file->get_parent_directory()) {
         $parent_path = '/';
+        $filepath = '/';
         $filename = get_string('files').'.zip';
     } else {
         $parent_path = $file->get_parent_directory()->get_filepath();
         $filepath = explode('/', trim($file->get_filepath(), '/'));
-        $filename = array_pop($filepath).'.zip';
+        $filepath = array_pop($filepath);
+        $filename = $filepath.'.zip';
     }
 
-    $newfile = $zipper->archive_to_storage(array($file), $user_context->id, 'user', 'draft', $itemid, $parent_path, $filename, $USER->id);
+    $filename = repository::get_unused_filename($itemid, $parent_path, $filename);
+    $newfile = $zipper->archive_to_storage(array($filepath => $file), $user_context->id, 'user', 'draft', $itemid, $parent_path, $filename, $USER->id);
 
     $home_url->param('action', 'browse');
     $home_url->param('draftpath', $parent_path);
@@ -266,7 +269,7 @@ default:
             $path = '/' . trim($draftpath, '/') . '/';
             $parts = explode('/', $path);
             foreach ($parts as $part) {
-                if (!empty($part)) {
+                if ($part != '') {
                     $trail .= ('/'.$part.'/');
                     $data->path[] = array('name'=>$part, 'path'=>$trail);
                     $home_url->param('draftpath', $trail);
@@ -292,8 +295,10 @@ default:
             $home_url->param('action', 'mkdirform');
             echo ' <a href="'.$home_url->out().'">'.get_string('makeafolder', 'moodle').'</a>';
         }
-        $home_url->param('action', 'downloaddir');
-        echo html_writer::link($home_url, get_string('downloadfolder', 'repository'), array('target'=>'_blank'));
+        if (!empty($files->list)) {
+            $home_url->param('action', 'downloaddir');
+            echo ' ' . html_writer::link($home_url, get_string('downloadfolder', 'repository'), array('target'=>'_blank'));
+        }
     }
     echo '</div>';
 
index 54caa3b..596fc09 100644 (file)
@@ -47,6 +47,7 @@ class repository_s3 extends repository {
         $this->access_key = get_config('s3', 'access_key');
         $this->secret_key = get_config('s3', 'secret_key');
         $this->s = new S3($this->access_key, $this->secret_key);
+        $this->s->setExceptions(true);
     }
 
     /**
@@ -75,7 +76,7 @@ class repository_s3 extends repository {
     public function get_listing($path = '', $page = '') {
         global $CFG, $OUTPUT;
         if (empty($this->access_key)) {
-            die(json_encode(array('e'=>get_string('needaccesskey', 'repository_s3'))));
+            throw new moodle_exception('needaccesskey', 'repository_s3');
         }
 
         $list = array();
@@ -97,7 +98,11 @@ class repository_s3 extends repository {
         $tree = array();
 
         if (empty($path)) {
-            $buckets = $this->s->listBuckets();
+            try {
+                $buckets = $this->s->listBuckets();
+            } catch (S3Exception $e) {
+                throw new moodle_exception('errorwhilecommunicatingwith', 'repository', '', $this->get_name());
+            }
             foreach ($buckets as $bucket) {
                 $folder = array(
                     'title' => $bucket,
@@ -112,7 +117,11 @@ class repository_s3 extends repository {
             $folders = array();
             list($bucket, $uri) = $this->explode_path($path);
 
-            $contents = $this->s->getBucket($bucket, $uri, null, null, '/', true);
+            try {
+                $contents = $this->s->getBucket($bucket, $uri, null, null, '/', true);
+            } catch (S3Exception $e) {
+                throw new moodle_exception('errorwhilecommunicatingwith', 'repository', '', $this->get_name());
+            }
             foreach ($contents as $object) {
 
                 // If object has a prefix, it is a 'CommonPrefix', which we consider a folder
@@ -183,7 +192,11 @@ class repository_s3 extends repository {
     public function get_file($filepath, $file = '') {
         list($bucket, $uri) = $this->explode_path($filepath);
         $path = $this->prepare_file($file);
-        $this->s->getObject($bucket, $uri, $path);
+        try {
+            $this->s->getObject($bucket, $uri, $path);
+        } catch (S3Exception $e) {
+            throw new moodle_exception('errorwhilecommunicatingwith', 'repository', '', $this->get_name());
+        }
         return array('path' => $path);
     }
 
index 969dda3..d5b68fc 100644 (file)
@@ -35,7 +35,7 @@ if (empty($CFG->usetags)) {
     print_error('tagsaredisabled', 'tag');
 }
 
-$returnurl = optional_param('returnurl', null, PARAM_TEXT);
+$returnurl = optional_param('returnurl', null, PARAM_LOCALURL);
 $keyword = optional_param('coursetag_new_tag', '', PARAM_TEXT);
 $courseid = optional_param('entryid', 0, PARAM_INT);
 $userid = optional_param('userid', 0, PARAM_INT);
index 5508530..9314951 100644 (file)
@@ -899,7 +899,6 @@ sup {vertical-align: super;}
 
 /* Only set these options if we're showing the js container */
 .jsenabled .choosercontainer #chooseform .alloptions {
-    max-height: 550px;
     overflow-x: hidden;
     overflow-y: auto;
     max-width: 20.3em;
@@ -973,7 +972,6 @@ sup {vertical-align: super;}
     background-color: #FFFFFF;
     overflow-x: hidden;
     overflow-y: auto;
-    max-height: 550px;
     line-height: 2em;
 }
 
index 9fc9fe4..5693ab2 100644 (file)
@@ -268,6 +268,7 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .filemanager.fm-loaded .filemanager-loading {display:none;}
 .filemanager.fm-maxfiles .fp-btn-add {display:none;}
 .filemanager.fm-maxfiles .dndupload-message {display:none;}
+.filemanager.fm-noitems .fp-btn-download,
 .filemanager.fm-nofiles .fp-btn-download {display:none;}
 .filemanager .fm-empty-container {display:none;}
 .filemanager.fm-noitems .filemanager-container .fp-content {display:none;}
@@ -341,6 +342,8 @@ a.ygtvspacer:hover {color: transparent;text-decoration: none;}
 .filemanager-container.dndupload-over .dndupload-target {background:#FFFFFF;position:absolute;top:10px;bottom:10px;left:10px;right:10px;border: 2px dashed #6c8cd3;padding-top:85px;text-align:center;z-index: 3000;}
 .dndupload-message {display:none;}
 .dndsupported .dndupload-message {display:inline;}
+.dnduploadnotsupported-message {display:none;}
+.dndnotsupported .dnduploadnotsupported-message {display:inline;}
 .dndupload-target {display:none;}
 .dndsupported .dndupload-ready .dndupload-target {display:block;}
 .dndupload-uploadinprogress {display:none;text-align:center;}
index 54f7962..8035875 100644 (file)
@@ -32,7 +32,7 @@ if (isguestuser()) {
     die();
 }
 
-$returnurl = optional_param('returnurl', '', PARAM_URL);
+$returnurl = optional_param('returnurl', '', PARAM_LOCALURL);
 
 if (empty($returnurl)) {
     $returnurl = new moodle_url('/user/files.php');
index 6426111..9446751 100644 (file)
@@ -1,5 +1,6 @@
 <form id="theform" method="post" action="messageselect.php">
 <input type="hidden" name="id" value="<?php p($id) ?>" />
+<input type="hidden" name="sesskey" value="<?php echo sesskey() ?>" />
 <input type="hidden" name="returnto" value="<?php p($returnto) ?>" />
 <input type="hidden" name="deluser" value="" />
 <?php echo $OUTPUT->box_start(); ?>
index d54d26d..01de42d 100644 (file)
@@ -92,6 +92,7 @@ $messagebody = $SESSION->emailselect[$id]['messagebody'];
 $count = 0;
 
 if ($data = data_submitted()) {
+    require_sesskey();
     foreach ($data as $k => $v) {
         if (preg_match('/^(user|teacher)(\d+)$/',$k,$m)) {
             if (!array_key_exists($m[2],$SESSION->emailto[$id])) {
@@ -130,12 +131,14 @@ if ($count) {
 }
 
 if (!empty($messagebody) && !$edit && !$deluser && ($preview || $send)) {
+    require_sesskey();
     if (count($SESSION->emailto[$id])) {
         if (!empty($preview)) {
             echo '<form method="post" action="messageselect.php" style="margin: 0 20px;">
 <input type="hidden" name="returnto" value="'.s($returnto).'" />
 <input type="hidden" name="id" value="'.$id.'" />
 <input type="hidden" name="format" value="'.$format.'" />
+<input type="hidden" name="sesskey" value="' . sesskey() . '" />
 ';
             echo "<h3>".get_string('previewhtml')."</h3><div class=\"messagepreview\">\n".format_text($messagebody,$format)."\n</div>\n";
             echo '<p align="center"><input type="submit" name="send" value="'.get_string('sendmessage', 'message').'" />'."\n";
@@ -169,6 +172,7 @@ if ((!empty($send) || !empty($preview) || !empty($edit)) && (empty($messagebody)
 }
 
 if (count($SESSION->emailto[$id])) {
+    require_sesskey();
     $usehtmleditor = can_use_html_editor();
     require("message.html");
 }
index a970e61..471cb5d 100644 (file)
@@ -233,12 +233,16 @@ echo '</div>';
 
 echo '<table class="list" summary="">';
 
-//checks were performed above that ensure that if we've got to here either the user
-//is viewing their own profile ($USER->id == $user->id) or $user is enrolled in the course
+// Show email if any of the following conditions match.
+// 1. User is viewing his own profile.
+// 2. Has allowed everyone to see email
+// 3. User has allowed course members to can see email and current user is in same course
+// 4. Has either course:viewhiddenuserfields or site:viewuseridentity capability.
 if ($currentuser
-   or $user->maildisplay == 1 //allow everyone to see email address
-   or ($user->maildisplay == 2 && is_enrolled($coursecontext, $USER)) //fellow course members can see email. Already know $user is enrolled
-   or has_capability('moodle/course:useremail', $coursecontext)) {
+   or $user->maildisplay == 1
+   or ($user->maildisplay == 2 && is_enrolled($coursecontext, $USER))
+   or has_capability('moodle/course:viewhiddenuserfields', $coursecontext)
+   or has_capability('moodle/site:viewuseridentity', $coursecontext)) {
     print_row(get_string("email").":", obfuscate_mailto($user->email, ''));
 }
 
index b81d52c..f89d604 100644 (file)
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2012062503.09;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2012062504.00;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes
 
-$release  = '2.3.3+ (Build: 20121230)'; // Human-friendly version name
+$release  = '2.3.4 (Build: 20130114)';  // Human-friendly version name
 
 $branch   = '23';                       // this version's branch
 $maturity = MATURITY_STABLE;            // this version's maturity level