diff --git a/enrol/paypal/cli/sync.php b/enrol/paypal/cli/sync.php new file mode 100644 index 00000000000..5853e0c1e10 --- /dev/null +++ b/enrol/paypal/cli/sync.php @@ -0,0 +1,75 @@ +. + +/** + * 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); diff --git a/enrol/paypal/lang/en/enrol_paypal.php b/enrol/paypal/lang/en/enrol_paypal.php index 803d5604fa1..29b840477f4 100644 --- a/enrol/paypal/lang/en/enrol_paypal.php +++ b/enrol/paypal/lang/en/enrol_paypal.php @@ -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'; diff --git a/enrol/paypal/lib.php b/enrol/paypal/lib.php index 7d49d056c4d..d9d51245f63 100644 --- a/enrol/paypal/lib.php +++ b/enrol/paypal/lib.php @@ -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; + } } diff --git a/enrol/paypal/settings.php b/enrol/paypal/settings.php index 9b3c14d2988..aa026e0cd09 100644 --- a/enrol/paypal/settings.php +++ b/enrol/paypal/settings.php @@ -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 index 00000000000..d31b75b6708 --- /dev/null +++ b/enrol/paypal/tests/paypal_test.php @@ -0,0 +1,171 @@ +. + +/** + * 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))); + } +} diff --git a/enrol/paypal/version.php b/enrol/paypal/version.php index b9b64c95bc2..d0c4b7ffe3f 100644 --- a/enrol/paypal/version.php +++ b/enrol/paypal/version.php @@ -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;