Merge branch 'w02_MDL-37483_m25_redirect' of git://github.com/skodak/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 14 Jan 2013 22:29:55 +0000 (23:29 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 14 Jan 2013 22:29:55 +0000 (23:29 +0100)
52 files changed:
admin/plugins.php
backup/moodle2/restore_stepslib.php
course/dndupload.js
course/dnduploadlib.php
course/edit_form.php
course/editsection_form.php
course/format/renderer.php
course/view.php
enrol/paypal/cli/sync.php [new file with mode: 0644]
enrol/paypal/lang/en/enrol_paypal.php
enrol/paypal/lib.php
enrol/paypal/settings.php
enrol/paypal/tests/paypal_test.php [new file with mode: 0644]
enrol/paypal/version.php
enrol/self/db/upgrade.php
enrol/self/edit.php
enrol/self/edit_form.php
enrol/self/lang/en/enrol_self.php
enrol/self/lib.php
enrol/self/settings.php
enrol/self/tests/self_test.php
enrol/self/version.php
filter/tex/README.mimetex
filter/tex/mimetex.darwin
filter/tex/mimetex.exe
filter/tex/mimetex.freebsd
filter/tex/mimetex.linux
filter/tex/readme_moodle.txt
group/lib.php
lang/en/cache.php
lib/db/caches.php
lib/externallib.php
lib/tests/weblib_test.php
lib/weblib.php
lib/yui/formautosubmit/formautosubmit.js
mod/assign/assignmentplugin.php
mod/assign/db/services.php
mod/assign/externallib.php
mod/assign/feedback/file/importziplib.php
mod/assign/locallib.php
mod/assign/submission/file/locallib.php
mod/assign/submission/onlinetext/locallib.php
mod/assign/tests/externallib_test.php
mod/assign/version.php
mod/book/tool/importhtml/locallib.php
mod/forum/lib.php
mod/quiz/backup/moodle2/restore_quiz_stepslib.php
mod/scorm/module.js
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
user/view.php
version.php

index 4a99a29..c6738df 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', context_system::instance());
 admin_externalpage_setup('pluginsoverview');
+require_capability('moodle/site:config', context_system::instance());
 
 $fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
 $updatesonly = optional_param('updatesonly', false, PARAM_BOOL);
index b7da2d3..c3c688f 100644 (file)
@@ -824,20 +824,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 d41bfe9..1ee8395 100644 (file)
@@ -729,6 +729,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 + ')';
@@ -916,6 +919,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 21dc7a7..26e41e8 100644 (file)
@@ -620,6 +620,9 @@ 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));
         // Rebuild the course cache after update action
         rebuild_course_cache($this->course->id, true);
@@ -627,7 +630,10 @@ class dndupload_ajax_processor {
 
         $sectionid = course_add_cm_to_section($this->course, $this->cm->id, $this->section);
 
-        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));
+        }
 
         // retrieve the final info about this module.
         $info = get_fast_modinfo($this->course);
@@ -636,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;
 
@@ -675,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 34dbe12..8f7e875 100644 (file)
@@ -252,7 +252,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 4dffac5..4c405b5 100644 (file)
@@ -71,11 +71,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();
index befde23..ceb3045 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, null);
 
-        $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');
@@ -389,22 +392,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;
     }
@@ -671,9 +690,10 @@ abstract class format_section_renderer_base extends plugin_renderer_base {
                 continue;
             }
             // 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).
index 00c6754..6f40c36 100644 (file)
     }
     add_to_log($course->id, 'course', $loglabel, "view.php?". $logparam, $infoid);
 
-    $course->format = clean_param($course->format, PARAM_ALPHA);
-    if (!file_exists($CFG->dirroot.'/course/format/'.$course->format.'/format.php')) {
-        $course->format = 'weeks';  // Default format is weeks
-    }
+    // Fix course format if it is no longer installed
+    $course->format = course_get_format($course)->get_format();
 
     $PAGE->set_pagelayout('course');
     $PAGE->set_pagetype('course-view-' . $course->format);
     $PAGE->set_other_editing_capability('moodle/course:manageactivities');
 
+    // Preload course format renderer before output starts.
+    // This is a little hacky but necessary since
+    // format.php is not included until after output starts
+    if (file_exists($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php')) {
+        require_once($CFG->dirroot.'/course/format/'.$course->format.'/renderer.php');
+        if (class_exists('format_'.$course->format.'_renderer')) {
+            // call get_renderer only if renderer is defined in format plugin
+            // otherwise an exception would be thrown
+            $PAGE->get_renderer('format_'. $course->format);
+        }
+    }
+
     if ($reset_user_allowed_editing) {
         // ugly hack
         unset($PAGE->_user_allowed_editing);
diff --git a/enrol/paypal/cli/sync.php b/enrol/paypal/cli/sync.php
new file mode 100644 (file)
index 0000000..5853e0c
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * PayPal CLI tool.
+ *
+ * Notes:
+ *   - it is required to use the web server account when executing PHP CLI scripts
+ *   - you need to change the "www-data" to match the apache user account
+ *   - use "su" if "sudo" not available
+ *
+ * @package    enrol_paypal
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('CLI_SCRIPT', true);
+
+require(__DIR__.'/../../../config.php');
+require_once("$CFG->libdir/clilib.php");
+
+// Now get cli options.
+list($options, $unrecognized) = cli_get_params(array('verbose'=>false, 'help'=>false), array('v'=>'verbose', 'h'=>'help'));
+
+if ($unrecognized) {
+    $unrecognized = implode("\n  ", $unrecognized);
+    cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
+}
+
+if ($options['help']) {
+    $help =
+        "Process PayPal expiration sync
+
+Options:
+-v, --verbose         Print verbose progress information
+-h, --help            Print out this help
+
+Example:
+\$ sudo -u www-data /usr/bin/php enrol/paypal/cli/sync.php
+";
+
+    echo $help;
+    die;
+}
+
+if (!enrol_is_enabled('paypal')) {
+    echo('enrol_paypal plugin is disabled'."\n");
+    exit(2);
+}
+
+if (empty($options['verbose'])) {
+    $trace = new null_progress_trace();
+} else {
+    $trace = new text_progress_trace();
+}
+
+/** @var $plugin enrol_paypal_plugin */
+$plugin = enrol_get_plugin('paypal');
+
+$result = $plugin->sync($trace);
+
+exit($result);
index 803d560..29b8404 100644 (file)
@@ -41,6 +41,8 @@ $string['enrolperiod_desc'] = 'Default length of time that the enrolment is vali
 $string['enrolperiod_help'] = 'Length of time that the enrolment is valid, starting with the moment the user is enrolled. If disabled, the enrolment duration will be unlimited.';
 $string['enrolstartdate'] = 'Start date';
 $string['enrolstartdate_help'] = 'If enabled, users can be enrolled from this date onward only.';
+$string['expiredaction'] = 'Enrolment expiration action';
+$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
 $string['mailadmins'] = 'Notify admin';
 $string['mailstudents'] = 'Notify students';
 $string['mailteachers'] = 'Notify teachers';
index 7d49d05..d9d5124 100644 (file)
@@ -283,4 +283,125 @@ class enrol_paypal_plugin extends enrol_plugin {
         }
         return $actions;
     }
+
+    public function cron() {
+        $trace = new text_progress_trace();
+        $this->process_expirations($trace);
+    }
+
+    /**
+     * Execute synchronisation.
+     * @param progress_trace $trace
+     * @return int exit code, 0 means ok
+     */
+    public function sync(progress_trace $trace) {
+        $this->process_expirations($trace);
+        return 0;
+    }
+
+    /**
+     * Do any enrolment expiration processing.
+     *
+     * @param progress_trace $trace
+     * @return bool true if any data processed, false if not
+     */
+    protected function process_expirations(progress_trace $trace) {
+        global $DB;
+
+        //TODO: this method should be moved to parent class once we refactor all existing enrols, see MDL-36504.
+
+        $processed = false;
+        $name = $this->get_name();
+
+        // Deal with expired accounts.
+        $action = $this->get_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
+
+        if ($action == ENROL_EXT_REMOVED_UNENROL) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now";
+            $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'enrol'=>$name);
+
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (!$processed) {
+                    $trace->output("Starting processing of enrol_$name expirations...");
+                    $processed = true;
+                }
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+                if (!$this->roles_protected()) {
+                    // Let's just guess what extra roles are supposed to be removed.
+                    if ($instance->roleid) {
+                        role_unassign($instance->roleid, $ue->userid, $ue->contextid);
+                    }
+                }
+                // The unenrol cleans up all subcontexts if this is the only course enrolment for this user.
+                $this->unenrol_user($instance, $ue->userid);
+                $trace->output("Unenrolling expired user $ue->userid from course $instance->courseid", 1);
+            }
+            $rs->close();
+            unset($instances);
+
+        } else if ($action == ENROL_EXT_REMOVED_SUSPENDNOROLES) {
+            $instances = array();
+            $sql = "SELECT ue.*, e.courseid, c.id AS contextid
+                      FROM {user_enrolments} ue
+                      JOIN {enrol} e ON (e.id = ue.enrolid AND e.enrol = :enrol)
+                      JOIN {context} c ON (c.instanceid = e.courseid AND c.contextlevel = :courselevel)
+                     WHERE ue.timeend > 0 AND ue.timeend < :now
+                           AND ue.status = :useractive";
+            $params = array('now'=>time(), 'courselevel'=>CONTEXT_COURSE, 'useractive'=>ENROL_USER_ACTIVE, 'enrol'=>$name);
+            $rs = $DB->get_recordset_sql($sql, $params);
+            foreach ($rs as $ue) {
+                if (!$processed) {
+                    $trace->output("Starting processing of enrol_$name expirations...");
+                    $processed = true;
+                }
+                if (empty($instances[$ue->enrolid])) {
+                    $instances[$ue->enrolid] = $DB->get_record('enrol', array('id'=>$ue->enrolid));
+                }
+                $instance = $instances[$ue->enrolid];
+
+                if (!$this->roles_protected()) {
+                    // Let's just guess what roles should be removed.
+                    $count = $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid));
+                    if ($count == 1) {
+                        role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0));
+
+                    } else if ($count > 1 and $instance->roleid) {
+                        role_unassign($instance->roleid, $ue->userid, $ue->contextid, '', 0);
+                    }
+                }
+                // In any case remove all roles that belong to this instance and user.
+                role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'enrol_'.$name, 'itemid'=>$instance->id), true);
+                // Final cleanup of subcontexts if there are no more course roles.
+                if (0 == $DB->count_records('role_assignments', array('userid'=>$ue->userid, 'contextid'=>$ue->contextid))) {
+                    role_unassign_all(array('userid'=>$ue->userid, 'contextid'=>$ue->contextid, 'component'=>'', 'itemid'=>0), true);
+                }
+
+                $this->update_user_enrol($instance, $ue->userid, ENROL_USER_SUSPENDED);
+                $trace->output("Suspending expired user $ue->userid in course $instance->courseid", 1);
+            }
+            $rs->close();
+            unset($instances);
+
+        } else {
+            // ENROL_EXT_REMOVED_KEEP means no changes.
+        }
+
+        if ($processed) {
+            $trace->output("...finished processing of enrol_$name expirations");
+        } else {
+            $trace->output("No expired enrol_$name enrolments detected");
+        }
+        $trace->finished();
+
+        return $processed;
+    }
 }
index 9b3c14d..aa026e0 100644 (file)
@@ -40,6 +40,15 @@ if ($ADMIN->fulltree) {
 
     $settings->add(new admin_setting_configcheckbox('enrol_paypal/mailadmins', get_string('mailadmins', 'enrol_paypal'), '', 0));
 
+    // Note: let's reuse the ext sync constants and strings here, internally it is very similar,
+    //       it describes what should happen when users are not supposed to be enrolled any more.
+    $options = array(
+        ENROL_EXT_REMOVED_KEEP           => get_string('extremovedkeep', 'enrol'),
+        ENROL_EXT_REMOVED_SUSPENDNOROLES => get_string('extremovedsuspendnoroles', 'enrol'),
+        ENROL_EXT_REMOVED_UNENROL        => get_string('extremovedunenrol', 'enrol'),
+    );
+    $settings->add(new admin_setting_configselect('enrol_paypal/expiredaction', get_string('expiredaction', 'enrol_paypal'), get_string('expiredaction_help', 'enrol_paypal'), ENROL_EXT_REMOVED_SUSPENDNOROLES, $options));
+
     //--- enrol instance defaults ----------------------------------------------------------------------------
     $settings->add(new admin_setting_heading('enrol_paypal_defaults',
         get_string('enrolinstancedefaults', 'admin'), get_string('enrolinstancedefaults_desc', 'admin')));
diff --git a/enrol/paypal/tests/paypal_test.php b/enrol/paypal/tests/paypal_test.php
new file mode 100644 (file)
index 0000000..d31b75b
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * paypal enrolment plugin tests.
+ *
+ * @package    enrol_paypal
+ * @category   phpunit
+ * @copyright  2012 Petr Skoda {@link http://skodak.org}
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+
+class enrol_paypal_testcase extends advanced_testcase {
+
+    protected function enable_plugin() {
+        $enabled = enrol_get_plugins(true);
+        $enabled['paypal'] = true;
+        $enabled = array_keys($enabled);
+        set_config('enrol_plugins_enabled', implode(',', $enabled));
+    }
+
+    protected function disable_plugin() {
+        $enabled = enrol_get_plugins(true);
+        unset($enabled['paypal']);
+        $enabled = array_keys($enabled);
+        set_config('enrol_plugins_enabled', implode(',', $enabled));
+    }
+
+    public function test_basics() {
+        $this->assertFalse(enrol_is_enabled('paypal'));
+        $plugin = enrol_get_plugin('paypal');
+        $this->assertInstanceOf('enrol_paypal_plugin', $plugin);
+        $this->assertEquals(ENROL_EXT_REMOVED_SUSPENDNOROLES, get_config('enrol_paypal', 'expiredaction'));
+    }
+
+    public function test_sync_nothing() {
+        $this->resetAfterTest();
+
+        $this->enable_plugin();
+        $paypalplugin = enrol_get_plugin('paypal');
+
+        // Just make sure the sync does not throw any errors when nothing to do.
+        $paypalplugin->sync(new null_progress_trace());
+    }
+
+    public function test_expired() {
+        global $DB;
+        $this->resetAfterTest();
+
+        /** @var enrol_paypal_plugin $paypalplugin  */
+        $paypalplugin = enrol_get_plugin('paypal');
+        /** @var enrol_manual_plugin $manualplugin  */
+        $manualplugin = enrol_get_plugin('manual');
+        $this->assertNotEmpty($manualplugin);
+
+        $now = time();
+        $trace = new null_progress_trace();
+        $this->enable_plugin();
+
+
+        // Prepare some data.
+
+        $studentrole = $DB->get_record('role', array('shortname'=>'student'));
+        $this->assertNotEmpty($studentrole);
+        $teacherrole = $DB->get_record('role', array('shortname'=>'teacher'));
+        $this->assertNotEmpty($teacherrole);
+        $managerrole = $DB->get_record('role', array('shortname'=>'manager'));
+        $this->assertNotEmpty($managerrole);
+
+        $user1 = $this->getDataGenerator()->create_user();
+        $user2 = $this->getDataGenerator()->create_user();
+        $user3 = $this->getDataGenerator()->create_user();
+        $user4 = $this->getDataGenerator()->create_user();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $context1 = context_course::instance($course1->id);
+        $context2 = context_course::instance($course2->id);
+
+        $data = array('roleid'=>$studentrole->id, 'courseid'=>$course1->id);
+        $id = $paypalplugin->add_instance($course1, $data);
+        $instance1  = $DB->get_record('enrol', array('id'=>$id));
+        $data = array('roleid'=>$studentrole->id, 'courseid'=>$course2->id);
+        $id = $paypalplugin->add_instance($course2, $data);
+        $instance2 = $DB->get_record('enrol', array('id'=>$id));
+        $data = array('roleid'=>$teacherrole->id, 'courseid'=>$course2->id);
+        $id = $paypalplugin->add_instance($course2, $data);
+        $instance3 = $DB->get_record('enrol', array('id'=>$id));
+
+        $maninstance1 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'manual'), '*', MUST_EXIST);
+
+        $manualplugin->enrol_user($maninstance1, $user3->id, $studentrole->id);
+
+        $this->assertEquals(1, $DB->count_records('user_enrolments'));
+        $this->assertEquals(1, $DB->count_records('role_assignments'));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+
+        $paypalplugin->enrol_user($instance1, $user1->id, $studentrole->id);
+        $paypalplugin->enrol_user($instance1, $user2->id, $studentrole->id);
+        $paypalplugin->enrol_user($instance1, $user3->id, $studentrole->id, 0, $now-60);
+
+        $paypalplugin->enrol_user($instance2, $user1->id, $studentrole->id, 0, 0);
+        $paypalplugin->enrol_user($instance2, $user2->id, $studentrole->id, 0, $now-60*60);
+        $paypalplugin->enrol_user($instance2, $user3->id, $studentrole->id, 0, $now+60*60);
+
+        $paypalplugin->enrol_user($instance3, $user1->id, $teacherrole->id, $now-60*60*24*7, $now-60);
+        $paypalplugin->enrol_user($instance3, $user4->id, $teacherrole->id);
+
+        role_assign($managerrole->id, $user3->id, $context1->id);
+
+        $this->assertEquals(9, $DB->count_records('user_enrolments'));
+        $this->assertEquals(9, $DB->count_records('role_assignments'));
+        $this->assertEquals(6, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$managerrole->id)));
+
+        // Execute tests.
+
+        $paypalplugin->set_config('expiredaction', ENROL_EXT_REMOVED_KEEP);
+        $code = $paypalplugin->sync($trace);
+        $this->assertSame(0, $code);
+        $this->assertEquals(9, $DB->count_records('user_enrolments'));
+        $this->assertEquals(9, $DB->count_records('role_assignments'));
+
+
+        $paypalplugin->set_config('expiredaction', ENROL_EXT_REMOVED_SUSPENDNOROLES);
+        $paypalplugin->sync($trace);
+        $this->assertEquals(9, $DB->count_records('user_enrolments'));
+        $this->assertEquals(6, $DB->count_records('role_assignments'));
+        $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context1->id, 'userid'=>$user3->id, 'roleid'=>$studentrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context2->id, 'userid'=>$user2->id, 'roleid'=>$studentrole->id)));
+        $this->assertFalse($DB->record_exists('role_assignments', array('contextid'=>$context2->id, 'userid'=>$user1->id, 'roleid'=>$teacherrole->id)));
+        $this->assertTrue($DB->record_exists('role_assignments', array('contextid'=>$context2->id, 'userid'=>$user1->id, 'roleid'=>$studentrole->id)));
+
+
+        $paypalplugin->set_config('expiredaction', ENROL_EXT_REMOVED_UNENROL);
+        role_assign($studentrole->id, $user3->id, $context1->id);
+        role_assign($studentrole->id, $user2->id, $context2->id);
+        role_assign($teacherrole->id, $user1->id, $context2->id);
+        $this->assertEquals(9, $DB->count_records('user_enrolments'));
+        $this->assertEquals(9, $DB->count_records('role_assignments'));
+        $this->assertEquals(6, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(2, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+        $paypalplugin->sync($trace);
+        $this->assertEquals(6, $DB->count_records('user_enrolments'));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance1->id, 'userid'=>$user3->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance2->id, 'userid'=>$user2->id)));
+        $this->assertFalse($DB->record_exists('user_enrolments', array('enrolid'=>$instance3->id, 'userid'=>$user1->id)));
+        $this->assertEquals(5, $DB->count_records('role_assignments'));
+        $this->assertEquals(4, $DB->count_records('role_assignments', array('roleid'=>$studentrole->id)));
+        $this->assertEquals(1, $DB->count_records('role_assignments', array('roleid'=>$teacherrole->id)));
+    }
+}
index b9b64c9..d0c4b7f 100644 (file)
@@ -26,6 +26,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012122300;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900;        // Requires this Moodle version
 $plugin->component = 'enrol_paypal';    // Full name of the plugin (used for diagnostics)
+$plugin->cron      = 60;
index fda0d85..625ba82 100644 (file)
@@ -38,6 +38,12 @@ function xmldb_enrol_self_upgrade($oldversion) {
         upgrade_plugin_savepoint(true, 2012101400, 'enrol', 'self');
     }
 
+    if ($oldversion < 2012120600) {
+        // Enable new self enrolments everywhere.
+        $DB->execute("UPDATE {enrol} SET customint6 = 1 WHERE enrol = 'self'");
+        upgrade_plugin_savepoint(true, 2012120600, 'enrol', 'self');
+    }
+
 
     // Moodle v2.4.0 release upgrade line
     // Put any upgrade step following this
index e683d18..a339b8e 100644 (file)
@@ -82,6 +82,11 @@ if ($mform->is_cancelled()) {
         // Keep previous/default value of disabled expirythreshold option.
         $data->expirythreshold = $instance->expirythreshold;
     }
+    if (!isset($data->customint6)) {
+        // Add previous value of newenrols if disabled.
+        $data->customint6 = $instance->customint6;
+    }
+
     if ($instance->id) {
         $reset = ($instance->status != $data->status);
 
@@ -93,6 +98,7 @@ if ($mform->is_cancelled()) {
         $instance->customint3     = $data->customint3;
         $instance->customint4     = $data->customint4;
         $instance->customint5     = $data->customint5;
+        $instance->customint6     = $data->customint6;
         $instance->customtext1    = $data->customtext1;
         $instance->roleid         = $data->roleid;
         $instance->enrolperiod    = $data->enrolperiod;
@@ -118,6 +124,7 @@ if ($mform->is_cancelled()) {
             'customint3'      => $data->customint3,
             'customint4'      => $data->customint4,
             'customint5'      => $data->customint5,
+            'customint6'      => $data->customint6,
             'customtext1'     => $data->customtext1,
             'roleid'          => $data->roleid,
             'enrolperiod'     => $data->enrolperiod,
index 94287df..04bc13d 100644 (file)
@@ -45,6 +45,11 @@ class enrol_self_edit_form extends moodleform {
         $mform->addElement('select', 'status', get_string('status', 'enrol_self'), $options);
         $mform->addHelpButton('status', 'status', 'enrol_self');
 
+        $options = array(1 => get_string('yes'), 0 => get_string('no'));
+        $mform->addElement('select', 'customint6', get_string('newenrols', 'enrol_self'), $options);
+        $mform->addHelpButton('customint6', 'newenrols', 'enrol_self');
+        $mform->disabledIf('customint6', 'status', 'eq', ENROL_INSTANCE_DISABLED);
+
         $mform->addElement('passwordunmask', 'password', get_string('password', 'enrol_self'));
         $mform->addHelpButton('password', 'password', 'enrol_self');
         if (empty($instance->id) and $plugin->get_config('requirepassword')) {
index 10e41cc..92c7aa4 100644 (file)
@@ -69,6 +69,9 @@ $string['maxenrolled'] = 'Max enrolled users';
 $string['maxenrolled_help'] = 'Specifies the maximum number of users that can self enrol. 0 means no limit.';
 $string['maxenrolledreached'] = 'Maximum number of users allowed to self-enrol was already reached.';
 $string['messageprovider:expiry_notification'] = 'Self enrolment expiry notifications';
+$string['newenrols'] = 'Allow new enrolments';
+$string['newenrols_desc'] = 'Allow users to self enrol into new courses by default.';
+$string['newenrols_help'] = 'This setting determines whether a user can enrol into this course.';
 $string['nopassword'] = 'No enrolment key required.';
 $string['password'] = 'Enrolment key';
 $string['password_help'] = 'An enrolment key enables access to the course to be restricted to only those who know the key.
@@ -92,9 +95,9 @@ $string['sendcoursewelcomemessage'] = 'Send course welcome message';
 $string['sendcoursewelcomemessage_help'] = 'If enabled, users receive a welcome message via email when they self-enrol in a course.';
 $string['showhint'] = 'Show hint';
 $string['showhint_desc'] = 'Show first letter of the guest access key.';
-$string['status'] = 'Allow self enrolments';
-$string['status_desc'] = 'Allow users to self enrol into course by default.';
-$string['status_help'] = 'This setting determines whether a user can enrol (and also unenrol if they have the appropriate permission) themselves from the course.';
+$string['status'] = 'Enable existing enrolments';
+$string['status_desc'] = 'Enable self enrolment method in new courses.';
+$string['status_help'] = 'If disabled all existing self enrolments are suspended and new users can not enrol.';
 $string['unenrol'] = 'Unenrol user';
 $string['unenrolselfconfirm'] = 'Do you really want to unenrol yourself from course "{$a}"?';
 $string['unenroluser'] = 'Do you really want to unenrol "{$a->user}" from course "{$a->course}"?';
index 14930b9..0ab4f7c 100644 (file)
@@ -48,6 +48,10 @@ class enrol_self_plugin extends enrol_plugin {
         $key = false;
         $nokey = false;
         foreach ($instances as $instance) {
+            if (!$instance->customint6) {
+                // New enrols not allowed.
+                continue;
+            }
             if ($instance->password or $instance->customint1) {
                 $key = true;
             } else {
@@ -107,6 +111,12 @@ class enrol_self_plugin extends enrol_plugin {
         if ($instance->status != ENROL_INSTANCE_ENABLED) {
             return false;
         }
+
+        if (!$instance->customint6) {
+            // New enrols not allowed.
+            return false;
+        }
+
         if ($instance->customint5) {
             require_once("$CFG->dirroot/cohort/lib.php");
             return cohort_is_member($instance->customint5, $USER->id);
@@ -200,6 +210,11 @@ class enrol_self_plugin extends enrol_plugin {
             return null;
         }
 
+        if (!$instance->customint6) {
+            // New enrols not allowed.
+            return null;
+        }
+
         if ($instance->customint5) {
             require_once("$CFG->dirroot/cohort/lib.php");
             if (!cohort_is_member($instance->customint5, $USER->id)) {
@@ -298,6 +313,7 @@ class enrol_self_plugin extends enrol_plugin {
         $fields['customint3']      = $this->get_config('maxenrolled');
         $fields['customint4']      = $this->get_config('sendcoursewelcomemessage');
         $fields['customint5']      = 0;
+        $fields['customint6']      = $this->get_config('newenrols');
 
         return $fields;
     }
index 8401283..497c27e 100644 (file)
@@ -65,6 +65,10 @@ if ($ADMIN->fulltree) {
     $settings->add(new admin_setting_configselect('enrol_self/status',
         get_string('status', 'enrol_self'), get_string('status_desc', 'enrol_self'), ENROL_INSTANCE_DISABLED, $options));
 
+    $options = array(1  => get_string('yes'), 0 => get_string('no'));
+    $settings->add(new admin_setting_configselect('enrol_self/newenrols',
+        get_string('newenrols', 'enrol_self'), get_string('newenrols_desc', 'enrol_self'), 1, $options));
+
     $options = array(1  => get_string('yes'),
                      0 => get_string('no'));
     $settings->add(new admin_setting_configselect('enrol_self/groupkey',
index a8d5c5a..d395963 100644 (file)
@@ -442,4 +442,69 @@ class enrol_self_testcase extends advanced_testcase {
         $selfplugin->send_expiry_notifications($trace);
         $this->assertEquals(6, $sink->count());
     }
+
+    public function test_show_enrolme_link() {
+        global $DB, $CFG;
+        $this->resetAfterTest();
+        $this->preventResetByRollback(); // Messaging does not like transactions...
+
+        /** @var $selfplugin enrol_self_plugin */
+        $selfplugin = enrol_get_plugin('self');
+
+        $user1 = $this->getDataGenerator()->create_user();
+
+        $course1 = $this->getDataGenerator()->create_course();
+        $course2 = $this->getDataGenerator()->create_course();
+        $course3 = $this->getDataGenerator()->create_course();
+        $course4 = $this->getDataGenerator()->create_course();
+        $course5 = $this->getDataGenerator()->create_course();
+
+        $cohort1 = $this->getDataGenerator()->create_cohort();
+        $cohort2 = $this->getDataGenerator()->create_cohort();
+
+        $instance1 = $DB->get_record('enrol', array('courseid'=>$course1->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance1->customint6 = 1;
+        $DB->update_record('enrol', $instance1);
+        $selfplugin->update_status($instance1, ENROL_INSTANCE_ENABLED);
+
+        $instance2 = $DB->get_record('enrol', array('courseid'=>$course2->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance2->customint6 = 0;
+        $DB->update_record('enrol', $instance2);
+        $selfplugin->update_status($instance2, ENROL_INSTANCE_ENABLED);
+
+        $instance3 = $DB->get_record('enrol', array('courseid'=>$course3->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance3->customint6 = 1;
+        $DB->update_record('enrol', $instance3);
+        $selfplugin->update_status($instance3, ENROL_INSTANCE_DISABLED);
+
+        $instance4 = $DB->get_record('enrol', array('courseid'=>$course4->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance4->customint6 = 0;
+        $DB->update_record('enrol', $instance4);
+        $selfplugin->update_status($instance4, ENROL_INSTANCE_DISABLED);
+
+        $instance5 = $DB->get_record('enrol', array('courseid'=>$course5->id, 'enrol'=>'self'), '*', MUST_EXIST);
+        $instance5->customint6 = 1;
+        $instance5->customint5 = $cohort1->id;
+        $DB->update_record('enrol', $instance1);
+        $selfplugin->update_status($instance5, ENROL_INSTANCE_ENABLED);
+
+        $id = $selfplugin->add_instance($course5, $selfplugin->get_instance_defaults());
+        $instance6 = $DB->get_record('enrol', array('id'=>$id), '*', MUST_EXIST);
+        $instance6->customint6 = 1;
+        $instance6->customint5 = $cohort2->id;
+        $DB->update_record('enrol', $instance1);
+        $selfplugin->update_status($instance6, ENROL_INSTANCE_ENABLED);
+
+        $this->setUser($user1);
+        $this->assertTrue($selfplugin->show_enrolme_link($instance1));
+        $this->assertFalse($selfplugin->show_enrolme_link($instance2));
+        $this->assertFalse($selfplugin->show_enrolme_link($instance3));
+        $this->assertFalse($selfplugin->show_enrolme_link($instance4));
+
+        require_once("$CFG->dirroot/cohort/lib.php");
+        cohort_add_member($cohort1->id, $user1->id);
+
+        $this->assertTrue($selfplugin->show_enrolme_link($instance5));
+        $this->assertFalse($selfplugin->show_enrolme_link($instance6));
+    }
 }
index 722b1ca..3eb1046 100644 (file)
@@ -24,7 +24,7 @@
 
 defined('MOODLE_INTERNAL') || die();
 
-$plugin->version   = 2012112900;        // The current plugin version (Date: YYYYMMDDXX)
+$plugin->version   = 2012120600;        // The current plugin version (Date: YYYYMMDDXX)
 $plugin->requires  = 2012112900;        // Requires this Moodle version
 $plugin->component = 'enrol_self';      // Full name of the plugin (used for diagnostics)
 $plugin->cron      = 600;
index dd94231..dbfc250 100644 (file)
@@ -1,10 +1,10 @@
 
  --------------------------------------------------------------------------
- September 6, 2008                                             Version 1.70
+ March 31, 2012                                                Version 1.74
 
                   m i m e T e X   R e a d m e   F i l e
 
- Copyright(c) 2002-2008, John Forkosh Associates, Inc. All rights reserved.
+ Copyright(c) 2002-2012, John Forkosh Associates, Inc. All rights reserved.
  --------------------------------------------------------------------------
 
                             by: John Forkosh
@@ -179,6 +179,8 @@ IV.  REVISION HISTORY
   A more detailed account of mimeTeX's revision history
   is maintained at  http://www.forkosh.com/mimetexchangelog.html
   ---
+  03/31/12  J.Forkosh      version 1.74 released.
+  08/24/11  J.Forkosh      version 1.72 released.
   09/06/08  J.Forkosh      version 1.70 released.
   11/30/04  J.Forkosh      version 1.60 released
   10/02/04  J.Forkosh      version 1.50 released on CTAN with various new
index 9ef899c..79d2747 100755 (executable)
Binary files a/filter/tex/mimetex.darwin and b/filter/tex/mimetex.darwin differ
index cb61ebe..e9f8096 100644 (file)
Binary files a/filter/tex/mimetex.exe and b/filter/tex/mimetex.exe differ
index 5fa4b6a..1538cd3 100755 (executable)
Binary files a/filter/tex/mimetex.freebsd and b/filter/tex/mimetex.freebsd differ
index 50d58b8..e7e8650 100755 (executable)
Binary files a/filter/tex/mimetex.linux and b/filter/tex/mimetex.linux differ
index fe516a7..9b6ae9c 100644 (file)
@@ -1,25 +1,31 @@
-Description of mimeTeX v 1.70 import into Moodle
+Description of mimeTeX v 1.74 import into Moodle
 
 Compiling mimeTeX:
 
-===Windows===
+Windows
+=======
 1/ get "Automated MinGW Installer" from https://sourceforge.net/projects/mingw/files/
 2/ install mingw
 3/ go into directory with extracted source files
-4/ execute "c:\mingw\bin\gcc -DAA -DWINDOWS mimetex.c gifsave.c -lm -o mimetex.exe"
+4/ execute "set path=%path%;c:\mingw\bin"
+5/ execute "c:\mingw\bin\gcc -DAA -DWINDOWS mimetex.c gifsave.c -lm -o mimetex.exe"
 
-===Linux===
+Linux
+=====
 1/ install gcc
 2/ go into directory with extracted source files
 3/ execute "cc -DAA mimetex.c gifsave.c -lm -o mimetex.linux"
 
-===FreeBSD===
+FreeBSD
+=======
 1/ go into directory with extracted source files
 2/ execute "cc -DAA mimetex.c gifsave.c -lm -o mimetex.freebsd"
 
-===Apple OSX===
-1/ install gcc
+Apple OSX
+=========
+1/ install XCode and command line tools
 2/ go into directory with extracted source files
-3/ execute "cc -DAA mimetex.c gifsave.c -lm -o mimetex.darwin"
+3/ execute "cc -DAA -arch i386 -arch x86_64 -mmacosx-version-min=10.5 mimetex.c gifsave.c -lm -o mimetex.darwin"
+
 
-skodak
\ No newline at end of file
+Petr Skoda
\ No newline at end of file
index a3f8fac..32d3cf0 100644 (file)
@@ -713,9 +713,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))) {
@@ -724,7 +725,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 6a42f24..371d7cb 100644 (file)
@@ -36,6 +36,7 @@ $string['cacheadmin'] = 'Cache administration';
 $string['cacheconfig'] = 'Configuration';
 $string['cachedef_databasemeta'] = 'Database meta information';
 $string['cachedef_eventinvalidation'] = 'Event invalidation';
+$string['cachedef_htmlpurifier'] = 'HTML Purifier - cleaned content';
 $string['cachedef_locking'] = 'Locking';
 $string['cachedef_questiondata'] = 'Question definitions';
 $string['cachedef_string'] = 'Language string cache';
index 6c3ebe0..44e7e37 100644 (file)
@@ -75,4 +75,12 @@ $definitions = array(
         'datasource' => 'question_finder',
         'datasourcefile' => 'question/engine/bank.php',
     ),
+
+    // HTML Purifier cache
+    // This caches the html purifier cleaned text. This is done because the text is usually cleaned once for every user
+    // and context combo. Text caching handles caching for the combonation, this cache is responsible for caching the
+    // cleaned text which is shareable.
+    'htmlpurifier' => array(
+        'mode' => cache_store::MODE_APPLICATION,
+    )
 );
index 0c6e44d..abde8a2 100644 (file)
@@ -598,15 +598,15 @@ class external_warnings extends external_multiple_structure {
      *
      * @since Moodle 2.3
      */
-    public function __construct() {
+    public function __construct($itemdesc = 'item', $itemiddesc = 'item id',
+        $warningcodedesc = 'the warning code can be used by the client app to implement specific behaviour') {
 
         parent::__construct(
             new external_single_structure(
                 array(
-                    'item' => new external_value(PARAM_TEXT, 'item', VALUE_OPTIONAL),
-                    'itemid' => new external_value(PARAM_INT, 'item id', VALUE_OPTIONAL),
-                    'warningcode' => new external_value(PARAM_ALPHANUM,
-                            'the warning code can be used by the client app to implement specific behaviour'),
+                    'item' => new external_value(PARAM_TEXT, $itemdesc, VALUE_OPTIONAL),
+                    'itemid' => new external_value(PARAM_INT, $itemiddesc, VALUE_OPTIONAL),
+                    'warningcode' => new external_value(PARAM_ALPHANUM, $warningcodedesc),
                     'message' => new external_value(PARAM_TEXT,
                             'untranslated english message to explain the warning')
                 ), 'warning'),
index c5a75c8..f7f2fca 100644 (file)
@@ -212,4 +212,92 @@ class web_testcase extends advanced_testcase {
         $PAGE->set_url('/course/view.php', array('id'=>1));
         $this->assertEquals($CFG->wwwroot.'/course/view.php?id=1', qualified_me());
     }
+
+    public function test_null_progres_trace() {
+        $this->resetAfterTest(false);
+
+        $trace = new null_progress_trace();
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $output = ob_get_contents();
+        $this->assertSame('', $output);
+        $this->expectOutputString('');
+    }
+
+    public function test_text_progres_trace() {
+        $this->resetAfterTest(false);
+
+        $trace = new text_progress_trace();
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $this->expectOutputString("do\n  re\n    mi\n");
+    }
+
+    public function test_html_progres_trace() {
+        $this->resetAfterTest(false);
+
+        $trace = new html_progress_trace();
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $this->expectOutputString("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n");
+    }
+
+    public function test_html_list_progress_trace() {
+        $this->resetAfterTest(false);
+
+        $trace = new html_list_progress_trace();
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $this->expectOutputString("<ul>\n<li>do<ul>\n<li>re<ul>\n<li>mi</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n");
+    }
+
+    public function test_progres_trace_buffer() {
+        $this->resetAfterTest(false);
+
+        $trace = new progress_trace_buffer(new html_progress_trace());
+        ob_start();
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $output = ob_get_contents();
+        ob_end_clean();
+        $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $output);
+        $this->assertSame($output, $trace->get_buffer());
+
+        $trace = new progress_trace_buffer(new html_progress_trace(), false);
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace->get_buffer());
+        $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace->get_buffer());
+        $trace->reset_buffer();
+        $this->assertSame('', $trace->get_buffer());
+        $this->expectOutputString('');
+    }
+
+    public function test_combined_progres_trace() {
+        $this->resetAfterTest(false);
+
+        $trace1 = new progress_trace_buffer(new html_progress_trace(), false);
+        $trace2 = new progress_trace_buffer(new text_progress_trace(), false);
+
+        $trace = new combined_progress_trace(array($trace1, $trace2));
+        $trace->output('do');
+        $trace->output('re', 1);
+        $trace->output('mi', 2);
+        $trace->finished();
+        $this->assertSame("<p>do</p>\n<p>&#160;&#160;re</p>\n<p>&#160;&#160;&#160;&#160;mi</p>\n", $trace1->get_buffer());
+        $this->assertSame("do\n  re\n    mi\n", $trace2->get_buffer());
+        $this->expectOutputString('');
+    }
 }
index c943f9e..9b93a3a 100644 (file)
@@ -1578,8 +1578,21 @@ function is_purify_html_necessary($text) {
 function purify_html($text, $options = array()) {
     global $CFG;
 
-    $type = !empty($options['allowid']) ? 'allowid' : 'normal';
     static $purifiers = array();
+    static $caches = array();
+
+    $type = !empty($options['allowid']) ? 'allowid' : 'normal';
+
+    if (!array_key_exists($type, $caches)) {
+        $caches[$type] = cache::make('core', 'htmlpurifier', array('type' => $type));
+    }
+    $cache = $caches[$type];
+
+    $filteredtext = $cache->get($text);
+    if ($filteredtext !== false) {
+        return $filteredtext;
+    }
+
     if (empty($purifiers[$type])) {
 
         // make sure the serializer dir exists, it should be fine if it disappears later during cache reset
@@ -1627,15 +1640,17 @@ function purify_html($text, $options = array()) {
 
     $multilang = (strpos($text, 'class="multilang"') !== false);
 
+    $filteredtext = $text;
     if ($multilang) {
-        $text = preg_replace('/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/', '<span xxxlang="${2}">', $text);
+        $filteredtext = preg_replace('/<span(\s+lang="([a-zA-Z0-9_-]+)"|\s+class="multilang"){2}\s*>/', '<span xxxlang="${2}">', $filteredtext);
     }
-    $text = $purifier->purify($text);
+    $filteredtext = $purifier->purify($filteredtext);
     if ($multilang) {
-        $text = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="${1}" class="multilang">', $text);
+        $filteredtext = preg_replace('/<span xxxlang="([a-zA-Z0-9_-]+)">/', '<span lang="${1}" class="multilang">', $filteredtext);
     }
+    $cache->set($text, $filteredtext);
 
-    return $text;
+    return $filteredtext;
 }
 
 /**
@@ -3144,7 +3159,8 @@ EOT;
  */
 abstract class progress_trace {
     /**
-     * Ouput an progress message in whatever format.
+     * Output an progress message in whatever format.
+     *
      * @param string $message the message to output.
      * @param integer $depth indent depth for this message.
      */
@@ -3183,7 +3199,7 @@ class null_progress_trace extends progress_trace {
  */
 class text_progress_trace extends progress_trace {
     /**
-     * Output the trace message
+     * Output the trace message.
      *
      * @param string $message
      * @param int $depth
@@ -3203,7 +3219,7 @@ class text_progress_trace extends progress_trace {
  */
 class html_progress_trace extends progress_trace {
     /**
-     * Output the trace message
+     * Output the trace message.
      *
      * @param string $message
      * @param int $depth
@@ -3265,6 +3281,150 @@ class html_list_progress_trace extends progress_trace {
     }
 }
 
+/**
+ * This subclass of progress_trace outputs to error log.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package moodlecore
+ */
+class error_log_progress_trace extends progress_trace {
+    /** @var string log prefix */
+    protected $prefix;
+
+    /**
+     * Constructor.
+     * @param string $prefix optional log prefix
+     */
+    public function __construct($prefix = '') {
+        $this->prefix = $prefix;
+    }
+
+    /**
+     * Output the trace message.
+     *
+     * @param string $message
+     * @param int $depth
+     * @return void Output is sent to error log.
+     */
+    public function output($message, $depth = 0) {
+        error_log($this->prefix . str_repeat('  ', $depth) . $message);
+    }
+}
+
+/**
+ * Special type of trace that can be used for catching of
+ * output of other traces.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package moodlecore
+ */
+class progress_trace_buffer extends progress_trace {
+    /** @var progres_trace */
+    protected $trace;
+    /** @var bool do we pass output out */
+    protected $passthrough;
+    /** @var string output buffer */
+    protected $buffer;
+
+    /**
+     * Constructor.
+     *
+     * @param progress_trace $trace
+     * @param bool $passthrough true means output and buffer, false means just buffer and no output
+     */
+    public function __construct(progress_trace $trace, $passthrough = true) {
+        $this->trace       = $trace;
+        $this->passthrough = $passthrough;
+        $this->buffer      = '';
+    }
+
+    /**
+     * Output the trace message.
+     *
+     * @param string $message the message to output.
+     * @param int $depth indent depth for this message.
+     * @return void output stored in buffer
+     */
+    public function output($message, $depth = 0) {
+        ob_start();
+        $this->trace->output($message, $depth);
+        $this->buffer .= ob_get_contents();
+        if ($this->passthrough) {
+            ob_end_flush();
+        } else {
+            ob_end_clean();
+        }
+    }
+
+    /**
+     * Called when the processing is finished.
+     */
+    public function finished() {
+        ob_start();
+        $this->trace->finished();
+        $this->buffer .= ob_get_contents();
+        if ($this->passthrough) {
+            ob_end_flush();
+        } else {
+            ob_end_clean();
+        }
+    }
+
+    /**
+     * Reset internal text buffer.
+     */
+    public function reset_buffer() {
+        $this->buffer = '';
+    }
+
+    /**
+     * Return internal text buffer.
+     * @return string buffered plain text
+     */
+    public function get_buffer() {
+        return $this->buffer;
+    }
+}
+
+/**
+ * Special type of trace that can be used for redirecting to multiple
+ * other traces.
+ *
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package moodlecore
+ */
+class combined_progress_trace extends progress_trace {
+    protected $traces;
+
+    /**
+     * @param array $traces multiple traces
+     */
+    public function __construct(array $traces) {
+        $this->traces = $traces;
+    }
+
+    /**
+     * Output an progress message in whatever format.
+     *
+     * @param string $message the message to output.
+     * @param integer $depth indent depth for this message.
+     */
+    public function output($message, $depth = 0) {
+        foreach($this->traces as $trace) {
+            $trace->output($message, $depth);
+        }
+    }
+
+    /**
+     * Called when the processing is finished.
+     */
+    public function finished() {
+        foreach($this->traces as $trace) {
+            $trace->finished();
+        }
+    }
+}
+
 /**
  * Returns a localized sentence in the current language summarizing the current password policy
  *
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 2bfb6b1..1f676ee 100644 (file)
@@ -434,9 +434,11 @@ abstract class assign_plugin {
      *
      * @param stdClass $submissionorgrade assign_submission or assign_grade
      *                 For submission plugins this is the submission data, for feedback plugins it is the grade data
+     * @param stdClass $user The user record for the current submission.
+     *                         Needed for url rewriting if this is a group submission.
      * @return array - return an array of files indexed by filename
      */
-    public function get_files(stdClass $submissionorgrade) {
+    public function get_files(stdClass $submissionorgrade, stdClass $user) {
         return array();
     }
 
index b7fc5d1..4d8ab30 100644 (file)
@@ -39,5 +39,13 @@ $functions = array(
                 'classpath'   => 'mod/assign/externallib.php',
                 'description' => 'Returns the courses and assignments for the users capability',
                 'type'        => 'read'
+        ),
+
+        'mod_assign_get_submissions' => array(
+                'classname' => 'mod_assign_external',
+                'methodname' => 'get_submissions',
+                'classpath' => 'mod/assign/externallib.php',
+                'description' => 'Returns the submissions for assignments',
+                'type' => 'read'
         )
 );
index a7a2e80..50e8639 100644 (file)
@@ -184,7 +184,9 @@ class mod_assign_external extends external_api {
         return new external_single_structure(
             array(
                 'assignments' => new external_multiple_structure(self::assign_grades(), 'list of assignment grade information'),
-                'warnings'      => new external_warnings()
+                'warnings'      => new external_warnings('item is always \'assignment\'',
+                    'when errorcode is 3 then itemid is an assignment id. When errorcode is 1, itemid is a course module instance id',
+                    'errorcode can be 3 (no grades found) or 1 (no permission to get grades)')
             )
         );
     }
@@ -425,7 +427,250 @@ class mod_assign_external extends external_api {
         return new external_single_structure(
             array(
                 'courses' => new external_multiple_structure(self::get_assignments_course_structure(), 'list of courses'),
-                'warnings'  => new external_warnings()
+                'warnings'  => new external_warnings('item can be \'course\' (errorcode 1 or 2) or \'module\' (errorcode 1)',
+                    'When item is a course then itemid is a course id. When the item is a module then itemid is a module id',
+                    'errorcode can be 1 (no access rights) or 2 (not enrolled or no permissions)')
+            )
+        );
+    }
+
+    /**
+     * Describes the parameters for get_submissions
+     *
+     * @return external_external_function_parameters
+     * @since Moodle 2.5
+     */
+    public static function get_submissions_parameters() {
+        return new external_function_parameters(
+            array(
+                'assignmentids' => new external_multiple_structure(
+                    new external_value(PARAM_INT, 'assignment id'),
+                    '1 or more assignment ids',
+                    VALUE_REQUIRED),
+                'status' => new external_value(PARAM_ALPHA, 'status', VALUE_DEFAULT, ''),
+                'since' => new external_value(PARAM_INT, 'submitted since', VALUE_DEFAULT, 0),
+                'before' => new external_value(PARAM_INT, 'submitted before', VALUE_DEFAULT, 0)
+            )
+        );
+    }
+
+    /**
+     * Returns submissions for the requested assignment ids
+     *
+     * @param array of ints $assignmentids
+     * @param string $status only return submissions with this status
+     * @param int $since only return submissions with timemodified >= since
+     * @param int $before only return submissions with timemodified <= before
+     * @return array of submissions for each requested assignment
+     * @since Moodle 2.5
+     */
+    public static function get_submissions($assignmentids, $status = '', $since = 0, $before = 0) {
+        global $DB, $CFG;
+        require_once("$CFG->dirroot/mod/assign/locallib.php");
+        $params = self::validate_parameters(self::get_submissions_parameters(),
+                        array('assignmentids' => $assignmentids,
+                              'status' => $status,
+                              'since' => $since,
+                              'before' => $before));
+
+        $warnings = array();
+        $assignments = array();
+
+        // Check the user is allowed to get the submissions for the assignments requested.
+        $placeholders = array();
+        list($inorequalsql, $placeholders) = $DB->get_in_or_equal($params['assignmentids'], SQL_PARAMS_NAMED);
+        $sql = "SELECT cm.id, cm.instance FROM {course_modules} cm JOIN {modules} md ON md.id = cm.module ".
+               "WHERE md.name = :modname AND cm.instance ".$inorequalsql;
+        $placeholders['modname'] = 'assign';
+        $cms = $DB->get_records_sql($sql, $placeholders);
+        $assigns = array();
+        foreach ($cms as $cm) {
+            try {
+                $context = context_module::instance($cm->id);
+                self::validate_context($context);
+                require_capability('mod/assign:grade', $context);
+                $assign = new assign($context, null, null);
+                $assigns[] = $assign;
+            } catch (Exception $e) {
+                $warnings[] = array(
+                    'item' => 'assignment',
+                    'itemid' => $cm->instance,
+                    'warningcode' => '1',
+                    'message' => 'No access rights in module context'
+                );
+            }
+        }
+
+        foreach ($assigns as $assign) {
+            $submissions = array();
+            $submissionplugins = $assign->get_submission_plugins();
+            $placeholders = array('assignment' => $assign->get_instance()->id);
+            $sql = "SELECT mas.id, mas.assignment,mas.userid,".
+                   "mas.timecreated,mas.timemodified,mas.status,mas.groupid ".
+                   "FROM {assign_submission} mas ".
+                   "WHERE mas.assignment = :assignment";
+
+            if (!empty($params['status'])) {
+                $placeholders['status'] = $params['status'];
+                $sql = $sql." AND mas.status = :status";
+            }
+            if (!empty($params['before'])) {
+                $placeholders['since'] = $params['since'];
+                $placeholders['before'] = $params['before'];
+                $sql = $sql." AND mas.timemodified BETWEEN :since AND :before";
+            } else {
+                $placeholders['since'] = $params['since'];
+                $sql = $sql." AND mas.timemodified >= :since";
+            }
+
+            $submissionrecords = $DB->get_records_sql($sql, $placeholders);
+
+            if (!empty($submissionrecords)) {
+                $fs = get_file_storage();
+                foreach ($submissionrecords as $submissionrecord) {
+                    $submission = array(
+                        'id' => $submissionrecord->id,
+                        'userid' => $submissionrecord->userid,
+                        'timecreated' => $submissionrecord->timecreated,
+                        'timemodified' => $submissionrecord->timemodified,
+                        'status' => $submissionrecord->status,
+                        'groupid' => $submissionrecord->groupid
+                    );
+                    foreach ($submissionplugins as $submissionplugin) {
+                        $plugin = array(
+                            'name' => $submissionplugin->get_name(),
+                            'type' => $submissionplugin->get_type()
+                        );
+                        // Subtype is 'assignsubmission', type is currently 'file' or 'onlinetext'.
+                        $component = $submissionplugin->get_subtype().'_'.$submissionplugin->get_type();
+
+                        $fileareas = $submissionplugin->get_file_areas();
+                        foreach ($fileareas as $filearea => $name) {
+                            $fileareainfo = array('area' => $filearea);
+                            $files = $fs->get_area_files(
+                                $assign->get_context()->id,
+                                $component,
+                                $filearea,
+                                $submissionrecord->id,
+                                "timemodified",
+                                false
+                            );
+                            foreach ($files as $file) {
+                                $filepath = array('filepath' => $file->get_filepath().$file->get_filename());
+                                $fileareainfo['files'][] = $filepath;
+                            }
+                            $plugin['fileareas'][] = $fileareainfo;
+                        }
+
+                        $editorfields = $submissionplugin->get_editor_fields();
+                        foreach ($editorfields as $name => $description) {
+                            $editorfieldinfo = array(
+                                'name' => $name,
+                                'description' => $description,
+                                'text' => $submissionplugin->get_editor_text($name, $submissionrecord->id),
+                                'format' => $submissionplugin->get_editor_format($name, $submissionrecord->id)
+                            );
+                            $plugin['editorfields'][] = $editorfieldinfo;
+                        }
+
+                        $submission['plugins'][] = $plugin;
+                    }
+                    $submissions[] = $submission;
+                }
+            } else {
+                $warnings[] = array(
+                    'item' => 'module',
+                    'itemid' => $assign->get_instance()->id,
+                    'warningcode' => '3',
+                    'message' => 'No submissions found'
+                );
+            }
+
+            $assignments[] = array(
+                'assignmentid' => $assign->get_instance()->id,
+                'submissions' => $submissions
+            );
+
+        }
+
+        $result = array(
+            'assignments' => $assignments,
+            'warnings' => $warnings
+        );
+        return $result;
+    }
+
+    /**
+     * Creates an assign_submissions external_single_structure
+     *
+     * @return external_single_structure
+     * @since Moodle 2.5
+     */
+    private static function get_submissions_structure() {
+        return new external_single_structure(
+            array (
+                'assignmentid' => new external_value(PARAM_INT, 'assignment id'),
+                'submissions' => new external_multiple_structure(
+                    new external_single_structure(
+                        array(
+                            'id' => new external_value(PARAM_INT, 'submission id'),
+                            'userid' => new external_value(PARAM_INT, 'student id'),
+                            'timecreated' => new external_value(PARAM_INT, 'submission creation time'),
+                            'timemodified' => new external_value(PARAM_INT, 'submission last modified time'),
+                            'status' => new external_value(PARAM_TEXT, 'submission status'),
+                            'groupid' => new external_value(PARAM_INT, 'group id'),
+                            'plugins' => new external_multiple_structure(
+                                new external_single_structure(
+                                    array(
+                                        'type' => new external_value(PARAM_TEXT, 'submission plugin type'),
+                                        'name' => new external_value(PARAM_TEXT, 'submission plugin name'),
+                                        'fileareas' => new external_multiple_structure(
+                                            new external_single_structure(
+                                                array (
+                                                    'area' => new external_value (PARAM_TEXT, 'file area'),
+                                                    'files' => new external_multiple_structure(
+                                                        new external_single_structure(
+                                                            array (
+                                                                'filepath' => new external_value (PARAM_TEXT, 'file path')
+                                                            )
+                                                        ), 'files', VALUE_OPTIONAL
+                                                    )
+                                                )
+                                            ), 'fileareas', VALUE_OPTIONAL
+                                        ),
+                                        'editorfields' => new external_multiple_structure(
+                                            new external_single_structure(
+                                                array(
+                                                    'name' => new external_value(PARAM_TEXT, 'field name'),
+                                                    'description' => new external_value(PARAM_TEXT, 'field description'),
+                                                    'text' => new external_value (PARAM_TEXT, 'field value'),
+                                                    'format' => new external_value (PARAM_INT, 'field format')
+                                                )
+                                            )
+                                            , 'editorfields', VALUE_OPTIONAL
+                                        )
+                                    )
+                                )
+                                , 'plugins', VALUE_OPTIONAL
+                            )
+                        )
+                    )
+                )
+            )
+        );
+    }
+
+    /**
+     * Describes the get_submissions return value
+     *
+     * @return external_single_structure
+     * @since Moodle 2.5
+     */
+    public static function get_submissions_returns() {
+        return new external_single_structure(
+            array(
+                'assignments' => new external_multiple_structure(self::get_submissions_structure(), 'assignment submissions'),
+                'warnings' => new external_warnings()
             )
         );
     }
index c3a0441..a6c77a5 100644 (file)
@@ -112,7 +112,7 @@ class assignfeedback_file_zip_importer {
         if (!$sg) {
             return true;
         }
-        foreach ($plugin->get_files($sg) as $pluginfilename => $file) {
+        foreach ($plugin->get_files($sg, $user) as $pluginfilename => $file) {
             if ($pluginfilename == $filename) {
                 // Extract the file and compare hashes.
                 $contenthash = '';
@@ -144,7 +144,7 @@ class assignfeedback_file_zip_importer {
         $fs = get_file_storage();
 
         return $fs->delete_area_files($contextid,
-                                      'assignfeedback_files',
+                                      'assignfeedback_file',
                                       ASSIGNFEEDBACK_FILE_IMPORT_FILEAREA,
                                       $USER->id);
     }
index 8bdc7b5..4fa274b 100644 (file)
@@ -1940,6 +1940,39 @@ class assign {
         return $o;
     }
 
+    /**
+     * Rewrite plugin file urls so they resolve correctly in an exported zip.
+     *
+     * @param stdClass $user - The user record
+     * @param assign_plugin $plugin - The assignment plugin
+     */
+    public function download_rewrite_pluginfile_urls($text, $user, $plugin) {
+        $groupmode = groups_get_activity_groupmode($this->get_course_module());
+        $groupname = '';
+        if ($groupmode) {
+            $groupid = groups_get_activity_group($this->get_course_module(), true);
+            $groupname = groups_get_group_name($groupid).'-';
+        }
+
+        if ($this->is_blind_marking()) {
+            $prefix = $groupname . get_string('participant', 'assign');
+            $prefix = str_replace('_', ' ', $prefix);
+            $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
+        } else {
+            $prefix = $groupname . fullname($user);
+            $prefix = str_replace('_', ' ', $prefix);
+            $prefix = clean_filename($prefix . '_' . $this->get_uniqueid_for_user($user->id) . '_');
+        }
+
+        $subtype = $plugin->get_subtype();
+        $type = $plugin->get_type();
+        $prefix = $prefix . $subtype . '_' . $type . '_';
+
+        $result = str_replace('@@PLUGINFILE@@/', $prefix, $text);
+
+        return $result;
+    }
+
     /**
      * Render the content in editor that is often used by plugin.
      *
@@ -2102,7 +2135,7 @@ class assign {
                 if ($submission) {
                     foreach ($this->submissionplugins as $plugin) {
                         if ($plugin->is_enabled() && $plugin->is_visible()) {
-                            $pluginfiles = $plugin->get_files($submission);
+                            $pluginfiles = $plugin->get_files($submission, $student);
                             foreach ($pluginfiles as $zipfilename => $file) {
                                 $subtype = $plugin->get_subtype();
                                 $type = $plugin->get_type();
index a6c8e97..39c3561 100644 (file)
@@ -260,7 +260,7 @@ class assign_submission_file extends assign_submission_plugin {
      * @param stdClass $submission The submission
      * @return array - return an array of files indexed by filename
      */
-    public function get_files(stdClass $submission) {
+    public function get_files(stdClass $submission, stdClass $user) {
         $result = array();
         $fs = get_file_storage();
 
index d5c159b..edbe85a 100644 (file)
@@ -277,34 +277,17 @@ class assign_submission_onlinetext extends assign_submission_plugin {
      * Produce a list of files suitable for export that represent this submission.
      *
      * @param stdClass $submission - For this is the submission data
+     * @param stdClass $user - This is the user record for this submission
      * @return array - return an array of files indexed by filename
      */
-    public function get_files(stdClass $submission) {
+    public function get_files(stdClass $submission, stdClass $user) {
         global $DB;
 
         $files = array();
         $onlinetextsubmission = $this->get_onlinetext_submission($submission->id);
 
         if ($onlinetextsubmission) {
-            $user = $DB->get_record('user',
-                                    array('id'=>$submission->userid),
-                                    'id,username,firstname,lastname',
-                                     MUST_EXIST);
-
-            if (!$this->assignment->is_blind_marking()) {
-                $filename = str_replace('_', '', fullname($user)) . '_' .
-                            $this->assignment->get_uniqueid_for_user($submission->userid) . '_' .
-                            $this->get_name() . '_';
-                $prefix = clean_filename($filename);
-            } else {
-                $filename = get_string('participant', 'assign') . '_' .
-                            $this->assignment->get_uniqueid_for_user($submission->userid) . '_' .
-                            $this->get_name() . '_';
-                $prefix = clean_filename($filename);
-            }
-
-            $finaltext = str_replace('@@PLUGINFILE@@/', $prefix, $onlinetextsubmission->onlinetext);
-
+            $finaltext = $this->assignment->download_rewrite_pluginfile_urls($onlinetextsubmission->onlinetext, $user, $this);
             $formattedtext = format_text($finaltext,
                                          $onlinetextsubmission->onlineformat,
                                          array('context'=>$this->assignment->get_context()));
index 7ea9298..3e8399f 100644 (file)
@@ -182,4 +182,81 @@ class mod_assign_external_testcase extends externallib_advanced_testcase {
         $this->assertEquals(0, count($result['courses']));
         $this->assertEquals(1, count($result['warnings']));
     }
+
+    /**
+     * Test get_submissions
+     */
+    public function test_get_submissions () {
+        global $DB, $USER;
+
+        $this->resetAfterTest(true);
+        // Create a course and assignment.
+        $coursedata['idnumber'] = 'idnumbercourse1';
+        $coursedata['fullname'] = 'Lightwork Course 1';
+        $coursedata['summary'] = 'Lightwork Course 1 description';
+        $coursedata['summaryformat'] = FORMAT_MOODLE;
+        $course1 = self::getDataGenerator()->create_course($coursedata);
+
+        $assigndata['course'] = $course1->id;
+        $assigndata['name'] = 'lightwork assignment';
+
+        $assign1 = self::getDataGenerator()->create_module('assign', $assigndata);
+
+        // Create a student with an online text submission.
+        $student = self::getDataGenerator()->create_user();
+        $submission = new stdClass();
+        $submission->assignment = $assign1->id;
+        $submission->userid = $student->id;
+        $submission->timecreated = time();
+        $submission->timemodified = $submission->timecreated;
+        $submission->status = 'submitted';
+        $sid = $DB->insert_record('assign_submission', $submission);
+        $submission->id = $sid;
+
+        $onlinetextsubmission = new stdClass();
+        $onlinetextsubmission->onlinetext = "online test text";
+        $onlinetextsubmission->onlineformat = 1;
+        $onlinetextsubmission->submission = $submission->id;
+        $onlinetextsubmission->assignment = $assign1->id;
+        $DB->insert_record('assignsubmission_onlinetext', $onlinetextsubmission);
+
+        // Create manual enrolment record.
+        $manual_enrol_data['enrol'] = 'manual';
+        $manual_enrol_data['status'] = 0;
+        $manual_enrol_data['courseid'] = $course1->id;
+        $enrolid = $DB->insert_record('enrol', $manual_enrol_data);
+
+        // Create a teacher and give them capabilities.
+        $context = context_course::instance($course1->id);
+        $roleid = $this->assignUserCapability('moodle/course:viewparticipants', $context->id, 3);
+        $context = context_module::instance($assign1->id);
+        $this->assignUserCapability('mod/assign:grade', $context->id, $roleid);
+
+        // Create the teacher's enrolment record.
+        $user_enrolment_data['status'] = 0;
+        $user_enrolment_data['enrolid'] = $enrolid;
+        $user_enrolment_data['userid'] = $USER->id;
+        $DB->insert_record('user_enrolments', $user_enrolment_data);
+
+        $assignmentids[] = $assign1->id;
+        $result = mod_assign_external::get_submissions($assignmentids);
+
+        // Check the online text submission is returned.
+        $this->assertEquals(1, count($result['assignments']));
+        $assignment = $result['assignments'][0];
+        $this->assertEquals($assign1->id, $assignment['assignmentid']);
+        $this->assertEquals(1, count($assignment['submissions']));
+        $submission = $assignment['submissions'][0];
+        $this->assertEquals($sid, $submission['id']);
+        $this->assertGreaterThanOrEqual(3, count($submission['plugins']));
+        $plugins = $submission['plugins'];
+        foreach ($plugins as $plugin) {
+            $foundonlinetext = false;
+            if ($plugin['type'] == 'onlinetext') {
+                $foundonlinetext = true;
+                break;
+            }
+        }
+        $this->assertTrue($foundonlinetext);
+    }
 }
index 683d9db..ab394dd 100644 (file)
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 $module->component = 'mod_assign'; // Full name of the plugin (used for diagnostics).
-$module->version  = 2012112900;    // The current module version (Date: YYYYMMDDXX).
+$module->version  = 2012112901;    // The current module version (Date: YYYYMMDDXX).
 $module->requires = 2012112900;    // Requires this Moodle version.
 $module->cron     = 60;
 
index c37976f..7b64a21 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 68e6ad8..d393966 100644 (file)
@@ -715,6 +715,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".
@@ -1006,10 +1011,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!");
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 4ea972c..2ed708f 100644 (file)
@@ -527,6 +527,9 @@ M.mod_scorm.init = function(Y, hide_nav, hide_toc, toc_title, window_name, launc
 
         // finally activate the chosen item
         var scorm_first_url = tree.getRoot().children[0];
+        if (scorm_first_url == null) { // This is probably a single sco with no children (AICC Direct uses this).
+            scorm_first_url = tree.getRoot();
+        }
         scorm_first_url.title = scoes_nav[launch_sco].url;
         scorm_activate_item(scorm_first_url);
 
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 d57ce96..95de2f3 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 0aeb933..86be20c 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 95e64ca..f9fbcce 100644 (file)
@@ -30,7 +30,7 @@
 defined('MOODLE_INTERNAL') || die();
 
 
-$version  = 2013011100.01;              // YYYYMMDD      = weekly release date of this DEV branch
+$version  = 2013011100.02;              // YYYYMMDD      = weekly release date of this DEV branch
                                         //         RR    = release increments - 00 in DEV branches
                                         //           .XX = incremental changes