Commit a35392da authored by Zach Copley's avatar Zach Copley

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 <zach@status.net>
Date:   Fri Jun 17 02:17:31 2011 -0700

    * Set the reminder interval correctly

commit ae0ded8cf95210f54b4cd58dac0eeeedf2d99c67
Author: Zach Copley <zach@status.net>
Date:   Fri Jun 17 02:15:01 2011 -0700

    Send email reminders for invitations

commit 1b596d08f5dbe765a16fbdfbd21e2ad68e8b0058
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 16 23:53:48 2011 -0700

    Handle multiple confirmation types

commit 25d83351d878f39498cd6a14fddde27f1afef2ca
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 16 18:04:57 2011 -0700

    Actually send reminders and record a record of doing so

commit 9ffc2dbee15cacc7e7f9feab492185ee9964a17e
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 16 14:20:16 2011 -0700

    Make the queue handling actually work

commit 2a6ce3c17c045bdb0a3ddf36f2c290c9c48eb003
Author: Zach Copley <zach@status.net>
Date:   Thu Jun 16 13:27:56 2011 -0700

    Fix syntax errors

commit 054b54847dfadc490aa7d7dff12d473af31c99bb
Author: Zach Copley <zach@status.net>
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 <zach@status.net>
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 <zach@status.net>
Date:   Tue Jun 14 09:23:23 2011 -0700

    Most of the other classes needed to send email reminders

commit 4e9bb11dbb23556bf5c1847e7a127084b5cc217c
Author: Zach Copley <zach@status.net>
Date:   Mon Jun 13 12:10:55 2011 -0700

    size -> length

commit a9ea80ef8abae1e64d5713091baedd931b7184e2
Author: Zach Copley <zach@status.net>
Date:   Fri Jun 10 16:38:06 2011 -0400

    Stubby EmailReminderPlugin and data class

commit 5d893f982209b245cb9113a59e49721dd6e191b6
Author: Zach Copley <zach@status.net>
Date:   Fri Jun 10 14:01:48 2011 -0400

    Remove bogus data class

commit f74de8841a98add73536fd8a4d3cee76035b491c
Author: Zach Copley <zach@status.net>
Date:   Fri Jun 10 16:38:06 2011 -0400

    Stubby EmailReminderPlugin and data class

commit 5b14370918233e5112a95da94567c4ed83429bc9
Author: Zach Copley <zach@status.net>
Date:   Fri Jun 10 14:01:48 2011 -0400

    Remove bogus data class
parent b24e4fd9
<?php
/**
* Data class for counting greetings
*
* PHP version 5
*
* @category Data
* @package StatusNet
* @author Evan Prodromou <evan@status.net>
* @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 <http://www.gnu.org/licenses/>.
*/
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 <evan@status.net>
* @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;
}
}
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Plugin for sending email reminders about various things
*
* PHP version 5
*
* 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 <http://www.gnu.org/licenses/>.
*
* @category OnDemand
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 <zach@status.net>
* @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;
}
}
<?php
/**
* StatusNet - the distributed open-source microblogging tool
* Copyright (C) 2011, StatusNet, Inc.
*
* Data class for email reminders
*
* 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 <http://www.gnu.org/licenses/>.
*
* @category Data
* @package EmailReminder
* @author Zach Copley <zach@status.net>
* @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'),
),
);
}
}
<?php
/*
* StatusNet - the distributed open-source microblogging tool
*
* Handler for reminder queue items which send reminder emails to all users
* we would like to complete a given process (e.g.: registration).
*
* 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 <http://www.gnu.org/licenses/>.
*
* @category Email
* @package StatusNet
* @author Zach Copley <zach@status.net>
* @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 <zach@status.net>
* @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;
}
}
<?php
/**
* StatusNet - the distributed open-source microblogging tool
*
* Handler for queue items of type 'uregem' - sends email registration
* confirmation reminders to a particular user.
*
* 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.
*