From a35392da2f96f30a065ec1cd1de3d553ce31776a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 17 Jun 2011 02:24:34 -0700 Subject: [PATCH] EmailReminder plugin to send reminders about various things * Needs some cleanup and testing * Email templates need work * More documentation Squashed commit of the following: commit 1c7b418dad5ec1b7713d61b6a42d6d7a394d500f Author: Zach Copley Date: Fri Jun 17 02:17:31 2011 -0700 * Set the reminder interval correctly commit ae0ded8cf95210f54b4cd58dac0eeeedf2d99c67 Author: Zach Copley Date: Fri Jun 17 02:15:01 2011 -0700 Send email reminders for invitations commit 1b596d08f5dbe765a16fbdfbd21e2ad68e8b0058 Author: Zach Copley Date: Thu Jun 16 23:53:48 2011 -0700 Handle multiple confirmation types commit 25d83351d878f39498cd6a14fddde27f1afef2ca Author: Zach Copley Date: Thu Jun 16 18:04:57 2011 -0700 Actually send reminders and record a record of doing so commit 9ffc2dbee15cacc7e7f9feab492185ee9964a17e Author: Zach Copley Date: Thu Jun 16 14:20:16 2011 -0700 Make the queue handling actually work commit 2a6ce3c17c045bdb0a3ddf36f2c290c9c48eb003 Author: Zach Copley Date: Thu Jun 16 13:27:56 2011 -0700 Fix syntax errors commit 054b54847dfadc490aa7d7dff12d473af31c99bb Author: Zach Copley Date: Thu Jun 16 00:36:37 2011 -0700 Registration reminders should work now, but code is untested commit b44117017b64635aae340c260167cf1efab9b2ae Merge: 9d1441d f74de88 Author: Zach Copley Date: Tue Jun 14 09:43:19 2011 -0700 Merge branch 'email-reminder' of gitorious.org:~zcopley/statusnet/zcopleys-clone into email-reminder * 'email-reminder' of gitorious.org:~zcopley/statusnet/zcopleys-clone: Stubby EmailReminderPlugin and data class Remove bogus data class Conflicts: plugins/EmailReminder/EmailReminderPlugin.php plugins/EmailReminder/classes/Email_reminder.php commit 9d1441d7366df57e38cdfaf96e006f7d2f29d889 Author: Zach Copley Date: Tue Jun 14 09:23:23 2011 -0700 Most of the other classes needed to send email reminders commit 4e9bb11dbb23556bf5c1847e7a127084b5cc217c Author: Zach Copley Date: Mon Jun 13 12:10:55 2011 -0700 size -> length commit a9ea80ef8abae1e64d5713091baedd931b7184e2 Author: Zach Copley Date: Fri Jun 10 16:38:06 2011 -0400 Stubby EmailReminderPlugin and data class commit 5d893f982209b245cb9113a59e49721dd6e191b6 Author: Zach Copley Date: Fri Jun 10 14:01:48 2011 -0400 Remove bogus data class commit f74de8841a98add73536fd8a4d3cee76035b491c Author: Zach Copley Date: Fri Jun 10 16:38:06 2011 -0400 Stubby EmailReminderPlugin and data class commit 5b14370918233e5112a95da94567c4ed83429bc9 Author: Zach Copley Date: Fri Jun 10 14:01:48 2011 -0400 Remove bogus data class --- .../EmailRegistration/Email_confirmation.php | 183 --------------- plugins/EmailReminder/EmailReminderPlugin.php | 209 ++++++++++++++++++ .../EmailReminder/classes/Email_reminder.php | 154 +++++++++++++ .../lib/siteconfirmreminderhandler.php | 112 ++++++++++ .../lib/userconfirmregreminderhandler.php | 126 +++++++++++ .../lib/userinvitereminderhandler.php | 110 +++++++++ .../EmailReminder/lib/userreminderhandler.php | 63 ++++++ plugins/EmailReminder/mail-src/invite-1 | 26 +++ plugins/EmailReminder/mail-src/invite-3 | 26 +++ plugins/EmailReminder/mail-src/register-1 | 9 + plugins/EmailReminder/mail-src/register-3 | 9 + plugins/EmailReminder/mail-src/register-7 | 9 + plugins/EmailReminder/scripts/cron.sample | 1 + .../scripts/sendemailreminder.php | 132 +++++++++++ 14 files changed, 986 insertions(+), 183 deletions(-) delete mode 100644 plugins/EmailRegistration/Email_confirmation.php create mode 100644 plugins/EmailReminder/EmailReminderPlugin.php create mode 100644 plugins/EmailReminder/classes/Email_reminder.php create mode 100644 plugins/EmailReminder/lib/siteconfirmreminderhandler.php create mode 100644 plugins/EmailReminder/lib/userconfirmregreminderhandler.php create mode 100644 plugins/EmailReminder/lib/userinvitereminderhandler.php create mode 100644 plugins/EmailReminder/lib/userreminderhandler.php create mode 100644 plugins/EmailReminder/mail-src/invite-1 create mode 100644 plugins/EmailReminder/mail-src/invite-3 create mode 100644 plugins/EmailReminder/mail-src/register-1 create mode 100644 plugins/EmailReminder/mail-src/register-3 create mode 100644 plugins/EmailReminder/mail-src/register-7 create mode 100644 plugins/EmailReminder/scripts/cron.sample create mode 100644 plugins/EmailReminder/scripts/sendemailreminder.php diff --git a/plugins/EmailRegistration/Email_confirmation.php b/plugins/EmailRegistration/Email_confirmation.php deleted file mode 100644 index 949556fc10..0000000000 --- a/plugins/EmailRegistration/Email_confirmation.php +++ /dev/null @@ -1,183 +0,0 @@ - - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program 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 Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; - -/** - * Data class for counting greetings - * - * We use the DB_DataObject framework for data classes in StatusNet. Each - * table maps to a particular data class, making it easier to manipulate - * data. - * - * Data classes should extend Memcached_DataObject, the (slightly misnamed) - * extension of DB_DataObject that provides caching, internationalization, - * and other bits of good functionality to StatusNet-specific data classes. - * - * @category Action - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * - * @see DB_DataObject - */ -class User_greeting_count extends Memcached_DataObject -{ - public $__table = 'user_greeting_count'; // table name - public $user_id; // int(4) primary_key not_null - public $greeting_count; // int(4) - - /** - * Get an instance by key - * - * This is a utility method to get a single instance with a given key value. - * - * @param string $k Key to use to lookup (usually 'user_id' for this class) - * @param mixed $v Value to lookup - * - * @return User_greeting_count object found, or null for no hits - * - */ - function staticGet($k, $v=null) - { - return Memcached_DataObject::staticGet('User_greeting_count', $k, $v); - } - - /** - * return table definition for DB_DataObject - * - * DB_DataObject needs to know something about the table to manipulate - * instances. This method provides all the DB_DataObject needs to know. - * - * @return array array of column definitions - */ - function table() - { - return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'greeting_count' => DB_DATAOBJECT_INT); - } - - /** - * return key definitions for DB_DataObject - * - * DB_DataObject needs to know about keys that the table has, since it - * won't appear in StatusNet's own keys list. In most cases, this will - * simply reference your keyTypes() function. - * - * @return array list of key field names - */ - function keys() - { - return array_keys($this->keyTypes()); - } - - /** - * return key definitions for Memcached_DataObject - * - * Our caching system uses the same key definitions, but uses a different - * method to get them. This key information is used to store and clear - * cached data, so be sure to list any key that will be used for static - * lookups. - * - * @return array associative array of key definitions, field name to type: - * 'K' for primary key: for compound keys, add an entry for each component; - * 'U' for unique keys: compound keys are not well supported here. - */ - function keyTypes() - { - return array('user_id' => 'K'); - } - - /** - * Magic formula for non-autoincrementing integer primary keys - * - * If a table has a single integer column as its primary key, DB_DataObject - * assumes that the column is auto-incrementing and makes a sequence table - * to do this incrementation. Since we don't need this for our class, we - * overload this method and return the magic formula that DB_DataObject needs. - * - * @return array magic three-false array that stops auto-incrementing. - */ - function sequenceKey() - { - return array(false, false, false); - } - - /** - * Increment a user's greeting count and return instance - * - * This method handles the ins and outs of creating a new greeting_count for a - * user or fetching the existing greeting count and incrementing its value. - * - * @param integer $user_id ID of the user to get a count for - * - * @return User_greeting_count instance for this user, with count already incremented. - */ - static function inc($user_id) - { - $gc = User_greeting_count::staticGet('user_id', $user_id); - - if (empty($gc)) { - - $gc = new User_greeting_count(); - - $gc->user_id = $user_id; - $gc->greeting_count = 1; - - $result = $gc->insert(); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m("Could not save new greeting count for %d."), - $user_id)); - } - } else { - $orig = clone($gc); - - $gc->greeting_count++; - - $result = $gc->update($orig); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m('Could not increment greeting count for %d.'), - $user_id)); - } - } - - return $gc; - } -} diff --git a/plugins/EmailReminder/EmailReminderPlugin.php b/plugins/EmailReminder/EmailReminderPlugin.php new file mode 100644 index 0000000000..0bb10921b0 --- /dev/null +++ b/plugins/EmailReminder/EmailReminderPlugin.php @@ -0,0 +1,209 @@ +. + * + * @category OnDemand + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Email reminder plugin + * + * @category Plugin + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class EmailReminderPlugin extends Plugin +{ + /** + * Set up email_reminder table + * + * @see Schema + * @see ColumnDef + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onCheckSchema() + { + $schema = Schema::get(); + $schema->ensureTable('email_reminder', Email_reminder::schemaDef()); + return true; + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false + * means stop. + */ + function onAutoload($cls) { + $base = dirname(__FILE__); + $lower = strtolower($cls); + + $files = array("$base/classes/$cls.php", + "$base/lib/$lower.php"); + if (substr($lower, -6) == 'action') { + $files[] = "$base/actions/" . substr($lower, 0, -6) . ".php"; + } + foreach ($files as $file) { + if (file_exists($file)) { + include_once $file; + return false; + } + } + return true; + } + + /** + * Register our queue handlers + * + * @param QueueManager $qm Current queue manager + * + * @return boolean hook value + */ + function onEndInitializeQueueManager($qm) + { + $qm->connect('siterem', 'SiteConfirmReminderHandler'); + $qm->connect('uregrem', 'UserConfirmRegReminderHandler'); + $qm->connect('uinvrem', 'UserInviteReminderHandler'); + + return true; + } + + function onEndDocFileForTitle($title, $paths, &$filename) + { + if (empty($filename)) { + $filename = dirname(__FILE__) . '/mail-src/' . $title; + return false; + } + + return true; + } + + /** + * + * @param type $object + * @param type $subject + * @param type $day + */ + static function sendReminder($type, $object, $subject, $day) + { + common_debug("QQQQQ sendReminder() enter ... ", __FILE__); + + $title = "{$type}-{$day}"; + + common_debug("QQQQ title = {$title}", __FILE__); + + // Record the fact that we sent a reminder + if (self::sendReminderEmail($type, $object, $subject, $title)) { + common_debug("Recording reminder record for {$object->address}", __FILE__); + try { + Email_reminder::recordReminder($type, $object, $day); + } catch (Exception $e) { + // oh noez + common_log(LOG_ERR, $e->getMessage(), __FILE__); + } + } + + common_debug("QQQQQ sendReminder() exit ... ", __FILE__); + + return true; + } + + /** + * + * @param type $object + * @param type $subject + * @param type $title + * @return type + */ + static function sendReminderEmail($type, $object, $subject, $title=null) { + + $sitename = common_config('site', 'name'); + $recipients = array($object->address); + $inviter = null; + $inviterurl = null; + + if ($type == UserInviteReminderHandler::INVITE_REMINDER) { + $user = User::staticGet($object->user_id); + if (!empty($user)) { + $profile = $user->getProfile(); + $inviter = $profile->getBestName(); + $inviterUrl = $profile->profileurl; + } + } + + $headers['From'] = mail_notify_from(); + $headers['To'] = trim($object->address); + // TRANS: Subject for confirmation e-mail. + // TRANS: %s is the StatusNet sitename. + $headers['Subject'] = $subject; + $headers['Content-Type'] = 'text/html; charset=UTF-8'; + + $confirmUrl = common_local_url('register', array('code' => $object->code)); + + $template = DocFile::forTitle($title, DocFile::mailPaths()); + + $body = $template->toHTML( + array( + 'confirmurl' => $confirmUrl, + 'inviter' => $inviter, + 'inviterurl' => $inviterUrl + // @todo private invitation message + ) + ); + + return mail_send($recipients, $headers, $body); + } + + /** + * + * @param type $versions + * @return type + */ + function onPluginVersion(&$versions) + { + $versions[] = array( + 'name' => 'EmailReminder', + 'version' => STATUSNET_VERSION, + 'author' => 'Zach Copley', + 'homepage' => 'http://status.net/wiki/Plugin:EmailReminder', + 'rawdescription' => _m('Send email reminders for various things.') + ); + return true; + } + +} diff --git a/plugins/EmailReminder/classes/Email_reminder.php b/plugins/EmailReminder/classes/Email_reminder.php new file mode 100644 index 0000000000..2a82e6c4d8 --- /dev/null +++ b/plugins/EmailReminder/classes/Email_reminder.php @@ -0,0 +1,154 @@ +. + * + * @category Data + * @package EmailReminder + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class Email_reminder extends Managed_DataObject +{ + const INVITE_REMINDER = 'invite'; // @todo Move this to the invite reminder handler + + public $__table = 'email_reminder'; + + public $type; // type of reminder + public $code; // confirmation code + public $days; // number of days after code was created + public $sent; // timestamp + public $created; // timestamp + public $modified; // timestamp + + /** + * Get an instance by key + * + * This is a utility method to get a single instance with a given key value. + * + * @param string $k Key to use to lookup + * @param mixed $v Value to lookup + * + * @return QnA_Answer object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('email_reminder', $k, $v); + } + + /** + * + * @param type $type + * @param type $confirm + * @param type $day + * @return type + */ + static function needsReminder($type, $confirm, $days) { + + $reminder = new Email_reminder(); + $reminder->type = $type; + $reminder->code = $confirm->code; + $reminder->days = $days; + + $result = $reminder->find(true); + + if (empty($result)) { + return true; + } + + return false; + } + + /** + * + * @param type $type + * @param type $confirm + * @param type $day + * @return type + */ + static function recordReminder($type, $confirm, $days) { + + $reminder = new Email_reminder(); + $reminder->type = $type; + $reminder->code = $confirm->code; + $reminder->days = $days; + $reminder->sent = $reminder->created = common_sql_now(); + $result = $reminder->insert(); + + if (empty($result)) { + common_log_db_error($reminder, 'INSERT', __FILE__); + throw new ServerException( + _m('Database error inserting reminder record.') + ); + } + + return $result; + } + + /** + * Data definition for email reminders + */ + public static function schemaDef() + { + return array( + 'description' => 'Record of email reminders that have been sent', + 'fields' => array( + 'type' => array( + 'type' => 'varchar', + 'length' => 255, + 'not null' => true, + 'description' => 'type of reminder' + ), + 'code' => array( + 'type' => 'varchar', + 'not null' => 'true', + 'length' => 255, + 'description' => 'confirmation code' + ), + 'days' => array( + 'type' => 'int', + 'not null' => 'true', + 'description' => 'number of days since code creation' + ), + 'sent' => array( + 'type' => 'datetime', + 'not null' => true, + 'description' => 'Date and time the reminder was sent' + ), + 'created' => array( + 'type' => 'datetime', + 'not null' => true, + 'description' => 'Date and time the record was created' + ), + 'modified' => array( + 'type' => 'timestamp', + 'not null' => true, + 'description' => 'Date and time the record was last modified' + ), + ), + 'primary key' => array('type', 'code', 'days'), + 'indexes' => array( + 'sent_idx' => array('sent'), + ), + ); + } +} diff --git a/plugins/EmailReminder/lib/siteconfirmreminderhandler.php b/plugins/EmailReminder/lib/siteconfirmreminderhandler.php new file mode 100644 index 0000000000..e5b561827b --- /dev/null +++ b/plugins/EmailReminder/lib/siteconfirmreminderhandler.php @@ -0,0 +1,112 @@ +. + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Handler for reminder queue items which send reminder emails to all users + * we would like to complete a given process (e.g.: registration) + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class SiteConfirmReminderHandler extends QueueHandler +{ + /** + * Return transport keyword which identifies items this queue handler + * services; must be defined for all subclasses. + * + * Must be 8 characters or less to fit in the queue_item database. + * ex "email", "jabber", "sms", "irc", ... + * + * @return string + */ + function transport() + { + return 'siterem'; + } + + /** + * Handle the site + * + * @param string $reminderType type of reminder to send + * @return boolean true on success, false on failure + */ + function handle($reminderType) + { + $qm = QueueManager::get(); + + try { + switch($reminderType) { + case UserConfirmRegReminderHandler::REGISTER_REMINDER: + $confirm = new Confirm_address(); + $confirm->address_type = $object; + $confirm->find(); + while ($confirm->fetch()) { + try { + $qm->enqueue($confirm, 'uregrem'); + } catch (Exception $e) { + common_log(LOG_WARNING, $e->getMessage()); + continue; + } + } + break; + case UserInviteReminderHandler::INVITE_REMINDER: + $invitation = new Invitation(); + $invitation->find(); + while ($invitation->fetch()) { + try { + $qm->enqueue($invitation, 'uinvrem'); + } catch (Exception $e) { + common_log(LOG_WARNING, $e->getMessage()); + continue; + } + } + break; + default: + // WTF? + common_log( + LOG_ERR, + "Received unknown confirmation address type", + __FILE__ + ); + } + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + return false; + } + + return true; + } +} diff --git a/plugins/EmailReminder/lib/userconfirmregreminderhandler.php b/plugins/EmailReminder/lib/userconfirmregreminderhandler.php new file mode 100644 index 0000000000..5f1bbd62d9 --- /dev/null +++ b/plugins/EmailReminder/lib/userconfirmregreminderhandler.php @@ -0,0 +1,126 @@ +. + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Handler for queue items of type 'uregrem' + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class UserConfirmRegReminderHandler extends UserReminderHandler { + + const REGISTER_REMINDER = 'register'; + + /** + * Return transport keyword which identifies items this queue handler + * services; must be defined for all subclasses. + * + * Must be 8 characters or less to fit in the queue_item database. + * ex "email", "jabber", "sms", "irc", ... + * + * @return string + */ + function transport() { + return 'uregrem'; + } + + /** + * Send an email registration confirmation reminder until the user + * confirms her registration. We'll send a reminder after one day, + * three days, and a full week. + * + * @todo abstract this bit further + * + * @param Confirm_address $confirm + * @return boolean success value + */ + function sendNextReminder($confirm) + { + $regDate = strtotime($confirm->modified); // Seems like my best bet + $now = strtotime('now'); + + // Days since registration + $days = ($now - $regDate) / 86499; // 60*60*24 = 86499 + + // Welcome to one of the ugliest switch statement I've ever written + + switch($days) { + case ($days > 1 && $days < 2): + if (Email_reminder::needsReminder(self::REGISTER_REMINDER, $confirm, 1)) { + common_log(LOG_INFO, "Sending one day registration confirmation reminder to {$confirm->address}", __FILE__); + $subject = _m("Reminder - please confirm your registration!"); + return EmailReminderPlugin::sendReminder( + self::REGISTER_REMINDER, + $confirm, + $subject, + 1); + } else { + return true; + } + break; + case ($days > 3 && $days < 4): + if (Email_reminder::needsReminder(self::REGISTER_REMINDER, $confirm, 3)) { + common_log(LOG_INFO, "Sending three day registration confirmation reminder to {$confirm->address}", __FILE__); + $subject = _m("Second reminder - please confirm your registration!"); + return EmailReminderPlugin::sendReminder( + self::REGISTER_REMINDER, + $confirm, + $subject, + 3 + ); + } else { + return true; + } + break; + case ($days > 7 && $days < 8): + if (Email_reminder::needsReminder(self::REGISTER_REMINDER, $confirm, 7)) { + common_log(LOG_INFO, "Sending one week registration confirmation reminder to {$confirm->address}", __FILE__); + $subject = _m("Final reminder - please confirm your registration!"); + return EmailReminderPlugin::sendReminder( + self::REGISTER_REMINDER, + $confirm, + $subject, + 7 + ); + } else { + return true; + } + break; + } + return true; + } + +} diff --git a/plugins/EmailReminder/lib/userinvitereminderhandler.php b/plugins/EmailReminder/lib/userinvitereminderhandler.php new file mode 100644 index 0000000000..c4acecc62f --- /dev/null +++ b/plugins/EmailReminder/lib/userinvitereminderhandler.php @@ -0,0 +1,110 @@ +. + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Handler for queue items of type 'uinvrem' (user invite reminder) + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class UserInviteReminderHandler extends UserReminderHandler { + + const INVITE_REMINDER = 'invite'; + + /** + * Return transport keyword which identifies items this queue handler + * services; must be defined for all subclasses. + * + * Must be 8 characters or less to fit in the queue_item database. + * ex "email", "jabber", "sms", "irc", ... + * + * @return string + */ + function transport() { + return 'uinvrem'; + } + + /** + * Send an invitation reminder. We'll send one after one day, and then + * one after three days. + * + * @todo Abstract this stuff further + * + * @param Invitation $invitation + * @return boolean success value + */ + function sendNextReminder($invitation) + { + $invDate = strtotime($invitation->created); + $now = strtotime('now'); + + // Days since first invitation was sent + $days = ($now - $invDate) / 86499; // 60*60*24 = 86499 + + $siteName = common_config('site', 'name'); + + switch($days) { + case ($days > 1 && $days < 2): + if (Email_reminder::needsReminder(self::INVITE_REMINDER, $invitation, 1)) { + common_log(LOG_INFO, "Sending one day invitation reminder to {$invitation->address}", __FILE__); + $subject = _m("Reminder - You have been invited to join {$siteName}!"); + return EmailReminderPlugin::sendReminder( + self::INVITE_REMINDER, + $invitation, + $subject, + 1); + } else { + return true; + } + break; + case ($days > 3 && $days < 4): + if (Email_reminder::needsReminder(self::INVITE_REMINDER, $invitation, 3)) { + common_log(LOG_INFO, "Sending three day invitation reminder to {$invitation->address}", __FILE__); + $subject = _m("Final reminder - you have been invited to join {$siteName}!"); + return EmailReminderPlugin::sendReminder( + self::INVITE_REMINDER, + $invitation, + $subject, + 3 + ); + } else { + return true; + } + break; + } + return true; + } + +} diff --git a/plugins/EmailReminder/lib/userreminderhandler.php b/plugins/EmailReminder/lib/userreminderhandler.php new file mode 100644 index 0000000000..22f498762b --- /dev/null +++ b/plugins/EmailReminder/lib/userreminderhandler.php @@ -0,0 +1,63 @@ +. + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Handler for email reminder queue items + * + * @category Email + * @package StatusNet + * @author Zach Copley + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ +class UserReminderHandler extends QueueHandler { + + /** + * Send the next email reminder to the confirm address + * + * @param Confirm_address $confirm the confirmation email/code + * @return boolean true on success, false on failure + */ + function handle($confirm) { + return $this->sendNextReminder($confirm); + } + + /** + * Send the next reminder if one needs sending. + * Subclasses must override + * + * @param Confirm_address $confirm the confirmation email/code + */ + function sendNextReminder($confirm) + { + } +} diff --git a/plugins/EmailReminder/mail-src/invite-1 b/plugins/EmailReminder/mail-src/invite-1 new file mode 100644 index 0000000000..f1abf0b5d6 --- /dev/null +++ b/plugins/EmailReminder/mail-src/invite-1 @@ -0,0 +1,26 @@ +First reminder... + +%%arg.inviter%% has invited you to join them on %%site.name%%. + +%%site.name%% is a micro-blogging service that lets you keep +up-to-date with people you know and people who interest you. + +You can also share news about yourself, your thoughts, or your life +online with people who know about you. + +It's great for meeting new people who share your interests. + +You can see %%arg.inviter%%'s profile page on %%site.name%% here: + +> [%%arg.inviterurl%%](%%arg.inviterurl%%) + +If you'd like to try the service, click on the link below to accept +the invitation. + +> [%%arg.confirmurl%%](%%arg.confirmurl%%) + +If not, you can ignore this message. Thanks for your patience and your time. + +Sincerely, + +%%site.name%% diff --git a/plugins/EmailReminder/mail-src/invite-3 b/plugins/EmailReminder/mail-src/invite-3 new file mode 100644 index 0000000000..8955443b24 --- /dev/null +++ b/plugins/EmailReminder/mail-src/invite-3 @@ -0,0 +1,26 @@ +Second and final reminder... + +%%arg.inviter%% has invited you to join them on %%site.name%%. + +%%site.name%% is a micro-blogging service that lets you keep +up-to-date with people you know and people who interest you. + +You can also share news about yourself, your thoughts, or your life +online with people who know about you. + +It's great for meeting new people who share your interests. + +You can see %%arg.inviter%%'s profile page on %%site.name%% here: + +> [%%arg.inviterurl%%](%%arg.inviterurl%%) + +If you'd like to try the service, click on the link below to accept +the invitation. + +> [%%arg.confirmurl%%](%%arg.confirmurl%%) + +If not, you can ignore this message. Thanks for your patience and your time. + +Sincerely, + +%%site.name%% diff --git a/plugins/EmailReminder/mail-src/register-1 b/plugins/EmailReminder/mail-src/register-1 new file mode 100644 index 0000000000..9461842407 --- /dev/null +++ b/plugins/EmailReminder/mail-src/register-1 @@ -0,0 +1,9 @@ +Hey, it's been a whole day! + +Someone (probably you) has requested an account on %%site.name%% using this email address. + +To confirm the address, click the following URL or copy it into the address bar of your browser. + +> [%%arg.confirmurl%%](%%arg.confirmurl%%) + +If it was not you, you can safely ignore this message. diff --git a/plugins/EmailReminder/mail-src/register-3 b/plugins/EmailReminder/mail-src/register-3 new file mode 100644 index 0000000000..3065622477 --- /dev/null +++ b/plugins/EmailReminder/mail-src/register-3 @@ -0,0 +1,9 @@ +Hey, it's been three days!! + +Someone (probably you) has requested an account on %%site.name%% using this email address. + +To confirm the address, click the following URL or copy it into the address bar of your browser. + +> [%%arg.confirmurl%%](%%arg.confirmurl%%) + +If it was not you, you can safely ignore this message. diff --git a/plugins/EmailReminder/mail-src/register-7 b/plugins/EmailReminder/mail-src/register-7 new file mode 100644 index 0000000000..75db0ebfeb --- /dev/null +++ b/plugins/EmailReminder/mail-src/register-7 @@ -0,0 +1,9 @@ +IT'S BEEN A WHOLE WEEK! Final reminder... + +Someone (probably you) has requested an account on %%site.name%% using this email address. + +To confirm the address, click the following URL or copy it into the address bar of your browser. + +> [%%arg.confirmurl%%](%%arg.confirmurl%%) + +If it was not you, you can safely ignore this message. diff --git a/plugins/EmailReminder/scripts/cron.sample b/plugins/EmailReminder/scripts/cron.sample new file mode 100644 index 0000000000..91981e73d3 --- /dev/null +++ b/plugins/EmailReminder/scripts/cron.sample @@ -0,0 +1 @@ +* * * * * /opt/local/bin/php /path/to/statusnet/plugins/EmailReminder/scripts/sendemailreminder.php -t all -a -q > /tmp/cron.log diff --git a/plugins/EmailReminder/scripts/sendemailreminder.php b/plugins/EmailReminder/scripts/sendemailreminder.php new file mode 100644 index 0000000000..4cb7087fc7 --- /dev/null +++ b/plugins/EmailReminder/scripts/sendemailreminder.php @@ -0,0 +1,132 @@ +#!/usr/bin/env php +. +*/ + +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..')); + +$shortoptions = 't:e:au'; +$longoptions = array('type=', 'email=', 'all', 'universe'); + +$helptext = << array( + 'type' => 'register', + 'className' => 'Confirm_address', + 'utransport' => 'uregrem' + ), + // invitation confirmation reminder + 'invite' => array( + 'type' => 'invite', + 'className' => 'Invitation', + 'utransport' => 'uinvrem' + ) + // ... add more here +); + +$type = null; + +if (have_option('t', 'type')) { + $type = trim(get_option_value('t', 'type')); + if (!in_array($type, array_keys($types)) && $type !== 'all') { + print _m("Unknown reminder type: {$type}.\n"); + exit(1); + } +} else { + show_help(); + exit(1); +} + +$reminders = array(); + +switch($type) { +case 'register': + $reminders[] = $types['register']; + break; +case 'invite': + $reminders[] = $types['invite']; + break; +case 'all': + $reminders = $types; + break; +} + +if (have_option('u', 'universe')) { + $sn = new Status_network(); + if ($sn->find()) { + while ($sn->fetch()) { + $server = $sn->getServerName(); + StatusNet::init($server); + // Different queue manager, maybe! + $qm = QueueManager::get(); + foreach ($reminders as $reminder) { + extract($reminder); + $qm->enqueue($type, 'siterem'); + if (!$quiet) { print "Sent pending {$type} reminders to all unconfirmed addresses in the known universe.\n"; } + } + } + } +} else { + $qm = QueueManager::get(); + try { + // enqueue reminder for specific email address or all unconfirmed addresses + if (have_option('e', 'email')) { + $address = trim(get_option_value('e', 'email')); + foreach ($reminders as $reminder) { + // real bad voodoo here + extract($reminder); + $confirm = new $className; + $confirm->address = $address; + $result = $confirm->find(true); + if (empty($result)) { + throw new Exception("No confirmation code found for {$address}."); + } + $qm->enqueue($confirm, $utransport); + if (!$quiet) { print "Sent all pending {$type} reminder to {$address}.\n"; } + } + } else if (have_option('a', 'all')) { + foreach ($reminders as $reminder) { + extract($reminder); + $qm->enqueue($type, 'siterem'); + if (!$quiet) { print "Sent pending {$type} reminders to all unconfirmed addresses on the site.\n"; } + } + } else { + show_help(); + exit(1); + } + } catch (Exception $e) { + if (!$quiet) { print $e->getMessage() . "\n"; } + common_log(LOG_ERR, $e->getMessage(), __FILE__); + exit(1); + } +}