Merge branch 'w02_MDL-37286_m25_tracebuffer' of git://github.com/skodak/moodle
authorDan Poltawski <dan@moodle.com>
Mon, 14 Jan 2013 07:11:42 +0000 (15:11 +0800)
committerDan Poltawski <dan@moodle.com>
Mon, 14 Jan 2013 07:11:42 +0000 (15:11 +0800)
22 files changed:
course/dndupload.js
course/dnduploadlib.php
course/edit_form.php
course/editsection_form.php
course/format/renderer.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
lang/en/cache.php
lib/db/caches.php
lib/weblib.php
lib/yui/formautosubmit/formautosubmit.js
mod/book/tool/importhtml/locallib.php
mod/forum/lib.php
mod/scorm/module.js
mod/wiki/lang/en/wiki.php
mod/wiki/lib.php
user/view.php
version.php

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).
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 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 0b2caea..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;
 }
 
 /**
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 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 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