From 35429c28e5bdde71fe9dff9e69ef795a31a96e8d Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 9 Mar 2011 12:28:25 -0500 Subject: [PATCH] updates to make RSVPs work --- plugins/Event/EventPlugin.php | 23 +++- plugins/Event/Happening.php | 6 + plugins/Event/RSVP.php | 32 ++++- plugins/Event/cancelrsvp.php | 202 ++++++++++++++++++++++++++++ plugins/Event/cancelrsvpform.php | 128 ++++++++++++++++++ plugins/Event/newevent.php | 4 + plugins/Event/newrsvp.php | 218 ++++++++++++++++++------------- plugins/Event/rsvpform.php | 120 +++++++++++++++++ plugins/Event/showevent.php | 4 +- 9 files changed, 638 insertions(+), 99 deletions(-) create mode 100644 plugins/Event/cancelrsvp.php create mode 100644 plugins/Event/cancelrsvpform.php create mode 100644 plugins/Event/rsvpform.php diff --git a/plugins/Event/EventPlugin.php b/plugins/Event/EventPlugin.php index d8d9b572ed..7ca2fa9c0e 100644 --- a/plugins/Event/EventPlugin.php +++ b/plugins/Event/EventPlugin.php @@ -79,11 +79,14 @@ class EventPlugin extends MicroappPlugin { case 'NeweventAction': case 'NewrsvpAction': + case 'CancelrsvpAction': case 'ShoweventAction': case 'ShowrsvpAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; case 'EventForm': + case 'RSVPForm': + case 'CancelRSVPForm': include_once $dir . '/'.strtolower($cls).'.php'; break; case 'Happening': @@ -109,6 +112,8 @@ class EventPlugin extends MicroappPlugin 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}')); @@ -294,7 +299,7 @@ class EventPlugin extends MicroappPlugin function showRSVPNotice($notice, $out) { - $out->element('span', null, 'RSVP'); + $out->raw($notice->rendered); return; } @@ -312,7 +317,7 @@ class EventPlugin extends MicroappPlugin if (!empty($event->url)) { $out->element('a', - array('href' => $att->url, + array('href' => $event->url, 'class' => 'event-title entry-title summary'), $event->title); } else { @@ -349,6 +354,20 @@ class EventPlugin extends MicroappPlugin count($rsvps[RSVP::NEGATIVE]), count($rsvps[RSVP::POSSIBLE]))); + $user = common_current_user(); + + if (!empty($user)) { + $rsvp = $event->getRSVP($user->getProfile()); + + if (empty($rsvp)) { + $form = new RSVPForm($event, $out); + } else { + $form = new CancelRSVPForm($rsvp, $out); + } + + $form->show(); + } + $out->elementStart('div', array('class' => 'event-info entry-content')); $avatar = $profile->getAvatar(AVATAR_MINI_SIZE); diff --git a/plugins/Event/Happening.php b/plugins/Event/Happening.php index b2adb4d9b5..1a6a028dca 100644 --- a/plugins/Event/Happening.php +++ b/plugins/Event/Happening.php @@ -210,4 +210,10 @@ class Happening extends Managed_DataObject { return RSVP::forEvent($this); } + + function getRSVP($profile) + { + return RSVP::pkeyGet(array('profile_id' => $profile->id, + 'event_id' => $this->id)); + } } diff --git a/plugins/Event/RSVP.php b/plugins/Event/RSVP.php index 36c6b32ec7..22bd239a68 100644 --- a/plugins/Event/RSVP.php +++ b/plugins/Event/RSVP.php @@ -71,13 +71,27 @@ class RSVP extends Managed_DataObject return Memcached_DataObject::staticGet('RSVP', $k, $v); } + /** + * Get an instance by compound key + * + * @param array $kv array of key-value mappings + * + * @return Bookmark object found, or null for no hits + * + */ + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('RSVP', $kv); + } + /** * The One True Thingy that must be defined and declared. */ public static function schemaDef() { return array( - 'description' => 'A real-world event', + 'description' => 'Plan to attend event', 'fields' => array( 'id' => array('type' => 'char', 'length' => 36, @@ -107,7 +121,7 @@ class RSVP extends Managed_DataObject ); } - function saveNew($profile, $event, $result, $options) + function saveNew($profile, $event, $result, $options=array()) { if (array_key_exists('uri', $options)) { $other = RSVP::staticGet('uri', $options['uri']); @@ -181,7 +195,7 @@ class RSVP extends Managed_DataObject ($verb == RSVP::NEGATIVE) ? 0 : null; } - function verbFor($code) + static function verbFor($code) { return ($code == 1) ? RSVP::POSITIVE : ($code == 0) ? RSVP::NEGATIVE : null; @@ -189,7 +203,11 @@ class RSVP extends Managed_DataObject function getNotice() { - return Notice::staticGet('uri', $this->uri); + $notice = Notice::staticGet('uri', $this->uri); + if (empty($notice)) { + throw new ServerException("RSVP {$this->id} does not correspond to a notice in the DB."); + } + return $notice; } static function fromNotice($notice) @@ -207,11 +225,15 @@ class RSVP extends Managed_DataObject if ($rsvp->find()) { while ($rsvp->fetch()) { - $verb = $this->verbFor($rsvp->code); + $verb = self::verbFor($rsvp->result); $rsvps[$verb][] = clone($rsvp); } } return $rsvps; } + + function delete() + { + } } diff --git a/plugins/Event/cancelrsvp.php b/plugins/Event/cancelrsvp.php new file mode 100644 index 0000000000..21ed41a451 --- /dev/null +++ b/plugins/Event/cancelrsvp.php @@ -0,0 +1,202 @@ +. + * + * @category Event + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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() + { + return _('Cancel RSVP'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $rsvpId = $this->trimmed('rsvp'); + + if (empty($rsvpId)) { + throw new ClientException(_('No such rsvp.')); + } + + $this->rsvp = RSVP::staticGet('id', $rsvpId); + + if (empty($this->rsvp)) { + throw new ClientException(_('No such rsvp.')); + } + + $this->event = Happening::staticGet('id', $this->rsvp->event_id); + + if (empty($this->event)) { + throw new ClientException(_('No such event.')); + } + + $this->user = common_current_user(); + + if (empty($this->user)) { + throw new ClientException(_('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)) { + $notice->delete(); + } else { + $this->rsvp->delete(); + } + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + if ($this->boolean('ajax')) { + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + // TRANS: Page title after sending a notice. + $this->element('title', null, _('Event saved')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->elementStart('body'); + $form = new RSVPForm($this->event, $this); + $form->show(); + $this->elementEnd('body'); + $this->elementEnd('body'); + $this->elementEnd('html'); + } + } + + /** + * 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; + } + } +} diff --git a/plugins/Event/cancelrsvpform.php b/plugins/Event/cancelrsvpform.php new file mode 100644 index 0000000000..8cccbdb661 --- /dev/null +++ b/plugins/Event/cancelrsvpform.php @@ -0,0 +1,128 @@ +. + * + * @category Event + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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', $this->rsvp->id); + + switch (RSVP::verbFor($this->rsvp->result)) { + case RSVP::POSITIVE: + $this->out->text(_('You will attend this event.')); + break; + case RSVP::NEGATIVE: + $this->out->text(_('You will not attend this event.')); + break; + case RSVP::POSSIBLE: + $this->out->text(_('You might attend this event.')); + break; + } + + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('cancel', _m('BUTTON', 'Cancel')); + } +} diff --git a/plugins/Event/newevent.php b/plugins/Event/newevent.php index 7c0fd0177f..0f5635487b 100644 --- a/plugins/Event/newevent.php +++ b/plugins/Event/newevent.php @@ -157,6 +157,10 @@ class NeweventAction extends Action $this->description, $this->url); + $event = Happening::fromNotice($saved); + + RSVP::saveNew($profile, $event, RSVP::POSITIVE); + } catch (ClientException $ce) { $this->error = $ce->getMessage(); $this->showPage(); diff --git a/plugins/Event/newrsvp.php b/plugins/Event/newrsvp.php index a793ac6de2..da613ec6c7 100644 --- a/plugins/Event/newrsvp.php +++ b/plugins/Event/newrsvp.php @@ -1,17 +1,11 @@ - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2009, StatusNet, Inc. + * 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 @@ -25,140 +19,184 @@ * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . + * + * @category Event + * @package StatusNet + * @author Evan Prodromou + * @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); } /** - * Give a warm greeting to our friendly user + * RSVP for an event * - * This sample action shows some basic ways of doing output in an action - * class. - * - * Action classes have several output methods that they override from - * the parent class. - * - * @category Sample - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ + * @category Event + * @package StatusNet + * @author Evan Prodromou + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ */ -class HelloAction extends Action + +class NewrsvpAction extends Action { - var $user = null; - var $gc = null; + protected $user = null; + protected $event = null; + protected $type = null; /** - * Take arguments for running + * Returns the title of the action * - * This method is called first, and it lets the action class get - * all its arguments and validate them. It's also the time - * to fetch any relevant data from the database. - * - * Action classes should run parent::prepare($args) as the first - * line of this method to make sure the default argument-processing - * happens. - * - * @param array $args $_REQUEST args - * - * @return boolean success flag + * @return string Action title */ - function prepare($args) + + function title() { - parent::prepare($args); + return _('New RSVP'); + } + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + parent::prepare($argarray); + + $eventId = $this->trimmed('event'); + + if (empty($eventId)) { + throw new ClientException(_('No such event.')); + } + + $this->event = Happening::staticGet('id', $eventId); + + if (empty($this->event)) { + throw new ClientException(_('No such event.')); + } $this->user = common_current_user(); - if (!empty($this->user)) { - $this->gc = User_greeting_count::inc($this->user->id); + if (empty($this->user)) { + throw new ClientException(_('You must be logged in to RSVP for an event.')); } + if ($this->arg('yes')) { + $this->type = RSVP::POSITIVE; + } else if ($this->arg('no')) { + $this->type = RSVP::NEGATIVE; + } else { + $this->type = RSVP::POSSIBLE; + } return true; } /** - * Handle request + * Handler method * - * This is the main method for handling a request. Note that - * most preparation should be done in the prepare() method; - * by the time handle() is called the action should be - * more or less ready to go. - * - * @param array $args $_REQUEST args; handled in prepare() + * @param array $argarray is ignored since it's now passed in in prepare() * * @return void */ - function handle($args) - { - parent::handle($args); - $this->showPage(); + function handle($argarray=null) + { + parent::handle($argarray); + + if ($this->isPost()) { + $this->newRSVP(); + } else { + $this->showPage(); + } + + return; } /** - * Title of this page + * Add a new event * - * Override this method to show a custom title. - * - * @return string Title of the page + * @return void */ - function title() + + function newRSVP() { - if (empty($this->user)) { - return _m('Hello'); + try { + $saved = RSVP::saveNew($this->user->getProfile(), + $this->event, + $this->type); + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + if ($this->boolean('ajax')) { + $rsvp = RSVP::fromNotice($saved); + header('Content-Type: text/xml;charset=utf-8'); + $this->xw->startDocument('1.0', 'UTF-8'); + $this->elementStart('html'); + $this->elementStart('head'); + // TRANS: Page title after sending a notice. + $this->element('title', null, _('Event saved')); + $this->elementEnd('head'); + $this->elementStart('body'); + $this->elementStart('body'); + $cancel = new CancelRSVPForm($rsvp, $this); + $cancel->show(); + $this->elementEnd('body'); + $this->elementEnd('body'); + $this->elementEnd('html'); } else { - return sprintf(_m('Hello, %s!'), $this->user->nickname); + common_redirect($saved->bestUrl(), 303); } } /** - * Show content in the content area - * - * The default StatusNet page has a lot of decorations: menus, - * logos, tabs, all that jazz. This method is used to show - * content in the content area of the page; it's the main - * thing you want to overload. - * - * This method also demonstrates use of a plural localized string. + * Show the event form * * @return void */ + function showContent() { - if (empty($this->user)) { - $this->element('p', array('class' => 'greeting'), - _m('Hello, stranger!')); - } else { - $this->element('p', array('class' => 'greeting'), - sprintf(_m('Hello, %s'), $this->user->nickname)); - $this->element('p', array('class' => 'greeting_count'), - sprintf(_m('I have greeted you %d time.', - 'I have greeted you %d times.', - $this->gc->greeting_count), - $this->gc->greeting_count)); + if (!empty($this->error)) { + $this->element('p', 'error', $this->error); } + + $form = new RSVPForm($this->event, $this); + + $form->show(); + + return; } /** * Return true if read only. * - * Some actions only read from the database; others read and write. - * The simple database load-balancer built into StatusNet will - * direct read-only actions to database mirrors (if they are configured), - * and read-write actions to the master database. + * MAY override * - * This defaults to false to avoid data integrity issues, but you - * should make sure to overload it for performance gains. - * - * @param array $args other arguments, if RO/RW status depends on them. + * @param array $args other arguments * * @return boolean is read only action? */ + function isReadOnly($args) { - return false; + if ($_SERVER['REQUEST_METHOD'] == 'GET' || + $_SERVER['REQUEST_METHOD'] == 'HEAD') { + return true; + } else { + return false; + } } } diff --git a/plugins/Event/rsvpform.php b/plugins/Event/rsvpform.php new file mode 100644 index 0000000000..ad30f6a36e --- /dev/null +++ b/plugins/Event/rsvpform.php @@ -0,0 +1,120 @@ +. + * + * @category Event + * @package StatusNet + * @author Evan Prodromou + * @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 + * @copyright 2011 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class RSVPForm extends Form +{ + protected $event = null; + + function __construct($event, $out=null) + { + parent::__construct($out); + $this->event = $event; + } + + /** + * 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('newrsvp'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'new_rsvp_data')); + + $this->out->text(_('RSVP: ')); + + $this->out->hidden('event', $this->event->id); + + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('yes', _m('BUTTON', 'Yes')); + $this->out->submit('no', _m('BUTTON', 'No')); + $this->out->submit('maybe', _m('BUTTON', 'Maybe')); + } +} diff --git a/plugins/Event/showevent.php b/plugins/Event/showevent.php index f8b032c111..7fb702f9db 100644 --- a/plugins/Event/showevent.php +++ b/plugins/Event/showevent.php @@ -64,13 +64,13 @@ class ShoweventAction extends ShownoticeAction $this->id = $this->trimmed('id'); - $this->event = Event::staticGet('id', $this->id); + $this->event = Happening::staticGet('id', $this->id); if (empty($this->event)) { throw new ClientException(_('No such event.'), 404); } - $this->notice = $event->getNotice(); + $this->notice = $this->event->getNotice(); if (empty($this->notice)) { // Did we used to have it, and it got deleted?