]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Kinda complete and kinda working-ish events
authorEvan Prodromou <evan@status.net>
Wed, 9 Mar 2011 07:33:26 +0000 (02:33 -0500)
committerEvan Prodromou <evan@status.net>
Wed, 9 Mar 2011 07:37:55 +0000 (02:37 -0500)
plugins/Event/Event.php [deleted file]
plugins/Event/EventPlugin.php
plugins/Event/Happening.php [new file with mode: 0644]
plugins/Event/RSVP.php
plugins/Event/eventform.php [new file with mode: 0644]
plugins/Event/newevent.php
plugins/Event/showevent.php [new file with mode: 0644]
plugins/Event/showrsvp.php [new file with mode: 0644]

diff --git a/plugins/Event/Event.php b/plugins/Event/Event.php
deleted file mode 100644 (file)
index 38d68c9..0000000
+++ /dev/null
@@ -1,184 +0,0 @@
-<?php
-/**
- * Data class for counting greetings
- *
- * PHP version 5
- *
- * @category Data
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-if (!defined('STATUSNET')) {
-    exit(1);
-}
-
-require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
-
-/**
- * Data class for counting greetings
- *
- * We use the DB_DataObject framework for data classes in StatusNet. Each
- * table maps to a particular data class, making it easier to manipulate
- * data.
- *
- * Data classes should extend Memcached_DataObject, the (slightly misnamed)
- * extension of DB_DataObject that provides caching, internationalization,
- * and other bits of good functionality to StatusNet-specific data classes.
- *
- * @category Action
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * @see      DB_DataObject
- */
-
-class User_greeting_count extends Memcached_DataObject
-{
-    public $__table = 'user_greeting_count'; // table name
-    public $user_id;                         // int(4)  primary_key not_null
-    public $greeting_count;                  // int(4)
-
-    /**
-     * Get an instance by key
-     *
-     * This is a utility method to get a single instance with a given key value.
-     *
-     * @param string $k Key to use to lookup (usually 'user_id' for this class)
-     * @param mixed  $v Value to lookup
-     *
-     * @return User_greeting_count object found, or null for no hits
-     *
-     */
-    function staticGet($k, $v=null)
-    {
-        return Memcached_DataObject::staticGet('User_greeting_count', $k, $v);
-    }
-
-    /**
-     * return table definition for DB_DataObject
-     *
-     * DB_DataObject needs to know something about the table to manipulate
-     * instances. This method provides all the DB_DataObject needs to know.
-     *
-     * @return array array of column definitions
-     */
-    function table()
-    {
-        return array('user_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
-                     'greeting_count' => DB_DATAOBJECT_INT);
-    }
-
-    /**
-     * return key definitions for DB_DataObject
-     *
-     * DB_DataObject needs to know about keys that the table has, since it
-     * won't appear in StatusNet's own keys list. In most cases, this will
-     * simply reference your keyTypes() function.
-     *
-     * @return array list of key field names
-     */
-    function keys()
-    {
-        return array_keys($this->keyTypes());
-    }
-
-    /**
-     * return key definitions for Memcached_DataObject
-     *
-     * Our caching system uses the same key definitions, but uses a different
-     * method to get them. This key information is used to store and clear
-     * cached data, so be sure to list any key that will be used for static
-     * lookups.
-     *
-     * @return array associative array of key definitions, field name to type:
-     *         'K' for primary key: for compound keys, add an entry for each component;
-     *         'U' for unique keys: compound keys are not well supported here.
-     */
-    function keyTypes()
-    {
-        return array('user_id' => 'K');
-    }
-
-    /**
-     * Magic formula for non-autoincrementing integer primary keys
-     *
-     * If a table has a single integer column as its primary key, DB_DataObject
-     * assumes that the column is auto-incrementing and makes a sequence table
-     * to do this incrementation. Since we don't need this for our class, we
-     * overload this method and return the magic formula that DB_DataObject needs.
-     *
-     * @return array magic three-false array that stops auto-incrementing.
-     */
-    function sequenceKey()
-    {
-        return array(false, false, false);
-    }
-
-    /**
-     * Increment a user's greeting count and return instance
-     *
-     * This method handles the ins and outs of creating a new greeting_count for a
-     * user or fetching the existing greeting count and incrementing its value.
-     *
-     * @param integer $user_id ID of the user to get a count for
-     *
-     * @return User_greeting_count instance for this user, with count already incremented.
-     */
-    static function inc($user_id)
-    {
-        $gc = User_greeting_count::staticGet('user_id', $user_id);
-
-        if (empty($gc)) {
-
-            $gc = new User_greeting_count();
-
-            $gc->user_id        = $user_id;
-            $gc->greeting_count = 1;
-
-            $result = $gc->insert();
-
-            if (!$result) {
-                // TRANS: Exception thrown when the user greeting count could not be saved in the database.
-                // TRANS: %d is a user ID (number).
-                throw Exception(sprintf(_m("Could not save new greeting count for %d."),
-                                        $user_id));
-            }
-        } else {
-            $orig = clone($gc);
-
-            $gc->greeting_count++;
-
-            $result = $gc->update($orig);
-
-            if (!$result) {
-                // TRANS: Exception thrown when the user greeting count could not be saved in the database.
-                // TRANS: %d is a user ID (number).
-                throw Exception(sprintf(_m("Could not increment greeting count for %d."),
-                                        $user_id));
-            }
-        }
-
-        return $gc;
-    }
-}
index af0875c805f844ce2980de8e4c5720ffb6ab56b4..d6d7e00fc12a8de3d25740462e21ed91dbf1f242 100644 (file)
@@ -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 (file)
index 0000000..054e57c
--- /dev/null
@@ -0,0 +1,198 @@
+<?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 $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(_('<span class="vevent">'.
+                              '<span class="summary">%s</span> '.
+                              '<abbr class="dtstart" title="%s">%s</a> - '.
+                              '<abbr class="dtend" title="%s">%s</a> '.
+                              '(<span class="location">%s</span>): '.
+                              '<span class="description">%s</span> '.
+                              '</span>'),
+                            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);
+    }
+}
index 38d68c91ed35a473041874bc32d0c15cfa14048d..69cae4b7dc5972556c96ff0eac540ef13acf027e 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * Data class for counting greetings
+ * Data class for event RSVPs
  *
  * PHP version 5
  *
@@ -11,7 +11,7 @@
  * @link     http://status.net/
  *
  * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
+ * 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
@@ -31,154 +31,169 @@ 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 class for event RSVPs
  *
- * 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
+ * @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      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());
-    }
+        if (array_key_exists('uri', $options)) {
+            $other = RSVP::staticGet('uri', $options['uri']);
+            if (!empty($other)) {
+                throw new ClientException(_('RSVP already exists.'));
+            }
+        }
 
-    /**
-     * 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');
-    }
+        $other = RSVP::pkeyGet(array('profile_id' => $profile->id,
+                                     'event_id' => $event->id));
 
-    /**
-     * 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);
-    }
+        if (!empty($other)) {
+            throw new ClientException(_('RSVP already exists.'));
+        }
 
-    /**
-     * 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);
+        $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 (empty($gc)) {
+        if (array_key_exists('uri', $options)) {
+            $rsvp->uri = $options['uri'];
+        } else {
+            $rsvp->uri = common_local_url('showrsvp',
+                                        array('id' => $rsvp->id));
+        }
 
-            $gc = new User_greeting_count();
+        $rsvp->insert();
 
-            $gc->user_id        = $user_id;
-            $gc->greeting_count = 1;
+        // XXX: come up with something sexier
 
-            $result = $gc->insert();
+        $content = sprintf(_('RSVPed %s for an event.'),
+                           ($result == RSVP::POSITIVE) ? _('positively') :
+                           ($result == RSVP::NEGATIVE) ? _('negatively') : _('possibly'));
+        
+        $rendered = $content;
 
-            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);
+        $options = array_merge(array('object_type' => $result),
+                               $options);
 
-            $gc->greeting_count++;
+        if (!array_key_exists('uri', $options)) {
+            $options['uri'] = $rsvp->uri;
+        }
 
-            $result = $gc->update($orig);
+        $eventNotice = $event->getNotice();
 
-            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 (!empty($eventNotice)) {
+            $options['reply_to'] = $eventNotice->id;
         }
 
-        return $gc;
+        $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 (file)
index 0000000..8347639
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for entering 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);
+}
+
+/**
+ * Form for adding 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 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'));
+    }
+}
index a793ac6de2c98948a13c7268c7cad6312c85a446..66b15ea41b0420a24e9f276657073aabb34ecd66 100644 (file)
@@ -1,18 +1,12 @@
 <?php
 /**
- * Give a warm greeting to our friendly user
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
  *
+ * Add a new event
+ * 
  * PHP version 5
  *
- * @category Sample
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
- *
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2009, StatusNet, Inc.
- *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU Affero General Public License as published by
  * the Free Software Foundation, either version 3 of the License, or
  *
  * 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);
 }
 
 /**
- * Give a warm greeting to our friendly user
- *
- * This sample action shows some basic ways of doing output in an action
- * class.
+ * Add a new event
  *
- * Action classes have several output methods that they override from
- * the parent class.
- *
- * @category Sample
- * @package  StatusNet
- * @author   Evan Prodromou <evan@status.net>
- * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
- * @link     http://status.net/
+ * @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 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
-     *
-     * 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.
+     * Returns the title of the action
      *
-     * Action classes should run parent::prepare($args) as the first
-     * line of this method to make sure the default argument-processing
-     * happens.
+     * @return string Action title
+     */
+
+    function title()
+    {
+        return _('New event');
+    }
+
+    /**
+     * For initializing members of the class.
      *
-     * @param array $args $_REQUEST args
+     * @param array $argarray misc. arguments
      *
-     * @return boolean success flag
+     * @return boolean true
      */
-    function prepare($args)
+
+    function prepare($argarray)
     {
-        parent::prepare($args);
+        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)
+
+    function handle($argarray=null)
     {
-        parent::handle($args);
+        parent::handle($argarray);
+
+        if ($this->isPost()) {
+            $this->newEvent();
+        } else {
+            $this->showPage();
+        }
 
-        $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 newEvent()
     {
-        if (empty($this->user)) {
-            return _m('Hello');
-        } else {
-            return sprintf(_m('Hello, %s!'), $this->user->nickname);
+        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 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 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.
-     *
-     * This defaults to false to avoid data integrity issues, but you
-     * should make sure to overload it for performance gains.
+     * MAY override
      *
-     * @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 (file)
index 0000000..f8b032c
--- /dev/null
@@ -0,0 +1,109 @@
+<?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;
+
+    /**
+     * 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 (file)
index 0000000..fde1d48
--- /dev/null
@@ -0,0 +1,117 @@
+<?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;
+
+    /**
+     * 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);
+    }
+}