From f715821cf0a4f95eb0d265eda62abed294474b2b Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Wed, 9 Mar 2011 02:33:26 -0500 Subject: [PATCH] Kinda complete and kinda working-ish events --- plugins/Event/Event.php | 184 ------------------------ plugins/Event/EventPlugin.php | 224 ++++++++++++++++++++++++++++- plugins/Event/Happening.php | 198 ++++++++++++++++++++++++++ plugins/Event/RSVP.php | 255 ++++++++++++++++++---------------- plugins/Event/eventform.php | 157 +++++++++++++++++++++ plugins/Event/newevent.php | 217 ++++++++++++++++------------- plugins/Event/showevent.php | 109 +++++++++++++++ plugins/Event/showrsvp.php | 117 ++++++++++++++++ 8 files changed, 1060 insertions(+), 401 deletions(-) delete mode 100644 plugins/Event/Event.php create mode 100644 plugins/Event/Happening.php create mode 100644 plugins/Event/eventform.php create mode 100644 plugins/Event/showevent.php create mode 100644 plugins/Event/showrsvp.php diff --git a/plugins/Event/Event.php b/plugins/Event/Event.php deleted file mode 100644 index 38d68c91ed..0000000000 --- a/plugins/Event/Event.php +++ /dev/null @@ -1,184 +0,0 @@ - - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -require_once INSTALLDIR . '/classes/Memcached_DataObject.php'; - -/** - * Data class for counting greetings - * - * We use the DB_DataObject framework for data classes in StatusNet. Each - * table maps to a particular data class, making it easier to manipulate - * data. - * - * Data classes should extend Memcached_DataObject, the (slightly misnamed) - * extension of DB_DataObject that provides caching, internationalization, - * and other bits of good functionality to StatusNet-specific data classes. - * - * @category Action - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 - * @link http://status.net/ - * - * @see DB_DataObject - */ - -class User_greeting_count extends Memcached_DataObject -{ - public $__table = 'user_greeting_count'; // table name - public $user_id; // int(4) primary_key not_null - public $greeting_count; // int(4) - - /** - * Get an instance by key - * - * This is a utility method to get a single instance with a given key value. - * - * @param string $k Key to use to lookup (usually 'user_id' for this class) - * @param mixed $v Value to lookup - * - * @return User_greeting_count object found, or null for no hits - * - */ - function staticGet($k, $v=null) - { - return Memcached_DataObject::staticGet('User_greeting_count', $k, $v); - } - - /** - * return table definition for DB_DataObject - * - * DB_DataObject needs to know something about the table to manipulate - * instances. This method provides all the DB_DataObject needs to know. - * - * @return array array of column definitions - */ - function table() - { - return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'greeting_count' => DB_DATAOBJECT_INT); - } - - /** - * return key definitions for DB_DataObject - * - * DB_DataObject needs to know about keys that the table has, since it - * won't appear in StatusNet's own keys list. In most cases, this will - * simply reference your keyTypes() function. - * - * @return array list of key field names - */ - function keys() - { - return array_keys($this->keyTypes()); - } - - /** - * return key definitions for Memcached_DataObject - * - * Our caching system uses the same key definitions, but uses a different - * method to get them. This key information is used to store and clear - * cached data, so be sure to list any key that will be used for static - * lookups. - * - * @return array associative array of key definitions, field name to type: - * 'K' for primary key: for compound keys, add an entry for each component; - * 'U' for unique keys: compound keys are not well supported here. - */ - function keyTypes() - { - return array('user_id' => 'K'); - } - - /** - * Magic formula for non-autoincrementing integer primary keys - * - * If a table has a single integer column as its primary key, DB_DataObject - * assumes that the column is auto-incrementing and makes a sequence table - * to do this incrementation. Since we don't need this for our class, we - * overload this method and return the magic formula that DB_DataObject needs. - * - * @return array magic three-false array that stops auto-incrementing. - */ - function sequenceKey() - { - return array(false, false, false); - } - - /** - * Increment a user's greeting count and return instance - * - * This method handles the ins and outs of creating a new greeting_count for a - * user or fetching the existing greeting count and incrementing its value. - * - * @param integer $user_id ID of the user to get a count for - * - * @return User_greeting_count instance for this user, with count already incremented. - */ - static function inc($user_id) - { - $gc = User_greeting_count::staticGet('user_id', $user_id); - - if (empty($gc)) { - - $gc = new User_greeting_count(); - - $gc->user_id = $user_id; - $gc->greeting_count = 1; - - $result = $gc->insert(); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m("Could not save new greeting count for %d."), - $user_id)); - } - } else { - $orig = clone($gc); - - $gc->greeting_count++; - - $result = $gc->update($orig); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m("Could not increment greeting count for %d."), - $user_id)); - } - } - - return $gc; - } -} diff --git a/plugins/Event/EventPlugin.php b/plugins/Event/EventPlugin.php index af0875c805..d6d7e00fc1 100644 --- a/plugins/Event/EventPlugin.php +++ b/plugins/Event/EventPlugin.php @@ -58,7 +58,7 @@ class EventPlugin extends MicroappPlugin { $schema = Schema::get(); - $schema->ensureTable('event', Event::schemaDef()); + $schema->ensureTable('event', Happening::schemaDef()); $schema->ensureTable('rsvp', RSVP::schemaDef()); return true; @@ -79,9 +79,14 @@ class EventPlugin extends MicroappPlugin { case 'NeweventAction': case 'NewrsvpAction': + case 'ShoweventAction': + case 'ShowrsvpAction': include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; return false; - case 'Event': + case 'EventForm': + include_once $dir . '/'.strtolower($cls).'.php'; + break; + case 'Happening': case 'RSVP': include_once $dir . '/'.$cls.'.php'; return false; @@ -104,6 +109,12 @@ class EventPlugin extends MicroappPlugin array('action' => 'newevent')); $m->connect('main/event/rsvp', array('action' => 'newrsvp')); + $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}')); return true; } @@ -117,4 +128,213 @@ class EventPlugin extends MicroappPlugin _m('Event invitations and RSVPs.')); return true; } + + function appTitle() { + return _m('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, $actor, $options=array()) + { + if (count($activity->objects) != 1) { + throw new Exception('Too many activity objects.'); + } + + $happeningObj = $activity->objects[0]; + + if ($happeningObj->type != Happening::OBJECT_TYPE) { + throw new Exception('Wrong type for object.'); + } + + $notice = null; + + switch ($activity->verb) { + case ActivityVerb::POST: + $notice = Happening::saveNew($actor, + $start_time, + $end_time, + $happeningObj->title, + null, + $happeningObj->summary, + $options); + break; + case RSVP::POSITIVE: + case RSVP::NEGATIVE: + case RSVP::POSSIBLE: + $happening = Happening::staticGet('uri', $happeningObj->id); + if (empty($happening)) { + // FIXME: save the event + throw new Exception("RSVP for unknown event."); + } + $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options); + break; + default: + throw new Exception("Unknown verb for events"); + } + + return $notice; + } + + /** + * Turn a Notice into an activity object + * + * @param Notice $notice + * + * @return ActivityObject + */ + + function activityObjectFromNotice($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)) { + throw new Exception("Unknown object type."); + } + + $notice = $happening->getNotice(); + + if (empty($notice)) { + throw new Exception("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->bestUrl(); + + // 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_date)); + + $obj->extra[] = array('dtend', + array('xmlns' => 'urn:ietf:params:xml:ns:xcal'), + common_date_iso8601($happening->end_date)); + + // XXX: probably need other stuff here + + return $obj; + } + + /** + * Change the verb on RSVP notices + * + * @param Notice $notice + * + * @return ActivityObject + */ + + function onEndNoticeAsActivity($notice, &$act) { + switch ($notice->object_type) { + case RSVP::POSITIVE: + case RSVP::NEGATIVE: + case RSVP::POSSIBLE: + $act->verb = $notice->object_type; + break; + } + return true; + } + + /** + * Custom HTML output for our notices + * + * @param Notice $notice + * @param HTMLOutputter $out + */ + + function showNotice($notice, $out) + { + switch ($notice->object_type) { + case Happening::OBJECT_TYPE: + $this->showEventNotice($notice, $out); + break; + case RSVP::POSITIVE: + case RSVP::NEGATIVE: + case RSVP::POSSIBLE: + $this->showRSVPNotice($notice, $out); + break; + } + } + + function showRSVPNotice($notice, $out) + { + $out->element('span', null, 'RSVP'); + return; + } + + function showEventNotice($notice, $out) + { + $out->raw($notice->rendered); + return; + } + + /** + * 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) + { + switch ($notice->object_type) { + case Happening::OBJECT_TYPE: + $happening = Happening::fromNotice($notice); + $happening->delete(); + break; + case RSVP::POSITIVE: + case RSVP::NEGATIVE: + case RSVP::POSSIBLE: + $rsvp = RSVP::fromNotice($notice); + $rsvp->delete(); + break; + } + } } diff --git a/plugins/Event/Happening.php b/plugins/Event/Happening.php new file mode 100644 index 0000000000..054e57c732 --- /dev/null +++ b/plugins/Event/Happening.php @@ -0,0 +1,198 @@ + + * @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 . + */ + +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 + * @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 $description; // text + public $created; // datetime + + /** + * Get an instance by key + * + * @param string $k Key to use to lookup (usually 'id' for this class) + * @param mixed $v Value to lookup + * + * @return Happening object found, or null for no hits + * + */ + function staticGet($k, $v=null) + { + return Memcached_DataObject::staticGet('Happening', $k, $v); + } + + /** + * 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'), + 'start_time' => array('type' => 'datetime'), + 'end_time' => array('type' => 'datetime'), + 'title' => array('type' => 'varchar', + 'length' => 255, + 'not null' => true), + 'location' => array('type' => 'varchar', + 'length' => 255, + 'not null' => true), + 'description' => array('type' => 'text'), + 'created' => array('type' => 'datetime', + 'not null' => true), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'happening_uri_key' => array('uri'), + ), + ); + } + + function saveNew($profile, $start_time, $end_time, $title, $location, $description, $options) + { + if (array_key_exists('uri', $options)) { + $other = Happening::staticGet('uri', $options['uri']); + if (!empty($other)) { + throw new ClientException(_('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; + + 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? + + $content = sprintf(_('"%s" %s - %s (%s): %s'), + $title, + common_exact_date($start_time), + common_exact_date($end_time), + $location, + $description); + + $rendered = sprintf(_(''. + '%s '. + '%s - '. + '%s '. + '(%s): '. + '%s '. + ''), + htmlspecialchars($title), + htmlspecialchars(common_date_iso8601($start_time)), + htmlspecialchars(common_exact_date($start_time)), + htmlspecialchars(common_date_iso8601($end_time)), + htmlspecialchars(common_exact_date($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; + } + + $saved = Notice::saveNew($profile->id, + $content, + array_key_exists('source', $options) ? + $options['source'] : 'web', + $options); + + return $saved; + } + + function getNotice() + { + return Notice::staticGet('uri', $this->uri); + } + + static function fromNotice() + { + return Happening::staticGet('uri', $notice->uri); + } +} diff --git a/plugins/Event/RSVP.php b/plugins/Event/RSVP.php index 38d68c91ed..69cae4b7dc 100644 --- a/plugins/Event/RSVP.php +++ b/plugins/Event/RSVP.php @@ -1,6 +1,6 @@ * @license http://www.fsf.org/licensing/licenses/agpl.html AGPLv3 * @link http://status.net/ * - * @see DB_DataObject + * @see Managed_DataObject */ -class User_greeting_count extends Memcached_DataObject +class RSVP extends Managed_DataObject { - public $__table = 'user_greeting_count'; // table name - public $user_id; // int(4) primary_key not_null - public $greeting_count; // int(4) + 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 $result; // tinyint + public $created; // datetime /** * Get an instance by key * - * This is a utility method to get a single instance with a given key value. - * - * @param string $k Key to use to lookup (usually 'user_id' for this class) + * @param string $k Key to use to lookup (usually 'id' for this class) * @param mixed $v Value to lookup * - * @return User_greeting_count object found, or null for no hits + * @return RSVP object found, or null for no hits * */ function staticGet($k, $v=null) { - return Memcached_DataObject::staticGet('User_greeting_count', $k, $v); + return Memcached_DataObject::staticGet('RSVP', $k, $v); } /** - * return table definition for DB_DataObject - * - * DB_DataObject needs to know something about the table to manipulate - * instances. This method provides all the DB_DataObject needs to know. - * - * @return array array of column definitions + * The One True Thingy that must be defined and declared. */ - function table() + public static function schemaDef() { - return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL, - 'greeting_count' => DB_DATAOBJECT_INT); + return array( + 'description' => 'A real-world 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'), + 'result' => array('type' => 'tinyint', + 'description' => '1, 0, or null 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')), + ); } - /** - * return key definitions for DB_DataObject - * - * DB_DataObject needs to know about keys that the table has, since it - * won't appear in StatusNet's own keys list. In most cases, this will - * simply reference your keyTypes() function. - * - * @return array list of key field names - */ - function keys() + function saveNew($profile, $event, $result, $options) { - return array_keys($this->keyTypes()); - } - - /** - * return key definitions for Memcached_DataObject - * - * Our caching system uses the same key definitions, but uses a different - * method to get them. This key information is used to store and clear - * cached data, so be sure to list any key that will be used for static - * lookups. - * - * @return array associative array of key definitions, field name to type: - * 'K' for primary key: for compound keys, add an entry for each component; - * 'U' for unique keys: compound keys are not well supported here. - */ - function keyTypes() - { - return array('user_id' => 'K'); - } - - /** - * Magic formula for non-autoincrementing integer primary keys - * - * If a table has a single integer column as its primary key, DB_DataObject - * assumes that the column is auto-incrementing and makes a sequence table - * to do this incrementation. Since we don't need this for our class, we - * overload this method and return the magic formula that DB_DataObject needs. - * - * @return array magic three-false array that stops auto-incrementing. - */ - function sequenceKey() - { - return array(false, false, false); - } - - /** - * Increment a user's greeting count and return instance - * - * This method handles the ins and outs of creating a new greeting_count for a - * user or fetching the existing greeting count and incrementing its value. - * - * @param integer $user_id ID of the user to get a count for - * - * @return User_greeting_count instance for this user, with count already incremented. - */ - static function inc($user_id) - { - $gc = User_greeting_count::staticGet('user_id', $user_id); - - if (empty($gc)) { - - $gc = new User_greeting_count(); - - $gc->user_id = $user_id; - $gc->greeting_count = 1; - - $result = $gc->insert(); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m("Could not save new greeting count for %d."), - $user_id)); - } - } else { - $orig = clone($gc); - - $gc->greeting_count++; - - $result = $gc->update($orig); - - if (!$result) { - // TRANS: Exception thrown when the user greeting count could not be saved in the database. - // TRANS: %d is a user ID (number). - throw Exception(sprintf(_m("Could not increment greeting count for %d."), - $user_id)); + if (array_key_exists('uri', $options)) { + $other = RSVP::staticGet('uri', $options['uri']); + if (!empty($other)) { + throw new ClientException(_('RSVP already exists.')); } } - return $gc; + $other = RSVP::pkeyGet(array('profile_id' => $profile->id, + 'event_id' => $event->id)); + + if (!empty($other)) { + throw new ClientException(_('RSVP already exists.')); + } + + $rsvp = new RSVP(); + + $rsvp->id = UUID::gen(); + $rsvp->profile_id = $profile->id; + $rsvp->event_id = $event->id; + $rsvp->result = self::codeFor($result); + + 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(); + + // XXX: come up with something sexier + + $content = sprintf(_('RSVPed %s for an event.'), + ($result == RSVP::POSITIVE) ? _('positively') : + ($result == RSVP::NEGATIVE) ? _('negatively') : _('possibly')); + + $rendered = $content; + + $options = array_merge(array('object_type' => $result), + $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) + { + return ($verb == RSVP::POSITIVE) ? 1 : + ($verb == RSVP::NEGATIVE) ? 0 : null; + } + + function verbFor($code) + { + return ($code == 1) ? RSVP::POSITIVE : + ($code == 0) ? RSVP::NEGATIVE : null; + } + + function getNotice() + { + return Notice::staticGet('uri', $this->uri); + } + + static function fromNotice() + { + return RSVP::staticGet('uri', $notice->uri); } } diff --git a/plugins/Event/eventform.php b/plugins/Event/eventform.php new file mode 100644 index 0000000000..8347639b5b --- /dev/null +++ b/plugins/Event/eventform.php @@ -0,0 +1,157 @@ +. + * + * @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); +} + +/** + * Form for adding 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 EventForm extends Form +{ + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'form_new_event'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_settings ajax'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('newevent'); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('fieldset', array('id' => 'new_bookmark_data')); + $this->out->elementStart('ul', 'form_data'); + + $this->li(); + $this->out->input('title', + _('Title'), + null, + _('Title of the event')); + $this->unli(); + + $this->li(); + $this->out->input('startdate', + _('Start date'), + null, + _('Date the event starts')); + $this->unli(); + + $this->li(); + $this->out->input('starttime', + _('Start time'), + null, + _('Time the event starts')); + $this->unli(); + + $this->li(); + $this->out->input('enddate', + _('End date'), + null, + _('Date the event ends')); + $this->unli(); + + $this->li(); + $this->out->input('endtime', + _('End time'), + null, + _('Time the event ends')); + $this->unli(); + + $this->li(); + $this->out->input('location', + _('Location'), + null, + _('Event location')); + $this->unli(); + + $this->li(); + $this->out->input('description', + _('Description'), + null, + _('Description of the event')); + $this->unli(); + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('submit', _m('BUTTON', 'Save')); + } +} diff --git a/plugins/Event/newevent.php b/plugins/Event/newevent.php index a793ac6de2..66b15ea41b 100644 --- a/plugins/Event/newevent.php +++ b/plugins/Event/newevent.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. + * + * 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 @@ -25,140 +19,173 @@ * * 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 + * Add a new 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 NeweventAction extends Action { - var $user = null; - var $gc = null; + protected $user = null; + protected $error = null; + protected $complete = null; + protected $title = null; + protected $location = null; + protected $description = null; + protected $start_time = null; + protected $end_time = 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 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)) { - $this->gc = User_greeting_count::inc($this->user->id); + if (empty($this->user)) { + throw new ClientException(_("Must be logged in to post a event."), + 403); } + if ($this->isPost()) { + $this->checkSessionToken(); + } + + $this->title = $this->trimmed('title'); + $this->location = $this->trimmed('location'); + $this->description = $this->trimmed('description'); + 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(); - } - - /** - * Title of this page - * - * Override this method to show a custom title. - * - * @return string Title of the page - */ - function title() + function handle($argarray=null) { - if (empty($this->user)) { - return _m('Hello'); + parent::handle($argarray); + + if ($this->isPost()) { + $this->newEvent(); } else { - return sprintf(_m('Hello, %s!'), $this->user->nickname); + $this->showPage(); } + + return; } /** - * 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. + * Add a new event * * @return void */ + + function newEvent() + { + try { + if (empty($this->title)) { + throw new ClientException(_('Event must have a title.')); + } + + if (empty($this->url)) { + throw new ClientException(_('Event must have an URL.')); + } + + + $saved = Event::saveNew($this->user->getProfile(), + $this->title, + $this->url, + $this->tags, + $this->description); + + } catch (ClientException $ce) { + $this->error = $ce->getMessage(); + $this->showPage(); + return; + } + + common_redirect($saved->bestUrl(), 303); + } + + /** + * 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 EventForm($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/showevent.php b/plugins/Event/showevent.php new file mode 100644 index 0000000000..f8b032c111 --- /dev/null +++ b/plugins/Event/showevent.php @@ -0,0 +1,109 @@ +. + * + * @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); +} + +/** + * Show a single event, with associated information + * + * @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 ShoweventAction extends ShownoticeAction +{ + protected $id = null; + protected $event = null; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + OwnerDesignAction::prepare($argarray); + + $this->id = $this->trimmed('id'); + + $this->event = Event::staticGet('id', $this->id); + + if (empty($this->event)) { + throw new ClientException(_('No such event.'), 404); + } + + $this->notice = $event->getNotice(); + + if (empty($this->notice)) { + // Did we used to have it, and it got deleted? + throw new ClientException(_('No such event.'), 404); + } + + $this->user = User::staticGet('id', $this->event->profile_id); + + if (empty($this->user)) { + throw new ClientException(_('No such user.'), 404); + } + + $this->profile = $this->user->getProfile(); + + if (empty($this->profile)) { + throw new ServerException(_('User without a profile.')); + } + + $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); + + return true; + } + + /** + * Title of the page + * + * Used by Action class for layout. + * + * @return string page tile + */ + + function title() + { + return $this->event->title; + } +} diff --git a/plugins/Event/showrsvp.php b/plugins/Event/showrsvp.php new file mode 100644 index 0000000000..fde1d48f0e --- /dev/null +++ b/plugins/Event/showrsvp.php @@ -0,0 +1,117 @@ +. + * + * @category RSVP + * @package StatusNet + * @author Evan Prodromou + * @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 + * @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; + + /** + * For initializing members of the class. + * + * @param array $argarray misc. arguments + * + * @return boolean true + */ + + function prepare($argarray) + { + OwnerDesignAction::prepare($argarray); + + $this->id = $this->trimmed('id'); + + $this->rsvp = RSVP::staticGet('id', $this->id); + + if (empty($this->rsvp)) { + throw new ClientException(_('No such RSVP.'), 404); + } + + $this->event = $this->rsvp->getEvent(); + + if (empty($this->event)) { + throw new ClientException(_('No such Event.'), 404); + } + + $this->notice = $this->rsvp->getNotice(); + + if (empty($this->notice)) { + // Did we used to have it, and it got deleted? + throw new ClientException(_('No such RSVP.'), 404); + } + + $this->user = User::staticGet('id', $this->rsvp->profile_id); + + if (empty($this->user)) { + throw new ClientException(_('No such user.'), 404); + } + + $this->profile = $this->user->getProfile(); + + if (empty($this->profile)) { + throw new ServerException(_('User without a profile.')); + } + + $this->avatar = $this->profile->getAvatar(AVATAR_PROFILE_SIZE); + + return true; + } + + /** + * Title of the page + * + * Used by Action class for layout. + * + * @return string page tile + */ + + function title() + { + return sprintf(_('%s\'s RSVP for "%s"'), + $this->user->nickname, + $this->event->title); + } +}