Merge branch 'wip-MDL-37453-master' of git://github.com/marinaglancy/moodle
authorEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 14 Jan 2013 23:06:06 +0000 (00:06 +0100)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Mon, 14 Jan 2013 23:06:06 +0000 (00:06 +0100)
54 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/admin.php
lang/en/cache.php
lib/db/caches.php
lib/externallib.php
lib/moodlelib.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 eac8c62..161ab26 100644 (file)
@@ -332,7 +332,6 @@ $string['configthemedesignermode'] = 'Normally all theme images and style sheets
 $string['configthemelist'] = 'Leave this blank to allow any valid theme to be used.  If you want to shorten the theme menu, you can specify a comma-separated list of names here (Don\'t use spaces!).
 For example:  standard,orangewhite.';
 $string['configtimezone'] = 'You can set the default timezone here.  This is the only the DEFAULT timezone for displaying dates - each user can override this by setting their own in their profile. "Server time" here will make Moodle default to the server\'s operating system setting, but "Server time" in the user profile will make the user default to this timezone setting.  Cronjobs that depend on a time of day to run will use this timezone.';
-$string['configunzip'] = 'Indicate the location of your unzip program (Unix only, optional).  If specified, this will be used to unpack zip archives on the server.  If you leave this blank, then Moodle will use internal routines.';
 $string['configuseblogassociations'] = 'Should users be able to organize their blog by associating entries with courses and course modules?';
 $string['configuseexternalyui'] = 'Instead of using local files, use online files available on Yahoo&#145;s servers. WARNING: This requires an internet connection, or no AJAX will work on your site. This setting is not compatible with sites using https.';
 $string['configusesitenameforsitepages'] = 'If enabled the site\'s shortname will be used for the site pages node in the navigation rather than the string \'Site pages\'';
@@ -342,7 +341,6 @@ $string['configverifychangedemail'] = 'Enables verification of changed email add
 $string['configvisiblecourses'] = 'Display courses in hidden categories normally';
 $string['configwarning'] = 'Be careful modifying these settings - strange values could cause problems.';
 $string['configyuicomboloading'] = 'This options enables combined file loading optimisation for YUI libraries. This setting should be enabled on production sites for performance reasons.';
-$string['configzip'] = 'Indicate the location of your zip program (Unix only, optional).  If specified, this will be used to create zip archives on the server.  If you leave this blank, then Moodle will use internal routines.';
 $string['confirmation'] = 'Confirmation';
 $string['confirmdeletecomments'] = 'You are about to delete comments, are you sure?';
 $string['confirmed'] = 'Confirmed';
@@ -775,8 +773,6 @@ $string['pathtopgdumpinvalid'] = 'Invalid path to pg_dump - either wrong path or
 $string['pathtopsql'] = 'Path to psql';
 $string['pathtopsqldesc'] = 'This is only necessary to enter if you have more than one psql on your system (for example if you have more than one version of postgresql installed)';
 $string['pathtopsqlinvalid'] = 'Invalid path to psql - either wrong path or not executable';
-$string['pathtounzip'] = 'Path to unzip';
-$string['pathtozip'] = 'Path to zip';
 $string['pcreunicodewarning'] = 'It is strongly recommended to use PCRE PHP extension that is compatible with Unicode characters.';
 $string['perfdebug'] = 'Performance info';
 $string['performance'] = 'Performance';
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 d27391a..c7d6621 100644 (file)
@@ -3082,7 +3082,12 @@ function require_login($courseorid = NULL, $autologinguest = true, $cm = NULL, $
         if ($preventredirect) {
             throw new require_login_exception('Activity is hidden');
         }
-        redirect($CFG->wwwroot, get_string('activityiscurrentlyhidden'));
+        if ($course->id != SITEID) {
+            $url = new moodle_url('/course/view.php', array('id'=>$course->id));
+        } else {
+            $url = new moodle_url('/');
+        }
+        redirect($url, get_string('activityiscurrentlyhidden'));
     }
 
     // Finally access granted, update lastaccess times
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