code:
# Translation of StatusNet - EmailSummary to Italian (italiano)
# Exported from translatewiki.net
#
# Author: Ximo17
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:27+0000\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-07-01 11:18:46+0000\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Subject for e-mail.
#, php-format
msgid "Your latest updates from %s"
msgstr "I tuoi aggiornamenti più recenti su %s"

#. TRANS: Text in e-mail summary.
#. TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
#, php-format
msgid "Recent updates from %1$s for %2$s:"
msgstr "Aggiornamenti recenti da  %1$s di %2$s:"

#. TRANS: Link text for link to conversation view.
msgid "in context"
msgstr "in una discussione"

#. TRANS: Link text for link to e-mail settings.
#. TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
#, php-format
msgid "change your email settings for %2$s"
msgstr "modifica le tue impostazioni email per %2$s"

#. TRANS: Plugin description.
msgid "Send an email summary of the inbox to users."
msgstr "Invia un riepilogo di e-mail di posta in arrivo agli utenti."

#. TRANS: Checkbox label in e-mail preferences form.
msgid "Send me a periodic summary of updates from my network"
msgstr "Mandami un riepilogo periodico degli aggiornamenti dalla mia rete"


code:
# Translation of StatusNet - EmailSummary to Macedonian (македонски)
# Exported from translatewiki.net
#
# Author: Bjankuloski06
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:27+0000\n"
"Language-Team: Macedonian <https://translatewiki.net/wiki/Portal:mk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-07-01 11:18:46+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: mk\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=2; plural=(n == 1 || n%10 == 1) ? 0 : 1;\n"

#. TRANS: Subject for e-mail.
#, php-format
msgid "Your latest updates from %s"
msgstr "Најново од %s"

#. TRANS: Text in e-mail summary.
#. TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
#, php-format
msgid "Recent updates from %1$s for %2$s:"
msgstr "Скорешни поднови од %1$s за %2$s:"

#. TRANS: Link text for link to conversation view.
msgid "in context"
msgstr "во контекст"

#. TRANS: Link text for link to e-mail settings.
#. TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
#, php-format
msgid "change your email settings for %2$s"
msgstr "сменете си ги нагодувањата за е-пошта на %2$s"

#. TRANS: Plugin description.
msgid "Send an email summary of the inbox to users."
msgstr "Испрати им на корисниците краток преглед на примената пошта."

#. TRANS: Checkbox label in e-mail preferences form.
msgid "Send me a periodic summary of updates from my network"
msgstr "Испраќај ми повремен краток преглед на подновите од мојата мрежа."

license: mit


code:
# Translation of StatusNet - EmailSummary to Dutch (Nederlands)
# Exported from translatewiki.net
#
# Author: SPQRobin
# Author: Siebrand
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:27+0000\n"
"Language-Team: Dutch <https://translatewiki.net/wiki/Portal:nl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-07-01 11:18:46+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: nl\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Subject for e-mail.
#, php-format
msgid "Your latest updates from %s"
msgstr "Uw recentste updates van %s"

#. TRANS: Text in e-mail summary.
#. TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
#, php-format
msgid "Recent updates from %1$s for %2$s:"
msgstr "Recente updates van %1$s voor %2$s:"

#. TRANS: Link text for link to conversation view.
msgid "in context"
msgstr "in context"

#. TRANS: Link text for link to e-mail settings.
#. TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
#, php-format
msgid "change your email settings for %2$s"
msgstr "wijzig uw e-mailinstellingen voor %2$s"

#. TRANS: Plugin description.
msgid "Send an email summary of the inbox to users."
msgstr "E-mailsamenvatting verzenden naar het Postvak IN van gebruikers."

#. TRANS: Checkbox label in e-mail preferences form.
msgid "Send me a periodic summary of updates from my network"
msgstr "Stuur mij periodiek een samenvatting van de updates in mijn netwerk"


code:
# Translation of StatusNet - EmailSummary to Portuguese (Português)
# Exported from translatewiki.net
#
# Author: Waldir
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:13+0000\n"
"Language-Team: Portuguese <http://translatewiki.net/wiki/Portal:pt>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-18 20:07:04+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: pt\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#, php-format
msgid "Recent updates from %1s for %2s:"
msgstr ""

msgid "in context"
msgstr ""

#, php-format
msgid "
change your email settings for %2s
"
msgstr ""

msgid "Updates from your network"
msgstr ""

msgid "Send an email summary of the inbox to users."
msgstr "Enviar por email um resumo da caixa de entrada aos utilizadores."

#. TRANS: Checkbox label in e-mail preferences form.
#, fuzzy
msgid "Send me a periodic summary of updates from my network."
msgstr "Enviar por email um resumo da caixa de entrada aos utilizadores."


code:
# Translation of StatusNet - EmailSummary to Russian (Русский)
# Exported from translatewiki.net
#
# Author: Lockal
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:13+0000\n"
"Language-Team: Russian <http://translatewiki.net/wiki/Portal:ru>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-18 20:07:04+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: ru\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#, php-format
msgid "Recent updates from %1s for %2s:"
msgstr ""

msgid "in context"
msgstr ""

#, php-format
msgid "
change your email settings for %2s
"
msgstr ""

msgid "Updates from your network"
msgstr ""

msgid "Send an email summary of the inbox to users."
msgstr ""
"Отправляет обзор входящих сообщений на электронную почту пользователей."

#. TRANS: Checkbox label in e-mail preferences form.
#, fuzzy
msgid "Send me a periodic summary of updates from my network."
msgstr ""
"Отправляет обзор входящих сообщений на электронную почту пользователей."


code:
# Translation of StatusNet - EmailSummary to Tagalog (Tagalog)
# Exported from translatewiki.net
#
# Author: AnakngAraw
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:27+0000\n"
"Language-Team: Tagalog <https://translatewiki.net/wiki/Portal:tl>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-07-01 11:18:46+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: tl\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: Subject for e-mail.
#, php-format
msgid "Your latest updates from %s"
msgstr "Pinakahuli mong mga pagsasapanahon mula sa %s"

#. TRANS: Text in e-mail summary.
#. TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
#, php-format
msgid "Recent updates from %1$s for %2$s:"
msgstr "Kamakailang mga pagsasapanahon magmula sa %1$s para kay %2$s:"

#. TRANS: Link text for link to conversation view.
msgid "in context"
msgstr "sa loob ng diwa"

#. TRANS: Link text for link to e-mail settings.
#. TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
#, php-format
msgid "change your email settings for %2$s"
msgstr ""
"baguhin ang mga katakdaan mo ng e-liham para sa %2$s"

#. TRANS: Plugin description.
msgid "Send an email summary of the inbox to users."
msgstr ""
"Magpadala sa mga tagagamit ng isang buod mula sa e-liham ng kahong-tanggapan."

#. TRANS: Checkbox label in e-mail preferences form.
msgid "Send me a periodic summary of updates from my network"
msgstr ""
"Padalhan ako ng isang pana-panahong mga buod ng mga pagsasapanahon mula sa "
"lambatan ko"


code:
# Translation of StatusNet - EmailSummary to Ukrainian (українська)
# Exported from translatewiki.net
#
# Author: Boogie
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2012-06-30 11:07+0000\n"
"PO-Revision-Date: 2012-06-30 11:08:27+0000\n"
"Language-Team: Ukrainian <https://translatewiki.net/wiki/Portal:uk>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-07-01 11:18:46+0000\n"
"X-Generator: MediaWiki 1.20alpha (233fc08); Translate 2012-06-21\n"
"X-Translation-Project: translatewiki.net <https://translatewiki.net>\n"
"X-Language-Code: uk\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=3; plural=(n%10 == 1 && n%100 != 11) ? 0 : ( (n%10 >= "
"2 && n%10 <= 4 && (n%100 < 10 || n%100 >= 20)) ? 1 : 2 );\n"

#. TRANS: Subject for e-mail.
#, php-format
msgid "Your latest updates from %s"
msgstr ""

#. TRANS: Text in e-mail summary.
#. TRANS: %1$s is the StatusNet sitename, %2$s is the recipient's profile name.
#, php-format
msgid "Recent updates from %1$s for %2$s:"
msgstr "Поточні оновлення від %1$s для %2$s:"

#. TRANS: Link text for link to conversation view.
msgid "in context"
msgstr "у контексті"

#. TRANS: Link text for link to e-mail settings.
#. TRANS: %1$s is a link to the e-mail settings, %2$s is the StatusNet sitename.
#, php-format
msgid "change your email settings for %2$s"
msgstr "змінити налаштування електронної пошти для %2$s"

#. TRANS: Plugin description.
msgid "Send an email summary of the inbox to users."
msgstr ""
"Надсилає електронною поштою перелік вхідних приватних повідомлень, "
"адресованих користувачеві."

#. TRANS: Checkbox label in e-mail preferences form.
msgid "Send me a periodic summary of updates from my network"
msgstr ""
"Надсилати мені електронною поштою сумарний перелік оновлень моєї мережі."


code:
# Translation of StatusNet - EmailSummary to Simplified Chinese (中文(简体))
# Exported from translatewiki.net
#
# Author: Hydra
# --
# This file is distributed under the same license as the StatusNet package.
#
msgid ""
msgstr ""
"Project-Id-Version: StatusNet - EmailSummary\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-03-31 21:06+0000\n"
"PO-Revision-Date: 2011-03-31 21:09:13+0000\n"
"Language-Team: Simplified Chinese <http://translatewiki.net/wiki/Portal:zh-"
"hans>\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-POT-Import-Date: 2011-03-18 20:07:04+0000\n"
"X-Generator: MediaWiki 1.18alpha (r85082); Translate extension (2011-03-11)\n"
"X-Translation-Project: translatewiki.net at http://translatewiki.net\n"
"X-Language-Code: zh-hans\n"
"X-Message-Group: #out-statusnet-plugin-emailsummary\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#, php-format
msgid "Recent updates from %1s for %2s:"
msgstr ""

msgid "in context"
msgstr ""

#, php-format
msgid "
change your email settings for %2s
"
msgstr ""

msgid "Updates from your network"
msgstr ""

msgid "Send an email summary of the inbox to users."
msgstr "发送到用户的收件箱的电子邮件综述。"

#. TRANS: Checkbox label in e-mail preferences form.
#, fuzzy
msgid "Send me a periodic summary of updates from my network."
msgstr "发送到用户的收件箱的电子邮件综述。"


code:
<?php
/*
 * StatusNet - a distributed open-source microblogging tool
 * Copyright (C) 2010, 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/>.
 */

define('INSTALLDIR', realpath(dirname(__FILE__) . '/../../..'));

$shortoptions = 'i:n:au';
$longoptions = array('id=', 'nickname=', 'all', 'universe');

$helptext = <<<END_OF_SENDEMAILSUMMARY_HELP
sendemailsummary.php [options]
Send an email summary of the inbox to users

 -i --id       ID of user to send summary to
 -n --nickname nickname of the user to send summary to
 -a --all      send summary to all users
 -u --universe send summary to all users on all sites

END_OF_SENDEMAILSUMMARY_HELP;

require_once INSTALLDIR.'/scripts/commandline.inc';

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();
            $qm->enqueue(1, 'sitesum');
        }
    }
} else {
    $qm = QueueManager::get();
    // enqueue summary for user or all users
    try {
        $user = getUser();
        $qm->enqueue($user->id, 'usersum');
    } catch (NoUserArgumentException $nuae) {
        $qm->enqueue(1, 'sitesum');
    }
}


code:
This doesn't seem to have been functional for a while; can't find other references
to the enjit configuration or transport enqueuing. Keeping it in case someone
wants to bring it up to date.

-- brion vibber <brion@status.net> 2009-12-03

path: plugins/Enjit/README


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008, 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') && !defined('LACONICA')) {
    exit(1);
}

/**
 * Queue handler for watching new notices and posting to enjit.
 * @todo FIXME: Is this actually being used/functional atm?
 */
class EnjitQueueHandler extends QueueHandler
{
    function transport()
    {
        return 'enjit';
    }

    function handle($notice)
    {

        $profile = Profile::getKV($notice->profile_id);

        $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname);

        if ( ! $notice->is_local ) {
            $this->log(LOG_INFO, "Skipping remote notice");
            return "skipped";
        }

        #
        // Build an Atom message from the notice
        #
        $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
        $msg = $profile->nickname . ': ' . $notice->content;

        $atom  = "<entry xmlns='http://www.w3.org/2005/Atom'>\n";
        $atom .= "<apisource>".common_config('enjit','source')."</apisource>\n";
        $atom .= "<source>\n";
        $atom .= "<title>" . $profile->nickname . " - " . common_config('site', 'name') . "</title>\n";
        $atom .= "<link href='" . $profile->profileurl . "'/>\n";
        $atom .= "<link rel='self' type='application/rss+xml' href='" . common_local_url('userrss', array('nickname' => $profile->nickname)) . "'/>\n";
        $atom .= "<author><name>" . $profile->nickname . "</name></author>\n";
        $atom .= "<icon>" . $profile->avatarUrl(AVATAR_PROFILE_SIZE) . "</icon>\n";
        $atom .= "</source>\n";
        $atom .= "<title>" . htmlspecialchars($msg) . "</title>\n";
        $atom .= "<summary>" . htmlspecialchars($msg) . "</summary>\n";
        $atom .= "<link rel='alternate' href='" . $noticeurl . "' />\n";
        $atom .= "<id>". $notice->uri . "</id>\n";
        $atom .= "<published>".common_date_w3dtf($notice->created)."</published>\n";
        $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
        $atom .= "</entry>\n";

        $url  = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
        $data = array(
            'msg' => $atom,
        );

        #
        // POST the message to $config['enjit']['apiurl']
        #
        $request = HTTPClient::start();
        $response = $request->post($url, null, $data);

        return $response->isOk();
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Microapp plugin for event invitations and RSVPs
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * Event plugin
 *
 * @category  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 EventPlugin extends MicroAppPlugin
{
    /**
     * Set up our tables (event and rsvp)
     *
     * @see Schema
     * @see ColumnDef
     *
     * @return boolean hook value; true means continue processing, false means stop.
     */
    function onCheckSchema()
    {
        $schema = Schema::get();

        $schema->ensureTable('happening', Happening::schemaDef());
        $schema->ensureTable('rsvp', RSVP::schemaDef());

        return true;
    }

    /**
     * Map URLs to actions
     *
     * @param URLMapper $m path-to-action mapper
     *
     * @return boolean hook value; true means continue processing, false means stop.
     */
    public function onRouterInitialized(URLMapper $m)
    {
        $m->connect('main/event/new',
                    array('action' => 'newevent'));
        $m->connect('main/event/rsvp',
                    array('action' => 'newrsvp'));
        $m->connect('main/event/rsvp/cancel',
                    array('action' => 'cancelrsvp'));
        $m->connect('event/:id',
                    array('action' => 'showevent'),
                    array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
        $m->connect('rsvp/:id',
                    array('action' => 'showrsvp'),
                    array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
        $m->connect('main/event/updatetimes',
                    array('action' => 'timelist'));
        return true;
    }

    function onPluginVersion(&$versions)
    {
        $versions[] = array('name' => 'Event',
                            'version' => GNUSOCIAL_VERSION,
                            'author' => 'Evan Prodromou',
                            'homepage' => 'http://status.net/wiki/Plugin:Event',
                            'description' =>
                            // TRANS: Plugin description.
                            _m('Event invitations and RSVPs.'));
        return true;
    }

    function appTitle() {
        // TRANS: Title for event application.
        return _m('TITLE','Event');
    }

    function tag() {
        return 'event';
    }

    function types() {
        return array(Happening::OBJECT_TYPE,
                     RSVP::POSITIVE,
                     RSVP::NEGATIVE,
                     RSVP::POSSIBLE);
    }

    /**
     * Given a parsed ActivityStreams activity, save it into a notice
     * and other data structures.
     *
     * @param Activity $activity
     * @param Profile $actor
     * @param array $options=array()
     *
     * @return Notice the resulting notice
     */
    function saveNoticeFromActivity(Activity $activity, Profile $actor, array $options=array())
    {
        if (count($activity->objects) != 1) {
            // TRANS: Exception thrown when there are too many activity objects.
            throw new Exception(_m('Too many activity objects.'));
        }

        $happeningObj = $activity->objects[0];

        if ($happeningObj->type != Happening::OBJECT_TYPE) {
            // TRANS: Exception thrown when event plugin comes across a non-event type object.
            throw new Exception(_m('Wrong type for object.'));
        }

        $notice = null;

        switch ($activity->verb) {
        case ActivityVerb::POST:
        	// FIXME: get startTime, endTime, location and URL
            $notice = Happening::saveNew($actor,
                                         $start_time,
                                         $end_time,
                                         $happeningObj->title,
                                         null,
                                         $happeningObj->summary,
                                         null,
                                         $options);
            break;
        case RSVP::POSITIVE:
        case RSVP::NEGATIVE:
        case RSVP::POSSIBLE:
            $happening = Happening::getKV('uri', $happeningObj->id);
            if (empty($happening)) {
                // FIXME: save the event
                // TRANS: Exception thrown when trying to RSVP for an unknown event.
                throw new Exception(_m('RSVP for unknown event.'));
            }
            $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options);
            break;
        default:
            // TRANS: Exception thrown when event plugin comes across a undefined verb.
            throw new Exception(_m('Unknown verb for events.'));
        }

        return $notice;
    }

    /**
     * Turn a Notice into an activity object
     *
     * @param Notice $notice
     *
     * @return ActivityObject
     */
    function activityObjectFromNotice(Notice $notice)
    {
        $happening = null;

        switch ($notice->object_type) {
        case Happening::OBJECT_TYPE:
            $happening = Happening::fromNotice($notice);
            break;
        case RSVP::POSITIVE:
        case RSVP::NEGATIVE:
        case RSVP::POSSIBLE:
            $rsvp  = RSVP::fromNotice($notice);
            $happening = $rsvp->getEvent();
            break;
        }

        if (empty($happening)) {
            // TRANS: Exception thrown when event plugin comes across a unknown object type.
            throw new Exception(_m('Unknown object type.'));
        }

        $notice = $happening->getNotice();

        if (empty($notice)) {
            // TRANS: Exception thrown when referring to a notice that is not an event an in event context.
            throw new Exception(_m('Unknown event notice.'));
        }

        $obj = new ActivityObject();

        $obj->id      = $happening->uri;
        $obj->type    = Happening::OBJECT_TYPE;
        $obj->title   = $happening->title;
        $obj->summary = $happening->description;
        $obj->link    = $notice->getUrl();

        // XXX: how to get this stuff into JSON?!

        $obj->extra[] = array('dtstart',
                              array('xmlns' => 'urn:ietf:params:xml:ns:xcal'),
                              common_date_iso8601($happening->start_time));

        $obj->extra[] = array('dtend',
                              array('xmlns' => 'urn:ietf:params:xml:ns:xcal'),
                              common_date_iso8601($happening->end_time));

		// FIXME: add location
		// FIXME: add URL

        // XXX: probably need other stuff here

        return $obj;
    }

    /**
     * Change the verb on RSVP notices
     *
     * @param Notice $notice
     *
     * @return ActivityObject
     */
    protected function extendActivity(Notice $stored, Activity $act, Profile $scoped=null) {
        switch ($stored->object_type) {
        case RSVP::POSITIVE:
        case RSVP::NEGATIVE:
        case RSVP::POSSIBLE:
            $act->verb = $stored->object_type;
            break;
        }
        return true;
    }

    /**
     * Form for our app
     *
     * @param HTMLOutputter $out
     * @return Widget
     */
    function entryForm($out)
    {
        return new EventForm($out);
    }

    /**
     * When a notice is deleted, clean up related tables.
     *
     * @param Notice $notice
     */
    function deleteRelated(Notice $notice)
    {
        switch ($notice->object_type) {
        case Happening::OBJECT_TYPE:
            common_log(LOG_DEBUG, "Deleting event from notice...");
            $happening = Happening::fromNotice($notice);
            $happening->delete();
            break;
        case RSVP::POSITIVE:
        case RSVP::NEGATIVE:
        case RSVP::POSSIBLE:
            common_log(LOG_DEBUG, "Deleting rsvp from notice...");
            $rsvp = RSVP::fromNotice($notice);
            common_log(LOG_DEBUG, "to delete: $rsvp->id");
            $rsvp->delete();
            break;
        default:
            common_log(LOG_DEBUG, "Not deleting related, wtf...");
        }
    }

    function onEndShowScripts($action)
    {
        $action->script($this->path('js/event.js'));
    }

    function onEndShowStyles($action)
    {
        $action->cssLink($this->path('css/event.css'));
        return true;
    }

    function onStartAddNoticeReply($nli, $parent, $child)
    {
        // Filter out any poll responses
        if (($parent->object_type == Happening::OBJECT_TYPE) &&
            in_array($child->object_type, array(RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE))) {
            return false;
        }
        return true;
    }

    protected function showNoticeItemNotice(NoticeListItem $nli)
    {
        $nli->showAuthor();
        $nli->showContent();
    }

    protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
    {
        switch ($stored->object_type) {
        case Happening::OBJECT_TYPE:
            $this->showEvent($stored, $out, $scoped);
            break;
        case RSVP::POSITIVE:
        case RSVP::NEGATIVE:
        case RSVP::POSSIBLE:
            $this->showRSVP($stored, $out, $scoped);
            break;
        }
    }

    protected function showEvent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
    {
        $profile = $stored->getProfile();
        $event   = Happening::fromNotice($stored);

        if (!$event instanceof Happening) {
            // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
            $out->element('p', null, _m('Deleted.'));
            return;
        }

        $out->elementStart('div', 'h-event');

        $out->elementStart('h3', 'p-summary p-name');

        try {
            $out->element('a', array('href' => $event->getUrl()), $event->title);
        } catch (InvalidUrlException $e) {
            $out->text($event->title);
        }

        $out->elementEnd('h3');

        $now       = new DateTime();
        $startDate = new DateTime($event->start_time);
        $endDate   = new DateTime($event->end_time);
        $userTz    = new DateTimeZone(common_timezone());

        // Localize the time for the observer
        $now->setTimeZone($userTz);
        $startDate->setTimezone($userTz);
        $endDate->setTimezone($userTz);

        $thisYear  = $now->format('Y');
        $startYear = $startDate->format('Y');
        $endYear   = $endDate->format('Y');

        $dateFmt = 'D, F j, '; // e.g.: Mon, Aug 31

        if ($startYear != $thisYear || $endYear != $thisYear) {
            $dateFmt .= 'Y,'; // append year if we need to think about years
        }

        $startDateStr = $startDate->format($dateFmt);
        $endDateStr = $endDate->format($dateFmt);

        $timeFmt = 'g:ia';

        $startTimeStr = $startDate->format($timeFmt);
        $endTimeStr = $endDate->format("{$timeFmt} (T)");

        $out->elementStart('div', 'event-times'); // VEVENT/EVENT-TIMES IN

        // TRANS: Field label for event description.
        $out->element('strong', null, _m('Time:'));

        $out->element('time', array('class' => 'dt-start',
                                    'datetime' => common_date_iso8601($event->start_time)),
                      $startDateStr . ' ' . $startTimeStr);
        $out->text(' – ');
        $out->element('time', array('class' => 'dt-end',
                                    'datetime' => common_date_iso8601($event->end_time)),
                      $startDateStr != $endDateStr
                                    ? "$endDateStr $endTimeStr"
                                    :  $endTimeStr);

        $out->elementEnd('div'); // VEVENT/EVENT-TIMES OUT

        if (!empty($event->location)) {
            $out->elementStart('div', 'event-location');
            // TRANS: Field label for event description.
            $out->element('strong', null, _m('Location:'));
            $out->element('span', 'p-location', $event->location);
            $out->elementEnd('div');
        }

        if (!empty($event->description)) {
            $out->elementStart('div', 'event-description');
            // TRANS: Field label for event description.
            $out->element('strong', null, _m('Description:'));
            $out->element('div', 'p-description', $event->description);
            $out->elementEnd('div');
        }

        $rsvps = $event->getRSVPs();

        $out->elementStart('div', 'event-rsvps');

        // TRANS: Field label for event description.
        $out->element('strong', null, _m('Attending:'));
        $out->elementStart('ul', 'attending-list');

        foreach ($rsvps as $verb => $responses) {
            $out->elementStart('li', 'rsvp-list');
            switch ($verb) {
            case RSVP::POSITIVE:
                $out->text(_('Yes:'));
                break;
            case RSVP::NEGATIVE:
                $out->text(_('No:'));
                break;
            case RSVP::POSSIBLE:
                $out->text(_('Maybe:'));
                break;
            }
            $ids = array();
            foreach ($responses as $response) {
                $ids[] = $response->profile_id;
            }
            $ids = array_slice($ids, 0, ProfileMiniList::MAX_PROFILES + 1);
            $minilist = new ProfileMiniList(Profile::multiGet('id', $ids), $out);
            $minilist->show();

            $out->elementEnd('li');
        }

        $out->elementEnd('ul');
        $out->elementEnd('div');

        if ($scoped instanceof Profile) {
            $rsvp = $event->getRSVP($scoped);

            if (empty($rsvp)) {
                $form = new RSVPForm($event, $out);
            } else {
                $form = new CancelRSVPForm($rsvp, $out);
            }

            $form->show();
        }
        $out->elementEnd('div');
    }

    protected function showRSVP(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
    {
        $rsvp = RSVP::fromNotice($stored);

        if (empty($rsvp)) {
            // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
            $out->element('p', null, _m('Deleted.'));
            return;
        }

        $out->elementStart('div', 'rsvp');
        $out->raw($rsvp->asHTML());
        $out->elementEnd('div');
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Cancel the RSVP for an event
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * RSVP for an event
 *
 * @category  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 CancelrsvpAction extends Action
{
    protected $user  = null;
    protected $rsvp  = null;
    protected $event = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Title for RSVP ("please respond") action.
        return _m('TITLE','Cancel RSVP');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);
        if ($this->boolean('ajax')) {
            StatusNet::setApi(true); // short error results!
        }

        $rsvpId = $this->trimmed('rsvp');

        if (empty($rsvpId)) {
            // TRANS: Client exception thrown when referring to a non-existing RSVP ("please respond") item.
            throw new ClientException(_m('No such RSVP.'));
        }

        $this->rsvp = RSVP::getKV('id', $rsvpId);

        if (empty($this->rsvp)) {
            // TRANS: Client exception thrown when referring to a non-existing RSVP ("please respond") item.
            throw new ClientException(_m('No such RSVP.'));
        }

        $this->event = Happening::getKV('id', $this->rsvp->event_id);

        if (empty($this->event)) {
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'));
        }

        $this->user = common_current_user();

        if (empty($this->user)) {
            // TRANS: Client exception thrown when trying tp RSVP ("please respond") while not logged in.
            throw new ClientException(_m('You must be logged in to RSVP for an event.'));
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        parent::handle($argarray);

        if ($this->isPost()) {
            $this->cancelRSVP();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Add a new event
     *
     * @return void
     */
    function cancelRSVP()
    {
        try {
            $notice = $this->rsvp->getNotice();
            // NB: this will delete the rsvp, too
            if (!empty($notice)) {
                common_log(LOG_DEBUG, "Deleting notice...");
                $notice->delete();
            } else {
                common_log(LOG_DEBUG, "Deleting RSVP alone...");
                $this->rsvp->delete();
            }
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }

        if ($this->boolean('ajax')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending a notice.
            $this->element('title', null, _m('Event saved'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $form = new RSVPForm($this->event, $this);
            $form->show();
            $this->elementEnd('body');
            $this->endHTML();
        }
    }

    /**
     * Show the event form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new CancelRSVPForm($this->rsvp, $this);

        $form->show();

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
            return true;
        } else {
            return false;
        }
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Add a new event
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * Add a new event
 *
 * @category  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 NeweventAction extends Action
{
    protected $user        = null;
    protected $error       = null;
    protected $complete    = null;
    protected $title       = null;
    protected $location    = null;
    protected $description = null;
    protected $startTime   = null;
    protected $endTime     = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Title for new event form.
        return _m('TITLE','New event');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);

        $this->user = common_current_user();

        if (empty($this->user)) {
            // TRANS: Client exception thrown when trying to post an event while not logged in.
            throw new ClientException(_m('Must be logged in to post a event.'),
                                      403);
        }

        if ($this->isPost()) {
            $this->checkSessionToken();
        }

        try {

            $this->title = $this->trimmed('title');

            if (empty($this->title)) {
                // TRANS: Client exception thrown when trying to post an event without providing a title.
                throw new ClientException(_m('Title required.'));
            }

            $this->location    = $this->trimmed('location');
            $this->url         = $this->trimmed('url');
            $this->description = $this->trimmed('description');
            $tz                = $this->trimmed('tz');

            $startDate = $this->trimmed('startdate');

            if (empty($startDate)) {
                // TRANS: Client exception thrown when trying to post an event without providing a start date.
                throw new ClientException(_m('Start date required.'));
            }

            $startTime = $this->trimmed('event-starttime');

            if (empty($startTime)) {
                $startTime = '00:00';
            }

            $endDate   = $this->trimmed('enddate');

            if (empty($endDate)) {
                // TRANS: Client exception thrown when trying to post an event without providing an end date.
                throw new ClientException(_m('End date required.'));
            }

            $endTime   = $this->trimmed('event-endtime');

            if (empty($endTime)) {
                $endTime = '00:00';
            }

            $start = $startDate . ' ' . $startTime . ' ' . $tz;
            $end   = $endDate . ' ' . $endTime . ' ' . $tz;

            $this->startTime = strtotime($start);
            $this->endTime   = strtotime($end);

            if ($this->startTime == 0) {
                // TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
                // TRANS: %s is the data that could not be processed.
                throw new ClientException(sprintf(_m('Could not parse date "%s".'),
                                            $start));
            }

            if ($this->endTime == 0) {
                // TRANS: Client exception thrown when trying to post an event with a date that cannot be processed.
                // TRANS: %s is the data that could not be processed.
                throw new ClientException(sprintf(_m('Could not parse date "%s".'),
                                            $end));
            }
        } catch (ClientException $ce) {
            if ($this->boolean('ajax')) {
                $this->outputAjaxError($ce->getMessage());
                return false;
            } else {
                $this->error = $ce->getMessage();
                $this->showPage();
                return false;
            }
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        parent::handle($argarray);

        if ($this->isPost()) {
            $this->newEvent();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Add a new event
     *
     * @return void
     */
    function newEvent()
    {
        try {

            if (empty($this->title)) {
                // TRANS: Client exception thrown when trying to post an event without providing a title.
                throw new ClientException(_m('Event must have a title.'));
            }

            if (empty($this->startTime)) {
                // TRANS: Client exception thrown when trying to post an event without providing a start time.
                throw new ClientException(_m('Event must have a start time.'));
            }

            if (empty($this->endTime)) {
                // TRANS: Client exception thrown when trying to post an event without providing an end time.
                throw new ClientException(_m('Event must have an end time.'));
            }

            if (!empty($this->url) && Validate::uri($this->url) === false) {
                // TRANS: Client exception thrown when trying to post an event with an invalid URL.
                throw new ClientException(_m('URL must be valid.'));
            }

            $options = array();

            // Does the heavy-lifting for getting "To:" information

            ToSelector::fillOptions($this, $options);

            $profile = $this->user->getProfile();

            $saved = Happening::saveNew($profile,
                                        $this->startTime,
                                        $this->endTime,
                                        $this->title,
                                        $this->location,
                                        $this->description,
                                        $this->url,
                                        $options);

            $event = Happening::fromNotice($saved);

            RSVP::saveNew($profile, $event, RSVP::POSITIVE);

        } catch (ClientException $ce) {
            if ($this->boolean('ajax')) {
                $this->outputAjaxError($ce->getMessage());
                return;
            } else {
                $this->error = $ce->getMessage();
                $this->showPage();
                return;
            }
        }

        if ($this->boolean('ajax')) {
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after sending a notice.
            $this->element('title', null, _m('Event saved'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $this->showNotice($saved);
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($saved->getUrl(), 303);
        }
    }

    // @todo factor this out into a base class
    function outputAjaxError($msg)
    {
        $this->startHTML('text/xml;charset=utf-8');
        $this->elementStart('head');
        // TRANS: Page title after an AJAX error occurs
        $this->element('title', null, _('Ajax Error'));
        $this->elementEnd('head');
        $this->elementStart('body');
        $this->element('p', array('id' => 'error'), $msg);
        $this->elementEnd('body');
        $this->endHTML();
        return;
    }

    /**
     * Show the event form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new EventForm($this);

        $form->show();

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Output a notice
     *
     * Used to generate the notice code for Ajax results.
     *
     * @param Notice $notice Notice that was saved
     *
     * @return void
     */
    function showNotice(Notice $notice)
    {
        $nli = new NoticeListItem($notice, $this);
        $nli->show();
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * RSVP for an event
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * RSVP for an event
 *
 * @category  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 NewrsvpAction extends Action
{
    protected $user  = null;
    protected $event = null;
    protected $verb  = null;

    /**
     * Returns the title of the action
     *
     * @return string Action title
     */
    function title()
    {
        // TRANS: Title for RSVP ("please respond") action.
        return _m('TITLE','New RSVP');
    }

    /**
     * For initializing members of the class.
     *
     * @param array $argarray misc. arguments
     *
     * @return boolean true
     */
    function prepare($argarray)
    {
        parent::prepare($argarray);
        if ($this->boolean('ajax')) {
            StatusNet::setApi(true); // short error results!
        }

        $eventId = $this->trimmed('event');

        if (empty($eventId)) {
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'));
        }

        $this->event = Happening::getKV('id', $eventId);

        if (empty($this->event)) {
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'));
        }

        $this->user = common_current_user();

        if (empty($this->user)) {
            // TRANS: Client exception thrown when trying to RSVP ("please respond") while not logged in.
            throw new ClientException(_m('You must be logged in to RSVP for an event.'));
        }

        common_debug(print_r($this->args, true));

        switch (strtolower($this->trimmed('submitvalue'))) {
        case 'yes':
            $this->verb = RSVP::POSITIVE;
            break;
        case 'no':
            $this->verb = RSVP::NEGATIVE;
            break;
        case 'maybe':
            $this->verb = RSVP::POSSIBLE;
            break;
        default:
            // TRANS: Client exception thrown when using an invalid value for RSVP ("please respond").
            throw new ClientException(_m('Unknown submit value.'));
        }

        return true;
    }

    /**
     * Handler method
     *
     * @param array $argarray is ignored since it's now passed in in prepare()
     *
     * @return void
     */
    function handle($argarray=null)
    {
        parent::handle($argarray);

        if ($this->isPost()) {
            $this->newRSVP();
        } else {
            $this->showPage();
        }

        return;
    }

    /**
     * Add a new event
     *
     * @return void
     */
    function newRSVP()
    {
        try {
            $saved = RSVP::saveNew($this->user->getProfile(),
                                   $this->event,
                                   $this->verb);
        } catch (ClientException $ce) {
            $this->error = $ce->getMessage();
            $this->showPage();
            return;
        }

        if ($this->boolean('ajax')) {
            $rsvp = RSVP::fromNotice($saved);
            $this->startHTML('text/xml;charset=utf-8');
            $this->elementStart('head');
            // TRANS: Page title after creating an event.
            $this->element('title', null, _m('Event saved'));
            $this->elementEnd('head');
            $this->elementStart('body');
            $cancel = new CancelRSVPForm($rsvp, $this);
            $cancel->show();
            $this->elementEnd('body');
            $this->endHTML();
        } else {
            common_redirect($saved->getUrl(), 303);
        }
    }

    /**
     * Show the event form
     *
     * @return void
     */
    function showContent()
    {
        if (!empty($this->error)) {
            $this->element('p', 'error', $this->error);
        }

        $form = new RSVPForm($this->event, $this);

        $form->show();

        return;
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        if ($_SERVER['REQUEST_METHOD'] == 'GET' ||
            $_SERVER['REQUEST_METHOD'] == 'HEAD') {
            return true;
        } else {
            return false;
        }
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Show a single event
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * Show a single event, with associated information
 *
 * @category  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 ShoweventAction extends ShownoticeAction
{
    protected $id    = null;
    protected $event = null;

    function getNotice()
    {
        $this->id = $this->trimmed('id');

        $this->event = Happening::getKV('id', $this->id);

        if (empty($this->event)) {
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'), 404);
        }

        $notice = $this->event->getNotice();

        if (empty($notice)) {
            // Did we used to have it, and it got deleted?
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'), 404);
        }

        return $notice;
    }

    /**
     * Title of the page
     *
     * Used by Action class for layout.
     *
     * @return string page tile
     */
    function title()
    {
        return $this->event->title;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * Show a single RSVP
 *
 * 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  RSVP
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 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);
}

/**
 * Show a single RSVP, with associated information
 *
 * @category  RSVP
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2010 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class ShowrsvpAction extends ShownoticeAction
{
    protected $rsvp = null;
    protected $event = null;

    function getNotice()
    {
        $this->id = $this->trimmed('id');

        $this->rsvp = RSVP::getKV('id', $this->id);

        if (empty($this->rsvp)) {
            // TRANS: Client exception thrown when referring to a non-existing RSVP.
            // TRANS: RSVP stands for "Please reply".
            throw new ClientException(_m('No such RSVP.'), 404);
        }

        $this->event = $this->rsvp->getEvent();

        if (empty($this->event)) {
            // TRANS: Client exception thrown when referring to a non-existing event.
            throw new ClientException(_m('No such event.'), 404);
        }

        $notice = $this->rsvp->getNotice();

        if (empty($notice)) {
            // Did we used to have it, and it got deleted?
            // TRANS: Client exception thrown when referring to a non-existing RSVP.
            // TRANS: RSVP stands for "Please reply".
            throw new ClientException(_m('No such RSVP.'), 404);
        }

        return $notice;
    }

    /**
     * Title of the page
     *
     * Used by Action class for layout.
     *
     * @return string page tile
     */
    function title()
    {
        // TRANS: Title for event.
	// TRANS: %1$s is a user nickname, %2$s is an event title.
        return sprintf(_m('%1$s\'s RSVP for "%2$s"'),
                       $this->user->nickname,
                       $this->event->title);
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, 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/>.
 *
 * @category  Event
 * @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);
}

/**
 * Callback handler to populate end time dropdown
 */
class TimelistAction extends Action {
    private $start;
    private $duration;

    /**
     * Get ready
     *
     * @param array $args misc. arguments
     *
     * @return boolean true
     */
    function prepare($args) {
        parent::prepare($args);
        $this->start = $this->arg('start');
        $this->duration = $this->boolean('duration', false);
        return true;
    }

    /**
     * Handle input and ouput something
     *
     * @param array $args $_REQUEST arguments
     *
     * @return void
     */
    function handle($args)
    {
        parent::handle($args);

        if (!common_logged_in()) {
            // TRANS: Error message displayed when trying to perform an action that requires a logged in user.
            $this->clientError(_m('Not logged in.'));
        }

        if (!empty($this->start)) {
            $times = EventTimeList::getTimes($this->start, $this->duration);
        } else {
            // TRANS: Client error when submitting a form with unexpected information.
            $this->clientError(_m('Unexpected form submission.'));
        }

        if ($this->boolean('ajax')) {
            header('Content-Type: application/json; charset=utf-8');
            print json_encode($times);
        } else {
            // TRANS: Client error displayed when using an action in a non-AJAX way.
            $this->clientError(_m('This action is AJAX only.'));
        }
    }

    /**
     * Override the regular error handler to show something more
     * ajaxy
     *
     * @param string $msg   error message
     * @param int    $code  error code
     */
    function clientError($msg, $code = 400) {
        if ($this->boolean('ajax')) {
            header('Content-Type: application/json; charset=utf-8');
            print json_encode(
                array(
                    'success' => false,
                    'code'    => $code,
                    'message' => $msg
                )
            );
        } else {
            parent::clientError($msg, $code);
        }
    }
}


code:
<?php
/**
 * Data class for happenings
 *
 * 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) 2011, 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);
}

/**
 * Data class for happenings
 *
 * There's already an Event class in lib/event.php, so we couldn't
 * call this an Event without causing a hole in space-time.
 *
 * "Happening" seemed good enough.
 *
 * @category Event
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * @see      Managed_DataObject
 */
class Happening extends Managed_DataObject
{
    const OBJECT_TYPE = 'http://activitystrea.ms/schema/1.0/event';

    public $__table = 'happening'; // table name
    public $id;                    // varchar(36) UUID
    public $uri;                   // varchar(255)
    public $profile_id;            // int
    public $start_time;            // datetime
    public $end_time;              // datetime
    public $title;                 // varchar(255)
    public $location;              // varchar(255)
    public $url;                   // varchar(255)
    public $description;           // text
    public $created;               // datetime

    /**
     * The One True Thingy that must be defined and declared.
     */
    public static function schemaDef()
    {
        return array(
            'description' => 'A real-world happening',
            'fields' => array(
                'id' => array('type' => 'char',
                              'length' => 36,
                              'not null' => true,
                              'description' => 'UUID'),
                'uri' => array('type' => 'varchar',
                               'length' => 255,
                               'not null' => true),
                'profile_id' => array('type' => 'int', 'not null' => true),
                'start_time' => array('type' => 'datetime', 'not null' => true),
                'end_time' => array('type' => 'datetime', 'not null' => true),
                'title' => array('type' => 'varchar',
                                 'length' => 255,
                                 'not null' => true),
                'location' => array('type' => 'varchar',
                                    'length' => 255),
                'url' => array('type' => 'varchar',
                               'length' => 255),
                'description' => array('type' => 'text'),
                'created' => array('type' => 'datetime',
                                   'not null' => true),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'happening_uri_key' => array('uri'),
            ),
            'foreign keys' => array('happening_profile_id__key' => array('profile', array('profile_id' => 'id'))),
            'indexes' => array('happening_created_idx' => array('created'),
                               'happening_start_end_idx' => array('start_time', 'end_time')),
        );
    }

    static function saveNew($profile, $start_time, $end_time, $title, $location, $description, $url, $options=array())
    {
        if (array_key_exists('uri', $options)) {
            $other = Happening::getKV('uri', $options['uri']);
            if (!empty($other)) {
                // TRANS: Client exception thrown when trying to create an event that already exists.
                throw new ClientException(_m('Event already exists.'));
            }
        }

        $ev = new Happening();

        $ev->id          = UUID::gen();
        $ev->profile_id  = $profile->id;
        $ev->start_time  = common_sql_date($start_time);
        $ev->end_time    = common_sql_date($end_time);
        $ev->title       = $title;
        $ev->location    = $location;
        $ev->description = $description;
        $ev->url         = $url;

        if (array_key_exists('created', $options)) {
            $ev->created = $options['created'];
        } else {
            $ev->created = common_sql_now();
        }

        if (array_key_exists('uri', $options)) {
            $ev->uri = $options['uri'];
        } else {
            $ev->uri = common_local_url('showevent',
                                        array('id' => $ev->id));
        }

        $ev->insert();

        // XXX: does this get truncated?

        // TRANS: Event description. %1$s is a title, %2$s is start time, %3$s is end time,
        // TRANS: %4$s is location, %5$s is a description.
        $content = sprintf(_m('"%1$s" %2$s - %3$s (%4$s): %5$s'),
                           $title,
                           common_exact_date($ev->start_time),
                           common_exact_date($ev->end_time),
                           $location,
                           $description);

        // TRANS: Rendered microformats2 tagged event description.
        // TRANS: %1$s is a title, %2$s is start time, %3$s is start time,
        // TRANS: %4$s is end time, %5$s is end time, %6$s is location, %7$s is description.
        // TRANS: Class names should not be translated.
        $rendered = sprintf(_m('
'.
                              '
%1$s
 '.
                              '<time class="dt-start" datetime="%2$s">%3$s</time> - '.
                              '<time class="dt-end" datetime="%4$s">%5$s</time> '.
                              '(%6$s): '.
                              '
%7$s
 '.
                              '
'),
                            htmlspecialchars($title),
                            htmlspecialchars(common_date_iso8601($ev->start_time)),
                            htmlspecialchars(common_exact_date($ev->start_time)),
                            htmlspecialchars(common_date_iso8601($ev->end_time)),
                            htmlspecialchars(common_exact_date($ev->end_time)),
                            htmlspecialchars($location),
                            htmlspecialchars($description));

        $options = array_merge(array('object_type' => Happening::OBJECT_TYPE),
                               $options);

        if (!array_key_exists('uri', $options)) {
            $options['uri'] = $ev->uri;
        }

        if (!empty($url)) {
            $options['urls'] = array($url);
        }

        $saved = Notice::saveNew($profile->id,
                                 $content,
                                 array_key_exists('source', $options) ?
                                 $options['source'] : 'web',
                                 $options);

        return $saved;
    }

    /**
     * Returns the profile's canonical url, not necessarily a uri/unique id
     *
     * @return string $url
     */
    public function getUrl()
    {
        if (empty($this->url) ||
                !filter_var($this->url, FILTER_VALIDATE_URL)) {
            throw new InvalidUrlException($this->url);
        }
        return $this->url;
    }

    function getNotice()
    {
        return Notice::getKV('uri', $this->uri);
    }

    static function fromNotice($notice)
    {
        return Happening::getKV('uri', $notice->uri);
    }

    function getRSVPs()
    {
        return RSVP::forEvent($this);
    }

    function getRSVP($profile)
    {
        return RSVP::pkeyGet(array('profile_id' => $profile->id,
                                   'event_id' => $this->id));
    }
}


code:
<?php
/**
 * Data class for event RSVPs
 *
 * 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) 2011, 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);
}

/**
 * Data class for event RSVPs
 *
 * @category Event
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * @see      Managed_DataObject
 */
class RSVP extends Managed_DataObject
{
    const POSITIVE = 'http://activitystrea.ms/schema/1.0/rsvp-yes';
    const POSSIBLE = 'http://activitystrea.ms/schema/1.0/rsvp-maybe';
    const NEGATIVE = 'http://activitystrea.ms/schema/1.0/rsvp-no';

    public $__table = 'rsvp'; // table name
    public $id;                // varchar(36) UUID
    public $uri;               // varchar(255)
    public $profile_id;        // int
    public $event_id;          // varchar(36) UUID
    public $response;            // tinyint
    public $created;           // datetime

    /**
     * Add the compound profile_id/event_id index to our cache keys
     * since the DB_DataObject stuff doesn't understand compound keys
     * except for the primary.
     *
     * @return array
     */
    function _allCacheKeys() {
        $keys = parent::_allCacheKeys();
        $keys[] = self::multicacheKey('RSVP', array('profile_id' => $this->profile_id,
                                                    'event_id' => $this->event_id));
        return $keys;
    }

    /**
     * The One True Thingy that must be defined and declared.
     */
    public static function schemaDef()
    {
        return array(
            'description' => 'Plan to attend event',
            'fields' => array(
                'id' => array('type' => 'char',
                              'length' => 36,
                              'not null' => true,
                              'description' => 'UUID'),
                'uri' => array('type' => 'varchar',
                               'length' => 255,
                               'not null' => true),
                'profile_id' => array('type' => 'int'),
                'event_id' => array('type' => 'char',
                              'length' => 36,
                              'not null' => true,
                              'description' => 'UUID'),
                'response' => array('type' => 'char',
                                  'length' => '1',
                                  'description' => 'Y, N, or ? for three-state yes, no, maybe'),
                'created' => array('type' => 'datetime',
                                   'not null' => true),
            ),
            'primary key' => array('id'),
            'unique keys' => array(
                'rsvp_uri_key' => array('uri'),
                'rsvp_profile_event_key' => array('profile_id', 'event_id'),
            ),
            'foreign keys' => array('rsvp_event_id_key' => array('event', array('event_id' => 'id')),
                                    'rsvp_profile_id__key' => array('profile', array('profile_id' => 'id'))),
            'indexes' => array('rsvp_created_idx' => array('created')),
        );
    }

    function saveNew($profile, $event, $verb, $options=array())
    {
        if (array_key_exists('uri', $options)) {
            $other = RSVP::getKV('uri', $options['uri']);
            if (!empty($other)) {
                // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
                throw new ClientException(_m('RSVP already exists.'));
            }
        }

        $other = RSVP::pkeyGet(array('profile_id' => $profile->id,
                                     'event_id' => $event->id));

        if (!empty($other)) {
            // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
            throw new ClientException(_m('RSVP already exists.'));
        }

        $rsvp = new RSVP();

        $rsvp->id          = UUID::gen();
        $rsvp->profile_id  = $profile->id;
        $rsvp->event_id    = $event->id;
        $rsvp->response      = self::codeFor($verb);

        if (array_key_exists('created', $options)) {
            $rsvp->created = $options['created'];
        } else {
            $rsvp->created = common_sql_now();
        }

        if (array_key_exists('uri', $options)) {
            $rsvp->uri = $options['uri'];
        } else {
            $rsvp->uri = common_local_url('showrsvp',
                                        array('id' => $rsvp->id));
        }

        $rsvp->insert();

        self::blow('rsvp:for-event:%s', $event->id);

        // XXX: come up with something sexier

        $content = $rsvp->asString();

        $rendered = $rsvp->asHTML();

        $options = array_merge(array('object_type' => $verb),
                               $options);

        if (!array_key_exists('uri', $options)) {
            $options['uri'] = $rsvp->uri;
        }

        $eventNotice = $event->getNotice();

        if (!empty($eventNotice)) {
            $options['reply_to'] = $eventNotice->id;
        }

        $saved = Notice::saveNew($profile->id,
                                 $content,
                                 array_key_exists('source', $options) ?
                                 $options['source'] : 'web',
                                 $options);

        return $saved;
    }

    function codeFor($verb)
    {
        switch ($verb) {
        case RSVP::POSITIVE:
            return 'Y';
            break;
        case RSVP::NEGATIVE:
            return 'N';
            break;
        case RSVP::POSSIBLE:
            return '?';
            break;
        default:
            // TRANS: Exception thrown when requesting an undefined verb for RSVP.
            throw new Exception(sprintf(_m('Unknown verb "%s".'),$verb));
        }
    }

    static function verbFor($code)
    {
        switch ($code) {
        case 'Y':
            return RSVP::POSITIVE;
            break;
        case 'N':
            return RSVP::NEGATIVE;
            break;
        case '?':
            return RSVP::POSSIBLE;
            break;
        default:
            // TRANS: Exception thrown when requesting an undefined code for RSVP.
            throw new Exception(sprintf(_m('Unknown code "%s".'),$code));
        }
    }

    function getNotice()
    {
        $notice = Notice::getKV('uri', $this->uri);
        if (empty($notice)) {
            // TRANS: Server exception thrown when requesting a non-exsting notice for an RSVP ("please respond").
            // TRANS: %s is the RSVP with the missing notice.
            throw new ServerException(sprintf(_m('RSVP %s does not correspond to a notice in the database.'),$this->id));
        }
        return $notice;
    }

    static function fromNotice(Notice $notice)
    {
        $rsvp = new RSVP();
        $rsvp->uri = $notice->uri;
        if (!$rsvp->find(true)) {
            throw new NoResultException($rsvp);
        }
        return $rsvp;
    }

    static function forEvent(Happening $event)
    {
        $keypart = sprintf('rsvp:for-event:%s', $event->id);

        $idstr = self::cacheGet($keypart);

        if ($idstr !== false) {
            $ids = explode(',', $idstr);
        } else {
            $ids = array();

            $rsvp = new RSVP();

            $rsvp->selectAdd();
            $rsvp->selectAdd('id');

            $rsvp->event_id = $event->id;

            if ($rsvp->find()) {
                while ($rsvp->fetch()) {
                    $ids[] = $rsvp->id;
                }
            }
            self::cacheSet($keypart, implode(',', $ids));
        }

        $rsvps = array(RSVP::POSITIVE => array(),
                       RSVP::NEGATIVE => array(),
                       RSVP::POSSIBLE => array());

        foreach ($ids as $id) {
            $rsvp = RSVP::getKV('id', $id);
            if (!empty($rsvp)) {
                $verb = self::verbFor($rsvp->response);
                $rsvps[$verb][] = $rsvp;
            }
        }

        return $rsvps;
    }

    function getProfile()
    {
        $profile = Profile::getKV('id', $this->profile_id);
        if (empty($profile)) {
            // TRANS: Exception thrown when requesting a non-existing profile.
            // TRANS: %s is the ID of the non-existing profile.
            throw new Exception(sprintf(_m('No profile with ID %s.'),$this->profile_id));
        }
        return $profile;
    }

    function getEvent()
    {
        $event = Happening::getKV('id', $this->event_id);
        if (empty($event)) {
            // TRANS: Exception thrown when requesting a non-existing event.
            // TRANS: %s is the ID of the non-existing event.
            throw new Exception(sprintf(_m('No event with ID %s.'),$this->event_id));
        }
        return $event;
    }

    function asHTML()
    {
        $event = Happening::getKV('id', $this->event_id);

        return self::toHTML($this->getProfile(),
                            $event,
                            $this->response);
    }

    function asString()
    {
        $event = Happening::getKV('id', $this->event_id);

        return self::toString($this->getProfile(),
                              $event,
                              $this->response);
    }

    static function toHTML($profile, $event, $response)
    {
        $fmt = null;

        switch ($response) {
        case 'Y':
            // TRANS: HTML version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile URL, %2$s a profile name,
            // TRANS: %3$s is an event URL, %4$s an event title.
            $fmt = _m("%2\$s is attending %4\$s.");
            break;
        case 'N':
            // TRANS: HTML version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile URL, %2$s a profile name,
            // TRANS: %3$s is an event URL, %4$s an event title.
            $fmt = _m("%2\$s is not attending %4\$s.");
            break;
        case '?':
            // TRANS: HTML version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile URL, %2$s a profile name,
            // TRANS: %3$s is an event URL, %4$s an event title.
            $fmt = _m("%2\$s might attend %4\$s.");
            break;
        default:
            // TRANS: Exception thrown when requesting a user's RSVP status for a non-existing response code.
            // TRANS: %s is the non-existing response code.
            throw new Exception(sprintf(_m('Unknown response code %s.'),$response));
        }

        if (empty($event)) {
            $eventUrl = '#';
            // TRANS: Used as event title when not event title is available.
            // TRANS: Used as: Username [is [not ] attending|might attend] an unknown event.
            $eventTitle = _m('an unknown event');
        } else {
            $notice = $event->getNotice();
            $eventUrl = $notice->getUrl();
            $eventTitle = $event->title;
        }

        return sprintf($fmt,
                       htmlspecialchars($profile->profileurl),
                       htmlspecialchars($profile->getBestName()),
                       htmlspecialchars($eventUrl),
                       htmlspecialchars($eventTitle));
    }

    static function toString($profile, $event, $response)
    {
        $fmt = null;

        switch ($response) {
        case 'Y':
            // TRANS: Plain text version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile name, %2$s is an event title.
            $fmt = _m('%1$s is attending %2$s.');
            break;
        case 'N':
            // TRANS: Plain text version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile name, %2$s is an event title.
            $fmt = _m('%1$s is not attending %2$s.');
            break;
        case '?':
            // TRANS: Plain text version of an RSVP ("please respond") status for a user.
            // TRANS: %1$s is a profile name, %2$s is an event title.
            $fmt = _m('%1$s might attend %2$s.');
            break;
        default:
            // TRANS: Exception thrown when requesting a user's RSVP status for a non-existing response code.
            // TRANS: %s is the non-existing response code.
            throw new Exception(sprintf(_m('Unknown response code %s.'),$response));
            break;
        }

        if (empty($event)) {
            // TRANS: Used as event title when not event title is available.
            // TRANS: Used as: Username [is [not ] attending|might attend] an unknown event.
            $eventTitle = _m('an unknown event');
        } else {
            $notice = $event->getNotice();
            $eventTitle = $event->title;
        }

        return sprintf($fmt,
                       $profile->getBestName(),
                       $eventTitle);
    }

    function delete($useWhere=false)
    {
        self::blow('rsvp:for-event:%s', $this->id);
        return parent::delete($useWhere);
    }
}


code:
/* Event specific styles */

.event-tags li { display: inline; }
.event-mentions li { display: inline; }
.event-avatar { float: left; }
.event-notice-count { float: right; }
.event-info { float: left; }
.event-title { margin-left: 0px; }
.ui-autocomplete {
    max-height: 100px;
    overflow-y: auto;
    /* prevent horizontal scrollbar */
    overflow-x: hidden;
    /* add padding to account for vertical scrollbar */
    padding-right: 20px;
}

.attending-list { list-style-type: none; float: left; width: 100%; }

#form_event_rsvp { clear: left; }

li.rsvp-list { float: left; clear: left; }

li.rsvp-list ul.entities {
    display:inline;
}
li.rsvp-list .entities li {
    list-style-type: none;
    margin-right: 3px;
    margin-bottom: 8px;
    display: inline;
}
li.rsvp-list .entities li .u-photo {
    margin: 0 !important;
    float: none !important;
}

.notice .h-event div {
    margin-bottom: 8px;
}

.event-info {
    margin-left: 0px !important;
    margin-top: 2px !important;
}

.notice .event-info + .notice-options {
    margin-top: 14px;
}

.notice .threaded-replies .event-info + .notice-options {
    margin-top: 20px;
}

#form_event_rsvp #new_rsvp_data {
    display: inline;
    margin: 10px 0px;
}

#form_event_rsvp input.submit {
    height: auto;
    padding: 0px 10px;
    margin-left: 10px;
    color:#fff;
    font-weight: bold;
    text-transform: uppercase;
    font-size: 1.1em;
    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.2);
    border: 1px solid #d7621c;
    border-radius: 4px;
    -moz-border-radius: 4px;
    -webkit-border-radius: 4px;
    background: #FB6104;
    background: -moz-linear-gradient(top, #ff9d63 0%, #fb6104 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#ff9d63), color-stop(100%,#fb6104));
    background: -webkit-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
    background: -o-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
    background: -ms-linear-gradient(top, #ff9d63 0%,#fb6104 100%);
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff9d63', endColorstr='#fb6104',GradientType=0 );
    background: linear-gradient(top, #ff9d63 0%,#fb6104 100%);
}

#form_event_rsvp input.submit:hover {
    text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.6);
    background: #ff9d63;
    background: -moz-linear-gradient(top, #fb6104 0%, #fc8035 100%);
    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#fb6104), color-stop(100%,#fc8035));
    background: -webkit-linear-gradient(top, #fb6104 0%,#fc8035 100%);
    background: -o-linear-gradient(top, #fb6104 0%,#fc8035 100%);
    background: -ms-linear-gradient(top, #fb6104 0%,#fc8035 100%);
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fb6104', endColorstr='#fc8035',GradientType=0 );
    background: linear-gradient(top, #fb6104 0%,#fc8035 100%);
}

#wrap .h-event form.processing input.submit {
    text-indent: 0;
    background: #ff9d63;
}

#input_form_event .form_settings .form_data {
    float: left;
}

#input_form_event .form_settings .form_data li {
    float: left;
    width: auto;
}

#input_form_event .form_settings .form_data label {
    width: auto;
}

label[for=event-starttime], label[for=event-endtime] {
    display: none !important;
}

#event-starttime, #event-endtime {
    margin-top:  -1px;
    margin-bottom:  -1px;
    height: 2em;
}

#event-startdate, #event-enddate {
    margin-right: 20px;
    width: 120px;
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Form to RSVP for an event
 *
 * 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  Event
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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);
}

/**
 * A form to RSVP for an event
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@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 CancelRSVPForm extends Form
{
    protected $rsvp = null;

    function __construct($rsvp, $out=null)
    {
        parent::__construct($out);
        $this->rsvp = $rsvp;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'form_event_rsvp';
    }

    /**
     * class of the form
     *
     * @return string class of the form
     */
    function formClass()
    {
        return 'ajax';
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('cancelrsvp');
    }

    /**
     * Data elements of the form
     *
     * @return void
     */
    function formData()
    {
        $this->out->elementStart('fieldset', array('id' => 'new_rsvp_data'));

        $this->out->hidden('rsvp-id', $this->rsvp->id, 'rsvp');

        switch (RSVP::verbFor($this->rsvp->response)) {
        case RSVP::POSITIVE:
            // TRANS: Possible status for RSVP ("please respond") item.
            $this->out->text(_m('You will attend this event.'));
            break;
        case RSVP::NEGATIVE:
            // TRANS: Possible status for RSVP ("please respond") item.
            $this->out->text(_m('You will not attend this event.'));
            break;
        case RSVP::POSSIBLE:
            // TRANS: Possible status for RSVP ("please respond") item.
            $this->out->text(_m('You might attend this event.'));
            break;
        }

        $this->out->elementEnd('fieldset');
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        // TRANS: Button text to cancel responding to an RSVP ("please respond") item.
        $this->out->submit('rsvp-cancel', _m('BUTTON', 'Cancel'));
    }
}