From b7aacc08183bad5eb6f1eb4ce3d964aa56338f3c Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Fri, 10 Jun 2011 17:09:20 +0200 Subject: [PATCH 1/5] Update translator documentation and L10n. --- actions/siteadminpanel.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/actions/siteadminpanel.php b/actions/siteadminpanel.php index 11636b0b5c..bc96a6d73e 100644 --- a/actions/siteadminpanel.php +++ b/actions/siteadminpanel.php @@ -157,13 +157,13 @@ class SiteadminpanelAction extends AdminPanelAction // Validate logos if (!empty($values['site']['logo']) && !Validate::uri($values['site']['logo'], array('allowed_schemes' => array('http', 'https')))) { - // TRANS: Client error displayed when a logo URL does is not valid. + // TRANS: Client error displayed when a logo URL is not valid. $this->clientError(_('Invalid logo URL.')); } if (!empty($values['site']['ssllogo']) && !Validate::uri($values['site']['ssllogo'], array('allowed_schemes' => array('https')))) { - // TRANS: Client error displayed when an SSL logo URL is invalid. + // TRANS: Client error displayed when a SSL logo URL is invalid. $this->clientError(_('Invalid SSL logo URL.')); } @@ -196,7 +196,7 @@ class SiteadminpanelAction extends AdminPanelAction if (!Validate::number($values['site']['dupelimit'], array('min' => 1))) { // TRANS: Client error displayed trying to save site settings with a text limit below 1. - $this->clientError(_("Dupe limit must be one or more seconds.")); + $this->clientError(_('Dupe limit must be one or more seconds.')); } } } @@ -302,7 +302,7 @@ class SiteAdminPanelForm extends AdminForm _('Default language'), get_nice_language_list(), // TRANS: Dropdown title on site settings panel. - _('Site language when autodetection from browser settings is not available'), + _('The site language when autodetection from browser settings is not available.'), false, $this->value('language')); $this->unli(); @@ -374,6 +374,6 @@ class SiteAdminPanelForm extends AdminForm 'submit', null, // TRANS: Button title for saving site settings. - _('Save site settings')); + _('Save the site settings.')); } } From d6fe675fbe836b875dd272b00cd13f13a38f506e Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Wed, 15 Jun 2011 13:12:36 +0200 Subject: [PATCH 2/5] Update translator documentation per request of Nikerabbit. --- actions/approvesub.php | 1 + 1 file changed, 1 insertion(+) diff --git a/actions/approvesub.php b/actions/approvesub.php index be07b6e877..5fbb2149bd 100644 --- a/actions/approvesub.php +++ b/actions/approvesub.php @@ -73,6 +73,7 @@ class ApprovesubAction extends Action if (empty($this->request)) { // TRANS: Client error displayed trying to approve subscription for a non-existing request. + // TRANS: %s is a user nickname. $this->clientError(sprintf(_('%s is not in the moderation queue for your subscriptions.'), $this->profile->nickname), 403); } From bbb240e47e9bead36a9683a31ab66307e0d7e6a4 Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Wed, 15 Jun 2011 13:20:23 +0200 Subject: [PATCH 3/5] Update translator documentation. Whitespace updates. Line break changes in README. i18n updates. --- plugins/XCache/XCachePlugin.php | 2 +- plugins/Xmpp/README | 11 +++--- plugins/Xmpp/XmppPlugin.php | 69 +++++++++++++++++++-------------- plugins/Xmpp/xmppmanager.php | 8 ++-- 4 files changed, 50 insertions(+), 40 deletions(-) diff --git a/plugins/XCache/XCachePlugin.php b/plugins/XCache/XCachePlugin.php index 2baa290ed2..532714315f 100644 --- a/plugins/XCache/XCachePlugin.php +++ b/plugins/XCache/XCachePlugin.php @@ -117,8 +117,8 @@ class XCachePlugin extends Plugin 'author' => 'Craig Andrews', 'homepage' => 'http://status.net/wiki/Plugin:XCache', 'rawdescription' => + // TRANS: Plugin description. _m('Use the XCache variable cache to cache query results.')); return true; } } - diff --git a/plugins/Xmpp/README b/plugins/Xmpp/README index 9bd71e9807..96b0f3291a 100644 --- a/plugins/Xmpp/README +++ b/plugins/Xmpp/README @@ -1,4 +1,5 @@ -The XMPP plugin allows users to send and receive notices over the XMPP/Jabber/GTalk network. +The XMPP plugin allows users to send and receive notices over the +XMPP/Jabber/GTalk network. Installation ============ @@ -6,9 +7,10 @@ add "addPlugin('xmpp', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php -The daemon included with this plugin must be running. It will be started by -the plugin along with their other daemons when you run scripts/startdaemons.sh. -See the StatusNet README for more about queuing and daemons. +The daemon included with this plugin must be running. It will be +started by the plugin along with their other daemons when you run +scripts/startdaemons.sh. See the StatusNet README for more about queuing and +daemons. Settings ======== @@ -32,4 +34,3 @@ addPlugin('xmpp', array( 'password'=>'...', 'public'=>array('bob@aol.com', 'sue@google.com') )); - diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index f7df6812cf..ece4acac37 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -44,7 +44,6 @@ if (!defined('STATUSNET')) { * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ - class XmppPlugin extends ImPlugin { public $server = null; @@ -59,22 +58,22 @@ class XmppPlugin extends ImPlugin public $transport = 'xmpp'; function getDisplayName(){ + // TRANS: Plugin display name. return _m('XMPP/Jabber/GTalk'); } /** * Splits a Jabber ID (JID) into node, domain, and resource portions. - * + * * Based on validation routine submitted by: * @copyright 2009 Patrick Georgi - * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. + * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. * * @param string $jid string to check * * @return array with "node", "domain", and "resource" indices * @throws Exception if input is not valid */ - protected function splitJid($jid) { $chars = ''; @@ -102,11 +101,11 @@ class XmppPlugin extends ImPlugin $chars .= "\x{340}\x{341}\x{200e}\x{200f}\x{202a}-\x{202e}\x{206a}-\x{206f}"; /* C9 - Tagging characters */ $chars .= "\x{e0001}\x{e0020}-\x{e007f}"; - + /* Nodeprep forbids some more characters */ $nodeprepchars = $chars; $nodeprepchars .= "\x{22}\x{26}\x{27}\x{2f}\x{3a}\x{3c}\x{3e}\x{40}"; - + $parts = explode("/", $jid, 2); if (count($parts) > 1) { $resource = $parts[1]; @@ -117,10 +116,11 @@ class XmppPlugin extends ImPlugin } else { $resource = null; } - + $node = explode("@", $parts[0]); if ((count($node) > 2) || (count($node) == 0)) { - throw new Exception("Invalid JID: too many @s"); + // TRANS: Exception thrown when using too many @ signs in a Jabber ID. + throw new Exception(_m('Invalid JID: too many @s.')); } else if (count($node) == 1) { $domain = $node[0]; $node = null; @@ -128,47 +128,57 @@ class XmppPlugin extends ImPlugin $domain = $node[1]; $node = $node[0]; if ($node == '') { - throw new Exception("Invalid JID: @ but no node"); + // TRANS: Exception thrown when using @ sign not followed by a Jabber ID. + throw new Exception(_m('Invalid JID: @ but no node')); } } - + // Length limits per http://xmpp.org/rfcs/rfc3920.html#addressing if ($node !== null) { if (strlen($node) > 1023) { - throw new Exception("Invalid JID: node too long."); + // TRANS: Exception thrown when using too long a Jabber ID (>1023). + throw new Exception(_m('Invalid JID: node too long.')); } if (preg_match("/[".$nodeprepchars."]/u", $node)) { - throw new Exception("Invalid JID node '$node'"); + // TRANS: Exception thrown when using an invalid Jabber ID. + // TRANS: %s is the invalid Jabber ID. + throw new Exception(sprintf(_m('Invalid JID node "%s".'),$node)); } } - + if (strlen($domain) > 1023) { - throw new Exception("Invalid JID: domain too long."); + // TRANS: Exception thrown when using too long a Jabber domain (>1023). + throw new Exception(_m('Invalid JID: domain too long.')); } if (!common_valid_domain($domain)) { - throw new Exception("Invalid JID domain name '$domain'"); + // TRANS: Exception thrown when using an invalid Jabber domain name. + // TRANS: %s is the invalid domain name. + throw new Exception(sprintf(_m('Invalid JID domain name "%s".'),$domain)); } - + if ($resource !== null) { if (strlen($resource) > 1023) { + // TRANS: Exception thrown when using too long a resource (>1023). throw new Exception("Invalid JID: resource too long."); } if (preg_match("/[".$chars."]/u", $resource)) { - throw new Exception("Invalid JID resource '$resource'"); + // TRANS: Exception thrown when using an invalid Jabber resource. + // TRANS: %s is the invalid resource. + throw new Exception(sprintf(_m('Invalid JID resource "%s".'),$resource)); } } - + return array('node' => is_null($node) ? null : mb_strtolower($node), 'domain' => is_null($domain) ? null : mb_strtolower($domain), 'resource' => $resource); } - + /** * Checks whether a string is a syntactically valid Jabber ID (JID), * either with or without a resource. - * + * * Note that a bare domain can be a valid JID. - * + * * @param string $jid string to check * @param bool $check_domain whether we should validate that domain... * @@ -188,15 +198,15 @@ class XmppPlugin extends ImPlugin return false; } } - + /** * Checks whether a string is a syntactically valid base Jabber ID (JID). * A base JID won't include a resource specifier on the end; since we * take it off when reading input we can't really use them reliably * to direct outgoing messages yet (sorry guys!) - * + * * Note that a bare domain can be a valid JID. - * + * * @param string $jid string to check * @param bool $check_domain whether we should validate that domain... * @@ -225,7 +235,6 @@ class XmppPlugin extends ImPlugin * * @return string an equivalent JID in normalized (lowercase) form */ - function normalize($jid) { try { @@ -308,7 +317,7 @@ class XmppPlugin extends ImPlugin function microiduri($screenname) { - return 'xmpp:' . $screenname; + return 'xmpp:' . $screenname; } function sendMessage($screenname, $body) @@ -320,7 +329,7 @@ class XmppPlugin extends ImPlugin { $msg = $this->formatNotice($notice); $entry = $this->format_entry($notice); - + $this->queuedConnection()->message($screenname, $msg, 'chat', null, $entry); return true; } @@ -333,7 +342,6 @@ class XmppPlugin extends ImPlugin * * @return string Extra information (Atom, HTML, addresses) in string format */ - function format_entry($notice) { $profile = $notice->getProfile(); @@ -355,6 +363,7 @@ class XmppPlugin extends ImPlugin $xs->element('a', array( 'href'=>common_local_url('conversation', array('id' => $notice->conversation)).'#notice-'.$notice->id), + // TRANS: Link description to notice in conversation. // TRANS: %s is a notice ID. sprintf(_m('[%s]'),$notice->id)); $xs->elementEnd('body'); @@ -380,14 +389,14 @@ class XmppPlugin extends ImPlugin } $this->handleIncoming($from, $pl['body']); - + return true; } /** * Build a queue-proxied XMPP interface object. Any outgoing messages * will be run back through us for enqueing rather than sent directly. - * + * * @return Queued_XMPP * @throws Exception if server settings are invalid. */ diff --git a/plugins/Xmpp/xmppmanager.php b/plugins/Xmpp/xmppmanager.php index 4aaed677b5..f6a9b40a1d 100644 --- a/plugins/Xmpp/xmppmanager.php +++ b/plugins/Xmpp/xmppmanager.php @@ -29,16 +29,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } * In a multi-site queuedaemon.php run, one connection will be instantiated * for each site being handled by the current process that has XMPP enabled. */ - class XmppManager extends ImManager { protected $lastping = null; protected $pingid = null; public $conn = null; - + const PING_INTERVAL = 120; - /** * Initialize connection to server. @@ -114,7 +112,7 @@ class XmppManager extends ImManager * * Side effect: kills process on exception from XMPP library. * - * @fixme non-dying error handling + * @todo FIXME: non-dying error handling */ public function idle($timeout=0) { @@ -165,6 +163,7 @@ class XmppManager extends ImManager } $this->conn->processUntil('session_start'); + // TRANS: Presence announcement for XMPP. $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); } return $this->conn; @@ -204,6 +203,7 @@ class XmppManager extends ImManager common_log(LOG_NOTICE, 'XMPP reconnected'); $this->conn->processUntil('session_start'); + // TRANS: Message for XMPP reconnect. $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); } From b24e4fd9fe4e85e6612542b7756578d72d0f95ee Mon Sep 17 00:00:00 2001 From: Siebrand Mazeland Date: Wed, 15 Jun 2011 13:24:53 +0200 Subject: [PATCH 4/5] Fix typo. Spotted by Nikerabbit. --- actions/recoverpassword.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/recoverpassword.php b/actions/recoverpassword.php index 8d731cb871..d81c13b005 100644 --- a/actions/recoverpassword.php +++ b/actions/recoverpassword.php @@ -260,7 +260,7 @@ class RecoverpasswordAction extends Action $this->elementStart('li'); // TRANS: Field label for password reset form where the password has to be typed again. $this->password('confirm', _('Confirm'), - // TRANS: Ttile for field label for password reset form where the password has to be typed again. + // TRANS: Title for field label for password reset form where the password has to be typed again. _('Same as password above.')); $this->elementEnd('li'); $this->elementEnd('ul'); From a35392da2f96f30a065ec1cd1de3d553ce31776a Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 17 Jun 2011 02:24:34 -0700 Subject: [PATCH 5/5] 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); + } +}