]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge remote branch 'origin/pluginstatic' into testing
authorEvan Prodromou <evan@status.net>
Tue, 8 Feb 2011 18:39:17 +0000 (13:39 -0500)
committerEvan Prodromou <evan@status.net>
Tue, 8 Feb 2011 18:39:17 +0000 (13:39 -0500)
27 files changed:
EVENTS.txt
actions/editgroup.php
actions/inbox.php
actions/newgroup.php
actions/outbox.php
actions/showgroup.php
actions/showmessage.php
classes/Notice.php
classes/User_group.php
lib/apiaction.php
lib/commandinterpreter.php
lib/common.php
lib/groupeditform.php
lib/mailbox.php
lib/messagelist.php [new file with mode: 0644]
lib/messagelistitem.php [new file with mode: 0644]
plugins/GroupPrivateMessage/GroupPrivateMessagePlugin.php [new file with mode: 0644]
plugins/GroupPrivateMessage/Group_message.php [new file with mode: 0644]
plugins/GroupPrivateMessage/Group_message_profile.php [new file with mode: 0644]
plugins/GroupPrivateMessage/Group_privacy_settings.php [new file with mode: 0644]
plugins/GroupPrivateMessage/groupinbox.php [new file with mode: 0644]
plugins/GroupPrivateMessage/groupmessagecommand.php [new file with mode: 0644]
plugins/GroupPrivateMessage/groupmessageform.php [new file with mode: 0644]
plugins/GroupPrivateMessage/groupmessagelist.php [new file with mode: 0644]
plugins/GroupPrivateMessage/groupmessagelistitem.php [new file with mode: 0644]
plugins/GroupPrivateMessage/newgroupmessage.php [new file with mode: 0644]
plugins/GroupPrivateMessage/showgroupmessage.php [new file with mode: 0644]

index 6719ba737a19b4a9a4449270ab8d635e87ee8626..f675c199a07355ddf72d6e8734e120cc3f5a375b 100644 (file)
@@ -1057,3 +1057,43 @@ StartCloseNoticeListItemElement: Before the closing </li> of a notice list eleme
 
 EndCloseNoticeListItemElement: After the closing </li> of a notice list element
 - $nli: The notice list item being shown
+
+StartGroupEditFormData: Beginning the group edit form entries
+- $form: The form widget being shown
+
+EndGroupEditFormData: Ending the group edit form entries
+- $form: The form widget being shown
+
+StartGroupSave: After initializing but before saving a group
+- &$group: group about to be saved
+
+EndGroupSave: After saving a group, aliases, and first member
+- $group: group that was saved
+
+StartInterpretCommand: Before running a command
+- $cmd: First word in the string, 'foo' in 'foo argument' 
+- $arg: Argument, if any, like 'argument' in 'foo argument'
+- $user: User who issued the command
+- &$result: Resulting command; you can set this!
+
+EndInterpretCommand: Before running a command
+- $cmd: First word in the string, 'foo' in 'foo argument' 
+- $arg: Argument, if any, like 'argument' in 'foo argument'
+- $user: User who issued the command
+- $result: Resulting command
+
+StartGroupActionsList: Start the list of actions on a group profile page (after <ul>, before first <li>)
+- $action: action being executed (for output and params)
+- $group: group for the page
+
+EndGroupActionsList: End the list of actions on a group profile page (before </ul>, after last </li>)
+- $action: action being executed (for output and params)
+- $group: group for the page
+
+StartGroupProfileElements: Start showing stuff about the group on its profile page
+- $action: action being executed (for output and params)
+- $group: group for the page
+                          
+EndGroupProfileElements: Start showing stuff about the group on its profile page
+- $action: action being executed (for output and params)
+- $group: group for the page
index ab4dbb28360e30278d8515a06a3c61b7fef85f09..0e041700511360d29b493541dee9b3d521978367 100644 (file)
@@ -177,115 +177,120 @@ class EditgroupAction extends GroupDesignAction
             return;
         }
 
-        $nickname    = Nickname::normalize($this->trimmed('nickname'));
-        $fullname    = $this->trimmed('fullname');
-        $homepage    = $this->trimmed('homepage');
-        $description = $this->trimmed('description');
-        $location    = $this->trimmed('location');
-        $aliasstring = $this->trimmed('aliases');
-
-        if ($this->nicknameExists($nickname)) {
-            // TRANS: Group edit form validation error.
-            $this->showForm(_('Nickname already in use. Try another one.'));
-            return;
-        } else if (!User_group::allowedNickname($nickname)) {
-            // TRANS: Group edit form validation error.
-            $this->showForm(_('Not a valid nickname.'));
-            return;
-        } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
-                   !Validate::uri($homepage,
-                                  array('allowed_schemes' =>
-                                        array('http', 'https')))) {
-            // TRANS: Group edit form validation error.
-            $this->showForm(_('Homepage is not a valid URL.'));
-            return;
-        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
-            // TRANS: Group edit form validation error.
-            $this->showForm(_('Full name is too long (maximum 255 characters).'));
-            return;
-        } else if (User_group::descriptionTooLong($description)) {
-            $this->showForm(sprintf(
-                // TRANS: Group edit form validation error.
-                _m('Description is too long (maximum %d character).',
-                  'Description is too long (maximum %d characters).',
-                  User_group::maxDescription()),
-                                    User_group::maxDescription()));
-            return;
-        } else if (!is_null($location) && mb_strlen($location) > 255) {
-            // TRANS: Group edit form validation error.
-            $this->showForm(_('Location is too long (maximum 255 characters).'));
-            return;
-        }
+        if (Event::handle('StartGroupSaveForm', array($this))) {
 
-        if (!empty($aliasstring)) {
-            $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
-        } else {
-            $aliases = array();
-        }
+            $nickname    = Nickname::normalize($this->trimmed('nickname'));
+            $fullname    = $this->trimmed('fullname');
+            $homepage    = $this->trimmed('homepage');
+            $description = $this->trimmed('description');
+            $location    = $this->trimmed('location');
+            $aliasstring = $this->trimmed('aliases');
 
-        if (count($aliases) > common_config('group', 'maxaliases')) {
-            // TRANS: Group edit form validation error.
-            // TRANS: %d is the maximum number of allowed aliases.
-            $this->showForm(sprintf(_m('Too many aliases! Maximum %d allowed.',
-                                       'Too many aliases! Maximum %d allowed.',
-                                       common_config('group', 'maxaliases')),
-                                    common_config('group', 'maxaliases')));
-            return;
-        }
-
-        foreach ($aliases as $alias) {
-            if (!Nickname::isValid($alias)) {
+            if ($this->nicknameExists($nickname)) {
                 // TRANS: Group edit form validation error.
-                $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                $this->showForm(_('Nickname already in use. Try another one.'));
                 return;
-            }
-            if ($this->nicknameExists($alias)) {
+            } else if (!User_group::allowedNickname($nickname)) {
+                // TRANS: Group edit form validation error.
+                $this->showForm(_('Not a valid nickname.'));
+                return;
+            } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
+                       !Validate::uri($homepage,
+                                      array('allowed_schemes' =>
+                                            array('http', 'https')))) {
+                // TRANS: Group edit form validation error.
+                $this->showForm(_('Homepage is not a valid URL.'));
+                return;
+            } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
                 // TRANS: Group edit form validation error.
-                $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
-                                        $alias));
+                $this->showForm(_('Full name is too long (maximum 255 characters).'));
+                return;
+            } else if (User_group::descriptionTooLong($description)) {
+                $this->showForm(sprintf(
+                                    // TRANS: Group edit form validation error.
+                                    _m('Description is too long (maximum %d character).',
+                                       'Description is too long (maximum %d characters).',
+                                       User_group::maxDescription()),
+                                    User_group::maxDescription()));
+                return;
+            } else if (!is_null($location) && mb_strlen($location) > 255) {
+                // TRANS: Group edit form validation error.
+                $this->showForm(_('Location is too long (maximum 255 characters).'));
                 return;
             }
-            // XXX assumes alphanum nicknames
-            if (strcmp($alias, $nickname) == 0) {
+
+            if (!empty($aliasstring)) {
+                $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
+            } else {
+                $aliases = array();
+            }
+
+            if (count($aliases) > common_config('group', 'maxaliases')) {
                 // TRANS: Group edit form validation error.
-                $this->showForm(_('Alias can\'t be the same as nickname.'));
+                // TRANS: %d is the maximum number of allowed aliases.
+                $this->showForm(sprintf(_m('Too many aliases! Maximum %d allowed.',
+                                           'Too many aliases! Maximum %d allowed.',
+                                           common_config('group', 'maxaliases')),
+                                        common_config('group', 'maxaliases')));
                 return;
             }
-        }
 
-        $this->group->query('BEGIN');
+            foreach ($aliases as $alias) {
+                if (!Nickname::isValid($alias)) {
+                    // TRANS: Group edit form validation error.
+                    $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                    return;
+                }
+                if ($this->nicknameExists($alias)) {
+                    // TRANS: Group edit form validation error.
+                    $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
+                                            $alias));
+                    return;
+                }
+                // XXX assumes alphanum nicknames
+                if (strcmp($alias, $nickname) == 0) {
+                    // TRANS: Group edit form validation error.
+                    $this->showForm(_('Alias can\'t be the same as nickname.'));
+                    return;
+                }
+            }
+
+            $this->group->query('BEGIN');
 
-        $orig = clone($this->group);
+            $orig = clone($this->group);
 
-        $this->group->nickname    = $nickname;
-        $this->group->fullname    = $fullname;
-        $this->group->homepage    = $homepage;
-        $this->group->description = $description;
-        $this->group->location    = $location;
-        $this->group->mainpage    = common_local_url('showgroup', array('nickname' => $nickname));
+            $this->group->nickname    = $nickname;
+            $this->group->fullname    = $fullname;
+            $this->group->homepage    = $homepage;
+            $this->group->description = $description;
+            $this->group->location    = $location;
+            $this->group->mainpage    = common_local_url('showgroup', array('nickname' => $nickname));
 
-        $result = $this->group->update($orig);
+            $result = $this->group->update($orig);
 
-        if (!$result) {
-            common_log_db_error($this->group, 'UPDATE', __FILE__);
-            // TRANS: Server error displayed when editing a group fails.
-            $this->serverError(_('Could not update group.'));
-        }
+            if (!$result) {
+                common_log_db_error($this->group, 'UPDATE', __FILE__);
+                // TRANS: Server error displayed when editing a group fails.
+                $this->serverError(_('Could not update group.'));
+            }
 
-        $result = $this->group->setAliases($aliases);
+            $result = $this->group->setAliases($aliases);
 
-        if (!$result) {
-            // TRANS: Server error displayed when group aliases could not be added.
-            $this->serverError(_('Could not create aliases.'));
-        }
+            if (!$result) {
+                // TRANS: Server error displayed when group aliases could not be added.
+                $this->serverError(_('Could not create aliases.'));
+            }
 
-        if ($nickname != $orig->nickname) {
-            common_log(LOG_INFO, "Saving local group info.");
-            $local = Local_group::staticGet('group_id', $this->group->id);
-            $local->setNickname($nickname);
-        }
+            if ($nickname != $orig->nickname) {
+                common_log(LOG_INFO, "Saving local group info.");
+                $local = Local_group::staticGet('group_id', $this->group->id);
+                $local->setNickname($nickname);
+            }
 
-        $this->group->query('COMMIT');
+            $this->group->query('COMMIT');
+
+            Event::handle('EndGroupSaveForm', array($this));
+        }
 
         if ($this->group->nickname != $orig->nickname) {
             common_redirect(common_local_url('editgroup',
index 3a50f4964f81ed0ca96960d10b8da3bec0955d57..6ab58f97513d7b0e5c46c2e5f694146370526177 100644 (file)
@@ -90,18 +90,9 @@ class InboxAction extends MailboxAction
         }
     }
 
-    /**
-     * Returns the profile we want to show with the message
-     *
-     * For inboxes, we show the sender; for outboxes, the recipient.
-     *
-     * @param Message $message The message to get the profile for
-     *
-     * @return Profile The profile that matches the message
-     */
-    function getMessageProfile($message)
+    function getMessageList($message)
     {
-        return $message->getFrom();
+        return new InboxMessageList($this, $message);
     }
 
     /**
@@ -115,3 +106,24 @@ class InboxAction extends MailboxAction
         return _('This is your inbox, which lists your incoming private messages.');
     }
 }
+
+class InboxMessageList extends MessageList
+{
+    function newItem($message)
+    {
+        return new InboxMessageListItem($this->out, $message);
+    }
+}
+
+class InboxMessageListItem extends MessageListItem
+{
+    /**
+     * Returns the profile we want to show with the message
+     *
+     * @return Profile The profile that matches the message
+     */
+    function getMessageProfile()
+    {
+        return $this->message->getFrom();
+    }
+}
\ No newline at end of file
index 53c95d03f0eeb2d96b5f1f888ffdbb3ded492b8f..9682b875cb0c666a058c0b241b4310d939cd2225 100644 (file)
@@ -120,103 +120,109 @@ class NewgroupAction extends Action
 
     function trySave()
     {
-        try {
-            $nickname = Nickname::normalize($this->trimmed('nickname'));
-        } catch (NicknameException $e) {
-            $this->showForm($e->getMessage());
-        }
-        $fullname    = $this->trimmed('fullname');
-        $homepage    = $this->trimmed('homepage');
-        $description = $this->trimmed('description');
-        $location    = $this->trimmed('location');
-        $aliasstring = $this->trimmed('aliases');
-
-        if ($this->nicknameExists($nickname)) {
-            // TRANS: Group create form validation error.
-            $this->showForm(_('Nickname already in use. Try another one.'));
-            return;
-        } else if (!User_group::allowedNickname($nickname)) {
-            // TRANS: Group create form validation error.
-            $this->showForm(_('Not a valid nickname.'));
-            return;
-        } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
-                   !Validate::uri($homepage,
-                                  array('allowed_schemes' =>
-                                        array('http', 'https')))) {
-            // TRANS: Group create form validation error.
-            $this->showForm(_('Homepage is not a valid URL.'));
-            return;
-        } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
-            // TRANS: Group create form validation error.
-            $this->showForm(_('Full name is too long (maximum 255 characters).'));
-            return;
-        } else if (User_group::descriptionTooLong($description)) {
-            // TRANS: Group create form validation error.
-            // TRANS: %d is the maximum number of allowed characters.
-            $this->showForm(sprintf(_m('Description is too long (maximum %d character).',
-                                       'Description is too long (maximum %d characters).',
-                                       User_group::maxDescription()),
-                                    User_group::maxDescription()));
-            return;
-        } else if (!is_null($location) && mb_strlen($location) > 255) {
-            // TRANS: Group create form validation error.
-            $this->showForm(_('Location is too long (maximum 255 characters).'));
-            return;
-        }
-
-        if (!empty($aliasstring)) {
-            $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
-        } else {
-            $aliases = array();
-        }
-
-        if (count($aliases) > common_config('group', 'maxaliases')) {
-            // TRANS: Group create form validation error.
-            // TRANS: %d is the maximum number of allowed aliases.
-            $this->showForm(sprintf(_m('Too many aliases! Maximum %d allowed.',
-                                       'Too many aliases! Maximum %d allowed.',
-                                       common_config('group', 'maxaliases')),
-                                    common_config('group', 'maxaliases')));
-            return;
-        }
+        if (Event::handle('StartGroupSaveForm', array($this))) {
+            try {
+                $nickname = Nickname::normalize($this->trimmed('nickname'));
+            } catch (NicknameException $e) {
+                $this->showForm($e->getMessage());
+            }
+            $fullname    = $this->trimmed('fullname');
+            $homepage    = $this->trimmed('homepage');
+            $description = $this->trimmed('description');
+            $location    = $this->trimmed('location');
+            $aliasstring = $this->trimmed('aliases');
 
-        foreach ($aliases as $alias) {
-            if (!Nickname::isValid($alias)) {
+            if ($this->nicknameExists($nickname)) {
                 // TRANS: Group create form validation error.
-                // TRANS: %s is the invalid alias.
-                $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                $this->showForm(_('Nickname already in use. Try another one.'));
                 return;
-            }
-            if ($this->nicknameExists($alias)) {
-                // TRANS: Group create form validation error. %s is the already used alias.
-                $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
-                                        $alias));
+            } else if (!User_group::allowedNickname($nickname)) {
+                // TRANS: Group create form validation error.
+                $this->showForm(_('Not a valid nickname.'));
+                return;
+            } else if (!is_null($homepage) && (strlen($homepage) > 0) &&
+                       !Validate::uri($homepage,
+                                      array('allowed_schemes' =>
+                                            array('http', 'https')))) {
+                // TRANS: Group create form validation error.
+                $this->showForm(_('Homepage is not a valid URL.'));
+                return;
+            } else if (!is_null($fullname) && mb_strlen($fullname) > 255) {
+                // TRANS: Group create form validation error.
+                $this->showForm(_('Full name is too long (maximum 255 characters).'));
+                return;
+            } else if (User_group::descriptionTooLong($description)) {
+                // TRANS: Group create form validation error.
+                // TRANS: %d is the maximum number of allowed characters.
+                $this->showForm(sprintf(_m('Description is too long (maximum %d character).',
+                                           'Description is too long (maximum %d characters).',
+                                           User_group::maxDescription()),
+                                        User_group::maxDescription()));
+                return;
+            } else if (!is_null($location) && mb_strlen($location) > 255) {
+                // TRANS: Group create form validation error.
+                $this->showForm(_('Location is too long (maximum 255 characters).'));
                 return;
             }
-            // XXX assumes alphanum nicknames
-            if (strcmp($alias, $nickname) == 0) {
+
+            if (!empty($aliasstring)) {
+                $aliases = array_map('common_canonical_nickname', array_unique(preg_split('/[\s,]+/', $aliasstring)));
+            } else {
+                $aliases = array();
+            }
+
+            if (count($aliases) > common_config('group', 'maxaliases')) {
                 // TRANS: Group create form validation error.
-                $this->showForm(_('Alias cannot be the same as nickname.'));
+                // TRANS: %d is the maximum number of allowed aliases.
+                $this->showForm(sprintf(_m('Too many aliases! Maximum %d allowed.',
+                                           'Too many aliases! Maximum %d allowed.',
+                                           common_config('group', 'maxaliases')),
+                                        common_config('group', 'maxaliases')));
                 return;
             }
-        }
 
-        $cur = common_current_user();
+            foreach ($aliases as $alias) {
+                if (!Nickname::isValid($alias)) {
+                    // TRANS: Group create form validation error.
+                    // TRANS: %s is the invalid alias.
+                    $this->showForm(sprintf(_('Invalid alias: "%s"'), $alias));
+                    return;
+                }
+                if ($this->nicknameExists($alias)) {
+                    // TRANS: Group create form validation error. %s is the already used alias.
+                    $this->showForm(sprintf(_('Alias "%s" already in use. Try another one.'),
+                                            $alias));
+                    return;
+                }
+                // XXX assumes alphanum nicknames
+                if (strcmp($alias, $nickname) == 0) {
+                    // TRANS: Group create form validation error.
+                    $this->showForm(_('Alias cannot be the same as nickname.'));
+                    return;
+                }
+            }
+
+            $cur = common_current_user();
 
-        // Checked in prepare() above
+            // Checked in prepare() above
 
-        assert(!is_null($cur));
+            assert(!is_null($cur));
 
-        $group = User_group::register(array('nickname' => $nickname,
-                                            'fullname' => $fullname,
-                                            'homepage' => $homepage,
-                                            'description' => $description,
-                                            'location' => $location,
-                                            'aliases'  => $aliases,
-                                            'userid'   => $cur->id,
-                                            'local'    => true));
+            $group = User_group::register(array('nickname' => $nickname,
+                                                'fullname' => $fullname,
+                                                'homepage' => $homepage,
+                                                'description' => $description,
+                                                'location' => $location,
+                                                'aliases'  => $aliases,
+                                                'userid'   => $cur->id,
+                                                'local'    => true));
 
-        common_redirect($group->homeUrl(), 303);
+            $this->group = $group;
+
+            Event::handle('EndGroupSaveForm', array($this));
+
+            common_redirect($group->homeUrl(), 303);
+        }
     }
 
     function nicknameExists($nickname)
index b81d4b9d0d8d1fac1e5d27b240147b77f9155c82..cad19bba2469b52d8ec3e965412675f5cb53db0a 100644 (file)
@@ -88,21 +88,9 @@ class OutboxAction extends MailboxAction
         }
     }
 
-    /**
-     * returns the profile we want to show with the message
-     *
-     * For outboxes, we show the recipient.
-     *
-     * @param Message $message The message to get the profile for
-     *
-     * @return Profile The profile of the message recipient
-     *
-     * @see MailboxAction::getMessageProfile()
-     */
-
-    function getMessageProfile($message)
+    function getMessageList($message)
     {
-        return $message->getTo();
+        return new OutboxMessageList($this, $message);
     }
 
     /**
@@ -116,3 +104,24 @@ class OutboxAction extends MailboxAction
         return _('This is your outbox, which lists private messages you have sent.');
     }
 }
+
+class OutboxMessageList extends MessageList
+{
+    function newItem($message)
+    {
+        return new OutboxMessageListItem($this->out, $message);
+    }
+}
+
+class OutboxMessageListItem extends MessageListItem
+{
+    /**
+     * Returns the profile we want to show with the message
+     *
+     * @return Profile The profile that matches the message
+     */
+    function getMessageProfile()
+    {
+        return $this->message->getTo();
+    }
+}
\ No newline at end of file
index f38cd420ac7b0a74d25b4332612f6e4167705197..2806944452288c95c4f03a3780f54876b1574328 100644 (file)
@@ -181,6 +181,7 @@ class ShowgroupAction extends GroupDesignAction
     function showContent()
     {
         $this->showGroupProfile();
+        $this->showGroupActions();
         $this->showGroupNotices();
     }
 
@@ -216,112 +217,123 @@ class ShowgroupAction extends GroupDesignAction
         $this->elementStart('div', array('id' => 'i',
                                          'class' => 'entity_profile vcard author'));
 
-        // TRANS: Group profile header (h2). Text hidden by default.
-        $this->element('h2', null, _('Group profile'));
+        if (Event::handle('StartGroupProfileElements', array($this, $this->group))) {
 
-        $this->elementStart('dl', 'entity_depiction');
-        // TRANS: Label for group avatar (dt). Text hidden by default.
-        $this->element('dt', null, _('Avatar'));
-        $this->elementStart('dd');
+            // TRANS: Group profile header (h2). Text hidden by default.
+            $this->element('h2', null, _('Group profile'));
 
-        $logo = ($this->group->homepage_logo) ?
-          $this->group->homepage_logo : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
-
-        $this->element('img', array('src' => $logo,
-                                    'class' => 'photo avatar',
-                                    'width' => AVATAR_PROFILE_SIZE,
-                                    'height' => AVATAR_PROFILE_SIZE,
-                                    'alt' => $this->group->nickname));
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
+            $this->elementStart('dl', 'entity_depiction');
+            // TRANS: Label for group avatar (dt). Text hidden by default.
+            $this->element('dt', null, _('Avatar'));
+            $this->elementStart('dd');
 
-        $this->elementStart('dl', 'entity_nickname');
-        // TRANS: Label for group nickname (dt). Text hidden by default.
-        $this->element('dt', null, _('Nickname'));
-        $this->elementStart('dd');
-        $hasFN = ($this->group->fullname) ? 'nickname url uid' : 'fn org nickname url uid';
-        $this->element('a', array('href' => $this->group->homeUrl(),
-                                  'rel' => 'me', 'class' => $hasFN),
-                            $this->group->nickname);
-        $this->elementEnd('dd');
-        $this->elementEnd('dl');
+            $logo = ($this->group->homepage_logo) ?
+                $this->group->homepage_logo : User_group::defaultLogo(AVATAR_PROFILE_SIZE);
 
-        if ($this->group->fullname) {
-            $this->elementStart('dl', 'entity_fn');
-            // TRANS: Label for full group name (dt). Text hidden by default.
-            $this->element('dt', null, _('Full name'));
-            $this->elementStart('dd');
-            $this->element('span', 'fn org', $this->group->fullname);
+            $this->element('img', array('src' => $logo,
+                                        'class' => 'photo avatar',
+                                        'width' => AVATAR_PROFILE_SIZE,
+                                        'height' => AVATAR_PROFILE_SIZE,
+                                        'alt' => $this->group->nickname));
             $this->elementEnd('dd');
             $this->elementEnd('dl');
-        }
-
-        if ($this->group->location) {
-            $this->elementStart('dl', 'entity_location');
-            // TRANS: Label for group location (dt). Text hidden by default.
-            $this->element('dt', null, _('Location'));
-            $this->element('dd', 'label', $this->group->location);
-            $this->elementEnd('dl');
-        }
 
-        if ($this->group->homepage) {
-            $this->elementStart('dl', 'entity_url');
-            // TRANS: Label for group URL (dt). Text hidden by default.
-            $this->element('dt', null, _('URL'));
+            $this->elementStart('dl', 'entity_nickname');
+            // TRANS: Label for group nickname (dt). Text hidden by default.
+            $this->element('dt', null, _('Nickname'));
             $this->elementStart('dd');
-            $this->element('a', array('href' => $this->group->homepage,
-                                      'rel' => 'me', 'class' => 'url'),
-                           $this->group->homepage);
+            $hasFN = ($this->group->fullname) ? 'nickname url uid' : 'fn org nickname url uid';
+            $this->element('a', array('href' => $this->group->homeUrl(),
+                                      'rel' => 'me', 'class' => $hasFN),
+                           $this->group->nickname);
             $this->elementEnd('dd');
             $this->elementEnd('dl');
-        }
 
-        if ($this->group->description) {
-            $this->elementStart('dl', 'entity_note');
-            // TRANS: Label for group description or group note (dt). Text hidden by default.
-            $this->element('dt', null, _('Note'));
-            $this->element('dd', 'note', $this->group->description);
-            $this->elementEnd('dl');
-        }
+            if ($this->group->fullname) {
+                $this->elementStart('dl', 'entity_fn');
+                // TRANS: Label for full group name (dt). Text hidden by default.
+                $this->element('dt', null, _('Full name'));
+                $this->elementStart('dd');
+                $this->element('span', 'fn org', $this->group->fullname);
+                $this->elementEnd('dd');
+                $this->elementEnd('dl');
+            }
 
-        if (common_config('group', 'maxaliases') > 0) {
-            $aliases = $this->group->getAliases();
+            if ($this->group->location) {
+                $this->elementStart('dl', 'entity_location');
+                // TRANS: Label for group location (dt). Text hidden by default.
+                $this->element('dt', null, _('Location'));
+                $this->element('dd', 'label', $this->group->location);
+                $this->elementEnd('dl');
+            }
 
-            if (!empty($aliases)) {
-                $this->elementStart('dl', 'entity_aliases');
-                // TRANS: Label for group aliases (dt). Text hidden by default.
-                $this->element('dt', null, _('Aliases'));
-                $this->element('dd', 'aliases', implode(' ', $aliases));
+            if ($this->group->homepage) {
+                $this->elementStart('dl', 'entity_url');
+                // TRANS: Label for group URL (dt). Text hidden by default.
+                $this->element('dt', null, _('URL'));
+                $this->elementStart('dd');
+                $this->element('a', array('href' => $this->group->homepage,
+                                          'rel' => 'me', 'class' => 'url'),
+                               $this->group->homepage);
+                $this->elementEnd('dd');
                 $this->elementEnd('dl');
             }
+
+            if ($this->group->description) {
+                $this->elementStart('dl', 'entity_note');
+                // TRANS: Label for group description or group note (dt). Text hidden by default.
+                $this->element('dt', null, _('Note'));
+                $this->element('dd', 'note', $this->group->description);
+                $this->elementEnd('dl');
+            }
+
+            if (common_config('group', 'maxaliases') > 0) {
+                $aliases = $this->group->getAliases();
+
+                if (!empty($aliases)) {
+                    $this->elementStart('dl', 'entity_aliases');
+                    // TRANS: Label for group aliases (dt). Text hidden by default.
+                    $this->element('dt', null, _('Aliases'));
+                    $this->element('dd', 'aliases', implode(' ', $aliases));
+                    $this->elementEnd('dl');
+                }
+            }
+
+            Event::handle('EndGroupProfileElements', array($this, $this->group));
         }
 
         $this->elementEnd('div');
+    }
 
+    function showGroupActions()
+    {
         $cur = common_current_user();
         $this->elementStart('div', 'entity_actions');
         // TRANS: Group actions header (h2). Text hidden by default.
         $this->element('h2', null, _('Group actions'));
         $this->elementStart('ul');
-        $this->elementStart('li', 'entity_subscribe');
-        if (Event::handle('StartGroupSubscribe', array($this, $this->group))) {
-            if ($cur) {
-                if ($cur->isMember($this->group)) {
-                    $lf = new LeaveForm($this, $this->group);
-                    $lf->show();
-                } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) {
-                    $jf = new JoinForm($this, $this->group);
-                    $jf->show();
+        if (Event::handle('StartGroupActionsList', array($this, $this->group))) {
+            $this->elementStart('li', 'entity_subscribe');
+            if (Event::handle('StartGroupSubscribe', array($this, $this->group))) {
+                if ($cur) {
+                    if ($cur->isMember($this->group)) {
+                        $lf = new LeaveForm($this, $this->group);
+                        $lf->show();
+                    } else if (!Group_block::isBlocked($this->group, $cur->getProfile())) {
+                        $jf = new JoinForm($this, $this->group);
+                        $jf->show();
+                    }
                 }
+                Event::handle('EndGroupSubscribe', array($this, $this->group));
             }
-            Event::handle('EndGroupSubscribe', array($this, $this->group));
-        }
-        $this->elementEnd('li');
-        if ($cur && $cur->hasRight(Right::DELETEGROUP)) {
-            $this->elementStart('li', 'entity_delete');
-            $df = new DeleteGroupForm($this, $this->group);
-            $df->show();
             $this->elementEnd('li');
+            if ($cur && $cur->hasRight(Right::DELETEGROUP)) {
+                $this->elementStart('li', 'entity_delete');
+                $df = new DeleteGroupForm($this, $this->group);
+                $df->show();
+                $this->elementEnd('li');
+            }
+            Event::handle('EndGroupActionsList', array($this, $this->group));
         }
         $this->elementEnd('ul');
         $this->elementEnd('div');
index d737f85d3a0b2ba3b4bb0531f5344f023c5eb4ed..1c867af1190ccbff3ff58d88a8cb4da00c91a7b9 100644 (file)
@@ -30,20 +30,17 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-require_once INSTALLDIR.'/lib/mailbox.php';
-
 /**
  * Show a single message
  *
- * // XXX: It is totally weird how this works!
- *
  * @category Personal
  * @package  StatusNet
  * @author   Evan Prodromou <evan@status.net>
  * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
  * @link     http://status.net/
  */
-class ShowmessageAction extends MailboxAction
+
+class ShowmessageAction extends Action
 {
     /**
      * Message object to show
@@ -82,22 +79,20 @@ class ShowmessageAction extends MailboxAction
 
         $this->user = common_current_user();
 
+        if (empty($this->user) ||
+            ($this->user->id != $this->message->from_profile &&
+             $this->user->id != $this->message->to_profile)) {
+            // TRANS: Client error displayed requesting a single direct message the requesting user was not a party in.
+            throw new ClientException(_('Only the sender and recipient ' .
+                                        'may read this message.'), 403);
+        }
+
         return true;
     }
 
     function handle($args)
     {
-        Action::handle($args);
-
-        if ($this->user && ($this->user->id == $this->message->from_profile ||
-            $this->user->id == $this->message->to_profile)) {
-                $this->showPage();
-        } else {
-            // TRANS: Client error displayed requesting a single direct message the requesting user was not a party in.
-            $this->clientError(_('Only the sender and recipient ' .
-                'may read this message.'), 403);
-            return;
-        }
+        $this->showPage();
     }
 
     function title()
@@ -121,42 +116,18 @@ class ShowmessageAction extends MailboxAction
         }
     }
 
-    function getMessages()
-    {
-        $message     = new Message();
-        $message->id = $this->message->id;
-        $message->find();
-        return $message;
-    }
 
-    function getMessageProfile()
+    function showContent()
     {
-        if ($this->user->id == $this->message->from_profile) {
-            return $this->message->getTo();
-        } else if ($this->user->id == $this->message->to_profile) {
-            return $this->message->getFrom();
-        } else {
-            // This shouldn't happen
-            return null;
-        }
+        $this->elementStart('ul', 'notices messages');
+        $ml = new ShowMessageListItem($this, $this->message, $this->user);
+        $ml->show();
+        $this->elementEnd('ul');
     }
 
-    /**
-     * Don't show local navigation
-     *
-     * @return void
-     */
-    function showLocalNavBlock()
-    {
-    }
-
-    /**
-     * Don't show page notice
-     *
-     * @return void
-     */
-    function showPageNoticeBlock()
+    function isReadOnly($args)
     {
+        return true;
     }
 
     /**
@@ -164,22 +135,30 @@ class ShowmessageAction extends MailboxAction
      *
      * @return void
      */
-    function showAside()
-    {
+
+    function showAside() {
     }
+}
 
-    /**
-     * Don't show any instructions
-     *
-     * @return string
-     */
-    function getInstructions()
+class ShowMessageListItem extends MessageListItem
+{
+    var $user;
+
+    function __construct($out, $message, $user)
     {
-        return '';
+        parent::__construct($out, $message);
+        $this->user = $user;
     }
 
-    function isReadOnly($args)
+    function getMessageProfile()
     {
-        return true;
+        if ($this->user->id == $this->message->from_profile) {
+            return $this->message->getTo();
+        } else if ($this->user->id == $this->message->to_profile) {
+            return $this->message->getFrom();
+        } else {
+            // This shouldn't happen
+            return null;
+        }
     }
 }
index e9ea479e14c4d0e06b5fa4bf5ecbd031bd462916..4522d1fc3889b4648afb6e8434afddfd0fbe8bd8 100644 (file)
@@ -446,7 +446,10 @@ class Notice extends Memcached_DataObject
     function blowOnInsert($conversation = false)
     {
         self::blow('profile:notice_ids:%d', $this->profile_id);
-        self::blow('public');
+
+        if ($this->isPublic()) {
+            self::blow('public');
+        }
 
         // XXX: Before we were blowing the casche only if the notice id
         // was not the root of the conversation.  What to do now?
@@ -481,7 +484,10 @@ class Notice extends Memcached_DataObject
         $this->blowOnInsert();
 
         self::blow('profile:notice_ids:%d;last', $this->profile_id);
-        self::blow('public;last');
+
+        if ($this->isPublic()) {
+            self::blow('public;last');
+        }
     }
 
     /** save all urls in the notice to the db
@@ -958,7 +964,7 @@ class Notice extends Memcached_DataObject
         $groups = array();
 
         /* extract all !group */
-        $count = preg_match_all('/(?:^|\s)!([A-Za-z0-9]{1,64})/',
+        $count = preg_match_all('/(?:^|\s)!(' . Nickname::DISPLAY_FMT . ')/',
                                 strtolower($this->content),
                                 $match);
         if (!$count) {
@@ -2107,4 +2113,14 @@ class Notice extends Memcached_DataObject
             $obj->whereAdd($max);
         }
     }
+
+    function isPublic()
+    {
+        if (common_config('public', 'localonly')) {
+            return ($this->is_local == Notice::LOCAL_PUBLIC);
+        } else {
+            return (($this->is_local != Notice::LOCAL_NONPUBLIC) &&
+                    ($this->is_local != Notice::GATEWAY));
+        }
+    }
 }
index d402ed47733e3b631bca94c153bac8a28ed1dbbf..5a9991fe9e3fd936221b64ef69b5d7f1362e2994 100644 (file)
@@ -512,64 +512,70 @@ class User_group extends Memcached_DataObject
         $group->mainpage    = $mainpage;
         $group->created     = common_sql_now();
 
-        $result = $group->insert();
+        if (Event::handle('StartGroupSave', array(&$group))) {
 
-        if (!$result) {
-            common_log_db_error($group, 'INSERT', __FILE__);
-            // TRANS: Server exception thrown when creating a group failed.
-            throw new ServerException(_('Could not create group.'));
-        }
+            $result = $group->insert();
 
-        if (!isset($uri) || empty($uri)) {
-            $orig = clone($group);
-            $group->uri = common_local_url('groupbyid', array('id' => $group->id));
-            $result = $group->update($orig);
             if (!$result) {
-                common_log_db_error($group, 'UPDATE', __FILE__);
-                // TRANS: Server exception thrown when updating a group URI failed.
-                throw new ServerException(_('Could not set group URI.'));
+                common_log_db_error($group, 'INSERT', __FILE__);
+                // TRANS: Server exception thrown when creating a group failed.
+                throw new ServerException(_('Could not create group.'));
             }
-        }
 
-        $result = $group->setAliases($aliases);
+            if (!isset($uri) || empty($uri)) {
+                $orig = clone($group);
+                $group->uri = common_local_url('groupbyid', array('id' => $group->id));
+                $result = $group->update($orig);
+                if (!$result) {
+                    common_log_db_error($group, 'UPDATE', __FILE__);
+                    // TRANS: Server exception thrown when updating a group URI failed.
+                    throw new ServerException(_('Could not set group URI.'));
+                }
+            }
 
-        if (!$result) {
-            // TRANS: Server exception thrown when creating group aliases failed.
-            throw new ServerException(_('Could not create aliases.'));
-        }
+            $result = $group->setAliases($aliases);
 
-        $member = new Group_member();
+            if (!$result) {
+                // TRANS: Server exception thrown when creating group aliases failed.
+                throw new ServerException(_('Could not create aliases.'));
+            }
 
-        $member->group_id   = $group->id;
-        $member->profile_id = $userid;
-        $member->is_admin   = 1;
-        $member->created    = $group->created;
+            $member = new Group_member();
 
-        $result = $member->insert();
+            $member->group_id   = $group->id;
+            $member->profile_id = $userid;
+            $member->is_admin   = 1;
+            $member->created    = $group->created;
 
-        if (!$result) {
-            common_log_db_error($member, 'INSERT', __FILE__);
-            // TRANS: Server exception thrown when setting group membership failed.
-            throw new ServerException(_('Could not set group membership.'));
-        }
+            $result = $member->insert();
 
-        if ($local) {
-            $local_group = new Local_group();
+            if (!$result) {
+                common_log_db_error($member, 'INSERT', __FILE__);
+                // TRANS: Server exception thrown when setting group membership failed.
+                throw new ServerException(_('Could not set group membership.'));
+            }
 
-            $local_group->group_id = $group->id;
-            $local_group->nickname = $nickname;
-            $local_group->created  = common_sql_now();
+            if ($local) {
+                $local_group = new Local_group();
 
-            $result = $local_group->insert();
+                $local_group->group_id = $group->id;
+                $local_group->nickname = $nickname;
+                $local_group->created  = common_sql_now();
 
-            if (!$result) {
-                common_log_db_error($local_group, 'INSERT', __FILE__);
-                // TRANS: Server exception thrown when saving local group information failed.
-                throw new ServerException(_('Could not save local group info.'));
+                $result = $local_group->insert();
+
+                if (!$result) {
+                    common_log_db_error($local_group, 'INSERT', __FILE__);
+                    // TRANS: Server exception thrown when saving local group information failed.
+                    throw new ServerException(_('Could not save local group info.'));
+                }
             }
+
+            $group->query('COMMIT');
+
+            Event::handle('EndGroupSave', array($group));
         }
 
-        $group->query('COMMIT');
         return $group;
     }
 
index b6e8f87b6de0337b71feafce6f44291d5b3ee620..5d70425720bfb21f2e9eb308607cd3d62b8614d5 100644 (file)
@@ -263,8 +263,7 @@ class ApiAction extends Action
             ? Design::url($design->backgroundimage) : '';
 
         $twitter_user['profile_background_tile']
-            = empty($design->disposition)
-            ? '' : ($design->disposition & BACKGROUND_TILE) ? 'true' : 'false';
+            = (bool)($design->disposition & BACKGROUND_TILE);
 
         $twitter_user['statuses_count'] = $profile->noticeCount();
 
index c288c2e5f0ec66d5d781b067417bbed6931eae3f..f2caf48bdb47d5ff9cf01714429c3002bdb17234 100644 (file)
@@ -25,252 +25,287 @@ class CommandInterpreter
 {
     function handle_command($user, $text)
     {
-        # XXX: localise
+        // XXX: localise
 
         $text = preg_replace('/\s+/', ' ', trim($text));
         list($cmd, $arg) = $this->split_arg($text);
 
-        # We try to support all the same commands as Twitter, see
-        # http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
-        # There are a few compatibility commands from earlier versions of
-        # StatusNet
+        // We try to support all the same commands as Twitter, see
+        // http://getsatisfaction.com/twitter/topics/what_are_the_twitter_commands
+        // There are a few compatibility commands from earlier versions of
+        // StatusNet
 
-        switch(strtolower($cmd)) {
-         case 'help':
-            if ($arg) {
-                return null;
-            }
-            return new HelpCommand($user);
-         case 'login':
-            if ($arg) {
-                return null;
-            } else {
-                return new LoginCommand($user);
-            }
-         case 'lose':
-            if ($arg) {
+        $cmd = strtolower($cmd);
+
+        if (Event::handle('StartIntepretCommand', array($cmd, $arg, $user, &$result))) {
+            switch($cmd) {
+            case 'help':
+                if ($arg) {
+                    $result = null;
+                }
+                $result = new HelpCommand($user);
+                break;
+            case 'login':
+                if ($arg) {
+                    $result = null;
+                } else {
+                    $result = new LoginCommand($user);
+                }
+                break;
+            case 'lose':
+                if ($arg) {
+                    list($other, $extra) = $this->split_arg($arg);
+                    if ($extra) {
+                        $result = null;
+                    } else {
+                        $result = new LoseCommand($user, $other);
+                    }
+                } else {
+                    $result = null;
+                }
+                break;
+            case 'subscribers':
+                if ($arg) {
+                    $result = null;
+                } else {
+                    $result = new SubscribersCommand($user);
+                }
+                break;
+            case 'subscriptions':
+                if ($arg) {
+                    $result = null;
+                } else {
+                    $result = new SubscriptionsCommand($user);
+                }
+                break;
+            case 'groups':
+                if ($arg) {
+                    $result = null;
+                } else {
+                    $result = new GroupsCommand($user);
+                }
+                break;
+            case 'on':
+                if ($arg) {
+                    list($other, $extra) = $this->split_arg($arg);
+                    if ($extra) {
+                        $result = null;
+                    } else {
+                        $result = new OnCommand($user, $other);
+                    }
+                } else {
+                    $result = new OnCommand($user);
+                }
+                break;
+            case 'off':
+                if ($arg) {
+                    list($other, $extra) = $this->split_arg($arg);
+                    if ($extra) {
+                        $result = null;
+                    } else {
+                        $result = new OffCommand($user, $other);
+                    }
+                } else {
+                    $result = new OffCommand($user);
+                }
+                break;
+            case 'stop':
+            case 'quit':
+                if ($arg) {
+                    $result = null;
+                } else {
+                    $result = new OffCommand($user);
+                }
+                break;
+            case 'join':
+                if (!$arg) {
+                    $result = null;
+                }
                 list($other, $extra) = $this->split_arg($arg);
                 if ($extra) {
-                    return null;
+                    $result = null;
                 } else {
-                    return new LoseCommand($user, $other);
+                    $result = new JoinCommand($user, $other);
+                }
+                break;
+            case 'drop':
+                if (!$arg) {
+                    $result = null;
                 }
-            } else {
-              return null;
-            }
-         case 'subscribers':
-            if ($arg) {
-                return null;
-            } else {
-                return new SubscribersCommand($user);
-            }
-         case 'subscriptions':
-            if ($arg) {
-                return null;
-            } else {
-                return new SubscriptionsCommand($user);
-            }
-         case 'groups':
-            if ($arg) {
-                return null;
-            } else {
-                return new GroupsCommand($user);
-            }
-         case 'on':
-            if ($arg) {
                 list($other, $extra) = $this->split_arg($arg);
                 if ($extra) {
-                    return null;
+                    $result = null;
                 } else {
-                    return new OnCommand($user, $other);
+                    $result = new DropCommand($user, $other);
                 }
-            } else {
-                return new OnCommand($user);
-            }
-         case 'off':
-            if ($arg) {
+                break;
+            case 'follow':
+            case 'sub':
+                if (!$arg) {
+                    $result = null;
+                }
+
                 list($other, $extra) = $this->split_arg($arg);
                 if ($extra) {
-                    return null;
+                    $result = null;
                 } else {
-                    return new OffCommand($user, $other);
+                    $result = new SubCommand($user, $other);
                 }
-            } else {
-                return new OffCommand($user);
-            }
-         case 'stop':
-         case 'quit':
-            if ($arg) {
-                return null;
-            } else {
-                return new OffCommand($user);
-            }
-         case 'join':
-             if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new JoinCommand($user, $other);
-            }
-         case 'drop':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new DropCommand($user, $other);
-            }
-         case 'follow':
-         case 'sub':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new SubCommand($user, $other);
-            }
-         case 'leave':
-         case 'unsub':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new UnsubCommand($user, $other);
-            }
-         case 'get':
-         case 'last':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new GetCommand($user, $other);
-            }
-         case 'd':
-         case 'dm':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if (!$extra) {
-                return null;
-            } else {
-                return new MessageCommand($user, $other, $extra);
-            }
-         case 'r':
-         case 'reply':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if (!$extra) {
-                return null;
-            } else {
-                return new ReplyCommand($user, $other, $extra);
-            }
-         case 'repeat':
-         case 'rp':
-         case 'rt':
-         case 'rd':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new RepeatCommand($user, $other);
-            }
-         case 'whois':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new WhoisCommand($user, $other);
-            }
-         case 'fav':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new FavCommand($user, $other);
-            }
-         case 'nudge':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new NudgeCommand($user, $other);
-            }
-         case 'stats':
-            if ($arg) {
-                return null;
-            }
-            return new StatsCommand($user);
-         case 'invite':
-            if (!$arg) {
-                return null;
-            }
-            list($other, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else {
-                return new InviteCommand($user, $other);
-            }
-         case 'track':
-            if (!$arg) {
-                return null;
-            }
-            list($word, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else if ($word == 'off') {
-                return new TrackOffCommand($user);
-            } else {
-                return new TrackCommand($user, $word);
-            }
-         case 'untrack':
-            if (!$arg) {
-                return null;
-            }
-            list($word, $extra) = $this->split_arg($arg);
-            if ($extra) {
-                return null;
-            } else if ($word == 'all') {
-                return new TrackOffCommand($user);
-            } else {
-                return new UntrackCommand($user, $word);
-            }
-         case 'tracks':
-         case 'tracking':
-            if ($arg) {
-                return null;
+                break;
+            case 'leave':
+            case 'unsub':
+                if (!$arg) {
+                    $result = null;
+                }
+
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new UnsubCommand($user, $other);
+                }
+                break;
+            case 'get':
+            case 'last':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new GetCommand($user, $other);
+                }
+                break;
+            case 'd':
+            case 'dm':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if (!$extra) {
+                    $result = null;
+                } else {
+                    $result = new MessageCommand($user, $other, $extra);
+                }
+                break;
+            case 'r':
+            case 'reply':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if (!$extra) {
+                    $result = null;
+                } else {
+                    $result = new ReplyCommand($user, $other, $extra);
+                }
+                break;
+            case 'repeat':
+            case 'rp':
+            case 'rt':
+            case 'rd':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new RepeatCommand($user, $other);
+                }
+                break;
+            case 'whois':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new WhoisCommand($user, $other);
+                }
+                break;
+            case 'fav':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new FavCommand($user, $other);
+                }
+                break;
+            case 'nudge':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new NudgeCommand($user, $other);
+                }
+                break;
+            case 'stats':
+                if ($arg) {
+                    $result = null;
+                }
+                $result = new StatsCommand($user);
+                break;
+            case 'invite':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($other, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else {
+                    $result = new InviteCommand($user, $other);
+                }
+                break;
+            case 'track':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($word, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else if ($word == 'off') {
+                    $result = new TrackOffCommand($user);
+                } else {
+                    $result = new TrackCommand($user, $word);
+                }
+                break;
+            case 'untrack':
+                if (!$arg) {
+                    $result = null;
+                }
+                list($word, $extra) = $this->split_arg($arg);
+                if ($extra) {
+                    $result = null;
+                } else if ($word == 'all') {
+                    $result = new TrackOffCommand($user);
+                } else {
+                    $result = new UntrackCommand($user, $word);
+                }
+                break;
+            case 'tracks':
+            case 'tracking':
+                if ($arg) {
+                    $result = null;
+                }
+                $result = new TrackingCommand($user);
+                break;
+            default:
+                $result = false;
             }
-            return new TrackingCommand($user);
-         default:
-            return false;
+                
+            Event::handle('EndInterpretCommand', array($cmd, $arg, $user, $result));
         }
+
+        return $result;
     }
 
     /**
index 08779fb6fd82cd0141f8c5f10d93fe14078ef42a..a5cfb6f8f70b178fa02a34d241c1ae08a226bd61 100644 (file)
@@ -23,7 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); }
 if (isset($_REQUEST['p']) && $_REQUEST['p'] == 'check-fancy') {  exit; }
 
 define('STATUSNET_BASE_VERSION', '0.9.7');
-define('STATUSNET_LIFECYCLE', 'alpha1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('STATUSNET_LIFECYCLE', 'beta1'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
 define('STATUSNET_VERSION', STATUSNET_BASE_VERSION . STATUSNET_LIFECYCLE);
 
 define('LACONICA_VERSION', STATUSNET_VERSION); // compatibility
@@ -36,6 +36,7 @@ define('AVATAR_MINI_SIZE', 24);
 
 define('NOTICES_PER_PAGE', 20);
 define('PROFILES_PER_PAGE', 20);
+define('MESSAGES_PER_PAGE', 20);
 
 define('FOREIGN_NOTICE_SEND', 1);
 define('FOREIGN_NOTICE_RECV', 2);
index 8a111b46429c4d5cbbefb1daed71a738ee21e70f..3a2cf6bf4ab244eb28962a30566916a625768524 100644 (file)
@@ -139,51 +139,54 @@ class GroupEditForm extends Form
         }
 
         $this->out->elementStart('ul', 'form_data');
-        $this->out->elementStart('li');
-        $this->out->hidden('groupid', $id);
-        $this->out->input('nickname', _('Nickname'),
-                          ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
-                          _('1-64 lowercase letters or numbers, no punctuation or spaces.'));
-        $this->out->elementEnd('li');
-        $this->out->elementStart('li');
-        $this->out->input('fullname', _('Full name'),
-                          ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
-        $this->out->elementEnd('li');
-        $this->out->elementStart('li');
-        $this->out->input('homepage', _('Homepage'),
-                          ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
-                          _('URL of the homepage or blog of the group or topic.'));
-        $this->out->elementEnd('li');
-        $this->out->elementStart('li');
-        $desclimit = User_group::maxDescription();
-        if ($desclimit == 0) {
-            $descinstr = _('Describe the group or topic');
-        } else {
-            $descinstr = sprintf(_m('Describe the group or topic in %d character or less.',
-                                    'Describe the group or topic in %d characters or less.',
-                                    $desclimit),
-                                 $desclimit);
-        }
-        $this->out->textarea('description', _('Description'),
-                             ($this->out->arg('description')) ? $this->out->arg('description') : $description,
-                             $descinstr);
-        $this->out->elementEnd('li');
-        $this->out->elementStart('li');
-        $this->out->input('location', _('Location'),
-                          ($this->out->arg('location')) ? $this->out->arg('location') : $location,
-                          _('Location for the group, if any, like "City, State (or Region), Country".'));
-        $this->out->elementEnd('li');
-        if (common_config('group', 'maxaliases') > 0) {
-            $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
+        if (Event::handle('StartGroupEditFormData', array($this))) {
+            $this->out->elementStart('li');
+            $this->out->hidden('groupid', $id);
+            $this->out->input('nickname', _('Nickname'),
+                              ($this->out->arg('nickname')) ? $this->out->arg('nickname') : $nickname,
+                              _('1-64 lowercase letters or numbers, no punctuation or spaces'));
+            $this->out->elementEnd('li');
+            $this->out->elementStart('li');
+            $this->out->input('fullname', _('Full name'),
+                              ($this->out->arg('fullname')) ? $this->out->arg('fullname') : $fullname);
+            $this->out->elementEnd('li');
+            $this->out->elementStart('li');
+            $this->out->input('homepage', _('Homepage'),
+                              ($this->out->arg('homepage')) ? $this->out->arg('homepage') : $homepage,
+                              _('URL of the homepage or blog of the group or topic.'));
+            $this->out->elementEnd('li');
+            $this->out->elementStart('li');
+            $desclimit = User_group::maxDescription();
+            if ($desclimit == 0) {
+                $descinstr = _('Describe the group or topic');
+            } else {
+                $descinstr = sprintf(_m('Describe the group or topic in %d character or less',
+                                        'Describe the group or topic in %d characters or less',
+                                        $desclimit),
+                                     $desclimit);
+            }
+            $this->out->textarea('description', _('Description'),
+                                 ($this->out->arg('description')) ? $this->out->arg('description') : $description,
+                                 $descinstr);
+            $this->out->elementEnd('li');
             $this->out->elementStart('li');
-            $this->out->input('aliases', _('Aliases'),
-                              ($this->out->arg('aliases')) ? $this->out->arg('aliases') :
-                              (!empty($aliases)) ? implode(' ', $aliases) : '',
-                              sprintf(_m('Extra nicknames for the group, separated with commas or spaces. Maximum %d alias allowed.',
-                                         'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.',
-                                         common_config('group', 'maxaliases')),
-                                      common_config('group', 'maxaliases')));;
+            $this->out->input('location', _('Location'),
+                              ($this->out->arg('location')) ? $this->out->arg('location') : $location,
+                              _('Location for the group, if any, like "City, State (or Region), Country".'));
             $this->out->elementEnd('li');
+            if (common_config('group', 'maxaliases') > 0) {
+                $aliases = (empty($this->group)) ? array() : $this->group->getAliases();
+                $this->out->elementStart('li');
+                $this->out->input('aliases', _('Aliases'),
+                                  ($this->out->arg('aliases')) ? $this->out->arg('aliases') :
+                                  (!empty($aliases)) ? implode(' ', $aliases) : '',
+                                  sprintf(_m('Extra nicknames for the group, separated with commas or spaces. Maximum %d alias allowed.',
+                                             'Extra nicknames for the group, separated with commas or spaces. Maximum %d aliases allowed.',
+                                             common_config('group', 'maxaliases')),
+                                          common_config('group', 'maxaliases')));;
+                $this->out->elementEnd('li');
+            }
+            Event::handle('EndGroupEditFormData', array($this));
         }
         $this->out->elementEnd('ul');
     }
index 2b00f5ffde0538f908fd7af15d4cf7d22c0697fc..7faeb7dba3b0365d0ba856e10e5285c29b52a106 100644 (file)
@@ -31,8 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
     exit(1);
 }
 
-define('MESSAGES_PER_PAGE', 20);
-
 /**
  * common superclass for direct messages inbox and outbox
  *
@@ -111,32 +109,22 @@ class MailboxAction extends CurrentUserDesignAction
         $message = $this->getMessages();
 
         if ($message) {
-            $cnt = 0;
-            $this->elementStart('div', array('id' =>'notices_primary'));
-            $this->element('h2', null, _('Notices'));
-            $this->elementStart('ul', 'notices');
-
-            while ($message->fetch() && $cnt <= MESSAGES_PER_PAGE) {
-                $cnt++;
-
-                if ($cnt > MESSAGES_PER_PAGE) {
-                    break;
-                }
 
-                $this->showMessage($message);
-            }
+            $ml = $this->getMessageList($message);
 
-            $this->elementEnd('ul');
+            $cnt = $ml->show();
 
-            $this->pagination($this->page > 1, $cnt > MESSAGES_PER_PAGE,
-                              $this->page, $this->trimmed('action'),
+            $this->pagination($this->page > 1,
+                              $cnt > MESSAGES_PER_PAGE,
+                              $this->page,
+                              $this->trimmed('action'),
                               array('nickname' => $this->user->nickname));
-            $this->elementEnd('div');
-            $message->free();
-            unset($message);
-        }
-        else {
-            $this->element('p', 'guide', _('You have no private messages. You can send private message to engage other users in conversation. People can send you messages for your eyes only.'));
+        } else {
+            $this->element('p', 
+                           'guide', 
+                           _('You have no private messages. '.
+                             'You can send private message to engage other users in conversation. '.
+                             'People can send you messages for your eyes only.'));
         }
     }
 
@@ -145,95 +133,11 @@ class MailboxAction extends CurrentUserDesignAction
         return null;
     }
 
-    /**
-     * returns the profile we want to show with the message
-     *
-     * For inboxes, we show the sender; for outboxes, the recipient.
-     *
-     * @param Message $message The message to get the profile for
-     *
-     * @return Profile The profile that matches the message
-     */
-
-    function getMessageProfile($message)
+    function getMessageList($message)
     {
         return null;
     }
 
-    /**
-     * show a single message in the list format
-     *
-     * XXX: This needs to be extracted out into a MessageList similar
-     * to NoticeList.
-     *
-     * @param Message $message the message to show
-     *
-     * @return void
-     */
-
-    function showMessage($message)
-    {
-        $this->elementStart('li', array('class' => 'hentry notice',
-                                         'id' => 'message-' . $message->id));
-
-        $profile = $this->getMessageProfile($message);
-
-        $this->elementStart('div', 'entry-title');
-        $this->elementStart('span', 'vcard author');
-        $this->elementStart('a', array('href' => $profile->profileurl,
-                                       'class' => 'url'));
-        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
-        $this->element('img', array('src' => ($avatar) ?
-                                    $avatar->displayUrl() :
-                                    Avatar::defaultImage(AVATAR_STREAM_SIZE),
-                                    'class' => 'photo avatar',
-                                    'width' => AVATAR_STREAM_SIZE,
-                                    'height' => AVATAR_STREAM_SIZE,
-                                    'alt' =>
-                                    ($profile->fullname) ? $profile->fullname :
-                                    $profile->nickname));
-        $this->element('span', array('class' => 'nickname fn'),
-                            $profile->nickname);
-        $this->elementEnd('a');
-        $this->elementEnd('span');
-
-        // FIXME: URL, image, video, audio
-        $this->elementStart('p', array('class' => 'entry-content'));
-        $this->raw($message->rendered);
-        $this->elementEnd('p');
-        $this->elementEnd('div');
-
-        $messageurl = common_local_url('showmessage',
-                                       array('message' => $message->id));
-
-        // XXX: we need to figure this out better. Is this right?
-        if (strcmp($message->uri, $messageurl) != 0 &&
-            preg_match('/^http/', $message->uri)) {
-            $messageurl = $message->uri;
-        }
-
-        $this->elementStart('div', 'entry-content');
-        $this->elementStart('a', array('rel' => 'bookmark',
-                                       'class' => 'timestamp',
-                                       'href' => $messageurl));
-        $dt = common_date_iso8601($message->created);
-        $this->element('abbr', array('class' => 'published',
-                                     'title' => $dt),
-                               common_date_string($message->created));
-        $this->elementEnd('a');
-
-        if ($message->source) {
-            $this->elementStart('span', 'source');
-            // FIXME: bad i18n. Device should be a parameter (from %s).
-            $this->text(_('from'));
-            $this->element('span', 'device', $this->showSource($message->source));
-            $this->elementEnd('span');
-        }
-        $this->elementEnd('div');
-
-        $this->elementEnd('li');
-    }
-
     /**
      * Show the page notice
      *
@@ -252,44 +156,6 @@ class MailboxAction extends CurrentUserDesignAction
         $this->elementEnd('div');
     }
 
-    /**
-     * Show the source of the message
-     *
-     * Returns either the name (and link) of the API client that posted the notice,
-     * or one of other other channels.
-     *
-     * @param string $source the source of the message
-     *
-     * @return void
-     */
-
-    function showSource($source)
-    {
-        $source_name = _($source);
-        switch ($source) {
-        case 'web':
-        case 'xmpp':
-        case 'mail':
-        case 'omb':
-        case 'api':
-            $this->element('span', 'device', $source_name);
-            break;
-        default:
-            $ns = Notice_source::staticGet($source);
-            if ($ns) {
-                $this->elementStart('span', 'device');
-                $this->element('a', array('href' => $ns->url,
-                                               'rel' => 'external'),
-                                    $ns->name);
-                $this->elementEnd('span');
-            } else {
-                $this->element('span', 'device', $source_name);
-            }
-            break;
-        }
-        return;
-    }
-
     /**
      * Mailbox actions are read only
      *
@@ -302,5 +168,4 @@ class MailboxAction extends CurrentUserDesignAction
     {
          return true;
     }
-
 }
diff --git a/lib/messagelist.php b/lib/messagelist.php
new file mode 100644 (file)
index 0000000..da7e9a6
--- /dev/null
@@ -0,0 +1,107 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * The message list widget
+ * 
+ * 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  Widget
+ * @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);
+}
+
+/**
+ * Message list widget
+ *
+ * @category  Widget
+ * @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/
+ */
+
+abstract class MessageList extends Widget
+{
+    var $message;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out     Output context
+     * @param Message       $message Stream of messages to show
+     */
+    function __construct($out, $message)
+    {
+        parent::__construct($out);
+        $this->message = $message;
+    }
+    
+    /**
+     * Show the widget
+     * 
+     * Uses newItem() to create each new item.
+     *
+     * @return integer count of messages seen.
+     */
+    function show()
+    {
+            $cnt = 0;
+
+            $this->out->elementStart('div', array('id' =>'notices_primary'));
+
+            $this->out->element('h2', null, _('Messages'));
+
+            $this->out->elementStart('ul', 'notices messages');
+
+            while ($this->message->fetch() && $cnt <= MESSAGES_PER_PAGE) {
+
+                $cnt++;
+
+                if ($cnt > MESSAGES_PER_PAGE) {
+                    break;
+                }
+                
+                $mli = $this->newItem($this->message);
+
+                $mli->show();
+            }
+
+            $this->out->elementEnd('ul');
+
+            $this->out->elementEnd('div');
+    }
+
+    /**
+     * Create a new message item for a message
+     *
+     * @param Message $message The message to show
+     *
+     * @return MessageListItem an item to show
+     */
+    abstract function newItem($message);
+}
diff --git a/lib/messagelistitem.php b/lib/messagelistitem.php
new file mode 100644 (file)
index 0000000..44e6976
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * A single list item for showing in a message list
+ * 
+ * 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  Widget
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+if (!defined('STATUSNET')) {
+    // This check helps protect against security problems;
+    // your code file can't be executed directly from the web.
+    exit(1);
+}
+
+/**
+ * A single item in a message list
+ *
+ * @category  Widget
+ * @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/
+ */
+abstract class MessageListItem extends Widget
+{
+    var $message;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out     Output context
+     * @param Message       $message Message to show
+     */
+    function __construct($out, $message)
+    {
+        parent::__construct($out);
+        $this->message = $message;
+    }
+
+    /**
+     * Show the widget
+     *
+     * @return void
+     */
+
+    function show()
+    {
+        $this->out->elementStart('li', array('class' => 'hentry notice',
+                                             'id' => 'message-' . $this->message->id));
+
+        $profile = $this->getMessageProfile();
+
+        $this->out->elementStart('div', 'entry-title');
+        $this->out->elementStart('span', 'vcard author');
+        $this->out->elementStart('a', array('href' => $profile->profileurl,
+                                            'class' => 'url'));
+        $avatar = $profile->getAvatar(AVATAR_STREAM_SIZE);
+        $this->out->element('img', array('src' => ($avatar) ?
+                                         $avatar->displayUrl() :
+                                         Avatar::defaultImage(AVATAR_STREAM_SIZE),
+                                         'class' => 'photo avatar',
+                                         'width' => AVATAR_STREAM_SIZE,
+                                         'height' => AVATAR_STREAM_SIZE,
+                                         'alt' =>
+                                         ($profile->fullname) ? $profile->fullname :
+                                         $profile->nickname));
+        $this->out->element('span', array('class' => 'nickname fn'),
+                            $profile->nickname);
+        $this->out->elementEnd('a');
+        $this->out->elementEnd('span');
+
+        // FIXME: URL, image, video, audio
+        $this->out->elementStart('p', array('class' => 'entry-content'));
+        $this->out->raw($this->message->rendered);
+        $this->out->elementEnd('p');
+        $this->out->elementEnd('div');
+
+        $messageurl = common_local_url('showmessage',
+                                       array('message' => $this->message->id));
+
+        // XXX: we need to figure this out better. Is this right?
+        if (strcmp($this->message->uri, $messageurl) != 0 &&
+            preg_match('/^http/', $this->message->uri)) {
+            $messageurl = $this->message->uri;
+        }
+
+        $this->out->elementStart('div', 'entry-content');
+        $this->out->elementStart('a', array('rel' => 'bookmark',
+                                            'class' => 'timestamp',
+                                            'href' => $messageurl));
+        $dt = common_date_iso8601($this->message->created);
+        $this->out->element('abbr', array('class' => 'published',
+                                          'title' => $dt),
+                            common_date_string($this->message->created));
+        $this->out->elementEnd('a');
+
+        if ($this->message->source) {
+            $this->out->elementStart('span', 'source');
+            // FIXME: bad i18n. Device should be a parameter (from %s).
+            $this->out->text(_('from'));
+            $this->showSource($this->message->source);
+            $this->out->elementEnd('span');
+        }
+        $this->out->elementEnd('div');
+
+        $this->out->elementEnd('li');
+    }
+
+
+    /**
+     * Show the source of the message
+     *
+     * Returns either the name (and link) of the API client that posted the notice,
+     * or one of other other channels.
+     *
+     * @param string $source the source of the message
+     *
+     * @return void
+     */
+    function showSource($source)
+    {
+        $source_name = _($source);
+        switch ($source) {
+        case 'web':
+        case 'xmpp':
+        case 'mail':
+        case 'omb':
+        case 'api':
+            $this->out->element('span', 'device', $source_name);
+            break;
+        default:
+            $ns = Notice_source::staticGet($source);
+            if ($ns) {
+                $this->out->elementStart('span', 'device');
+                $this->out->element('a', array('href' => $ns->url,
+                                               'rel' => 'external'),
+                                    $ns->name);
+                $this->out->elementEnd('span');
+            } else {
+                $this->out->element('span', 'device', $source_name);
+            }
+            break;
+        }
+        return;
+    }
+
+    /**
+     * Return the profile to show in the message item
+     *
+     * Overridden in sub-classes to show sender, receiver, or whatever
+     *
+     * @return Profile profile to show avatar and name of
+     */
+    abstract function getMessageProfile();
+}
diff --git a/plugins/GroupPrivateMessage/GroupPrivateMessagePlugin.php b/plugins/GroupPrivateMessage/GroupPrivateMessagePlugin.php
new file mode 100644 (file)
index 0000000..09fd1d7
--- /dev/null
@@ -0,0 +1,505 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Private groups for StatusNet 0.9.x
+ *
+ * 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  Privacy
+ * @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);
+}
+
+/**
+ * Private groups
+ *
+ * This plugin allows users to send private messages to a group.
+ *
+ * @category  Privacy
+ * @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 GroupPrivateMessagePlugin extends Plugin
+{
+    /**
+     * Database schema setup
+     *
+     * @see Schema
+     * @see ColumnDef
+     *
+     * @return boolean hook value
+     */
+
+    function onCheckSchema()
+    {
+        $schema = Schema::get();
+
+        // For storing user-submitted flags on profiles
+
+        $schema->ensureTable('group_privacy_settings',
+                             array(new ColumnDef('group_id',
+                                                 'integer',
+                                                 null,
+                                                 false,
+                                                 'PRI'),
+                                   new ColumnDef('allow_privacy',
+                                                 'integer'),
+                                   new ColumnDef('allow_sender',
+                                                 'integer'),
+                                   new ColumnDef('created',
+                                                 'datetime'),
+                                   new ColumnDef('modified',
+                                                 'timestamp')));
+                             
+        $schema->ensureTable('group_message',
+                             array(new ColumnDef('id',
+                                                 'char',
+                                                 36,
+                                                 false,
+                                                 'PRI'),
+                                   new ColumnDef('uri',
+                                                 'varchar',
+                                                 255,
+                                                 false,
+                                                 'UNI'),
+                                   new ColumnDef('from_profile',
+                                                 'integer',
+                                                 null,
+                                                 false,
+                                                 'MUL'),
+                                   new ColumnDef('to_group',
+                                                 'integer',
+                                                 null,
+                                                 false,
+                                                 'MUL'),
+                                   new ColumnDef('content',
+                                                 'text'),
+                                   new ColumnDef('rendered',
+                                                 'text'),
+                                   new ColumnDef('url',
+                                                 'varchar',
+                                                 255,
+                                                 false,
+                                                 'UNI'),
+                                   new ColumnDef('created',
+                                                 'datetime')));
+
+        $schema->ensureTable('group_message_profile',
+                             array(new ColumnDef('to_profile',
+                                                 'integer',
+                                                 null,
+                                                 false,
+                                                 'PRI'),
+                                   new ColumnDef('group_message_id',
+                                                 'char',
+                                                 36,
+                                                 false,
+                                                 'PRI'),
+                                   new ColumnDef('created',
+                                                 'datetime')));
+
+        return true;
+    }
+
+    /**
+     * Load related modules when needed
+     *
+     * @param string $cls Name of the class to be loaded
+     *
+     * @return boolean hook value
+     */
+
+    function onAutoload($cls)
+    {
+        $dir = dirname(__FILE__);
+
+        switch ($cls)
+        {
+        case 'GroupinboxAction':
+        case 'ShowgroupmessageAction':
+        case 'NewgroupmessageAction':
+            include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
+            return false;
+        case 'Group_privacy_settings':
+        case 'Group_message':
+        case 'Group_message_profile':
+            include_once $dir . '/'.$cls.'.php';
+            return false;
+        case 'GroupMessageCommand':
+        case 'GroupMessageList':
+        case 'GroupMessageListItem':
+        case 'GroupMessageForm':
+            include_once $dir . '/'.strtolower($cls).'.php';
+            return false;
+        default:
+            return true;
+        }
+    }
+
+    /**
+     * Map URLs to actions
+     *
+     * @param Net_URL_Mapper $m path-to-action mapper
+     *
+     * @return boolean hook value
+     */
+
+    function onRouterInitialized($m)
+    {
+        $m->connect('group/:nickname/inbox',
+                    array('action' => 'groupinbox'),
+                    array('nickname' => Nickname::DISPLAY_FMT));
+
+        $m->connect('group/message/:id',
+                    array('action' => 'showgroupmessage'),
+                    array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
+
+        $m->connect('group/:nickname/message/new',
+                    array('action' => 'newgroupmessage'),
+                    array('nickname' => Nickname::DISPLAY_FMT));
+
+        return true;
+    }
+
+    /**
+     * Add group inbox to the menu
+     *
+     * @param Action $action The current action handler. Use this to
+     *                       do any output.
+     *
+     * @return boolean hook value; true means continue processing, false means stop.
+     *
+     * @see Action
+     */
+
+    function onEndGroupGroupNav($groupnav)
+    {
+        $action = $groupnav->action;
+        $group  = $groupnav->group;
+
+        $action->menuItem(common_local_url('groupinbox',
+                                           array('nickname' => $group->nickname)),
+                          _m('Inbox'),
+                          _m('Private messages for this group'),
+                          $action->trimmed('action') == 'groupinbox',
+                          'nav_group_inbox');
+        return true;
+    }
+
+    /**
+     * Create default group privacy settings at group create time
+     *
+     * @param User_group $group Group that was just created
+     *
+     * @result boolean hook value
+     */
+
+    function onEndGroupSave($group)
+    {
+        $gps = new Group_privacy_settings();
+
+        $gps->group_id      = $group->id;
+        $gps->allow_privacy = Group_privacy_settings::SOMETIMES;
+        $gps->allow_sender  = Group_privacy_settings::MEMBER;
+        $gps->created       = common_sql_now();
+        $gps->modified      = $gps->created;
+
+        // This will throw an exception on error
+
+        $gps->insert();
+
+        return true;
+    }
+
+    /**
+     * Show group privacy controls on group edit form
+     *
+     * @param GroupEditForm $form form being shown
+     */
+
+    function onEndGroupEditFormData($form)
+    {
+        $gps = null;
+
+        if (!empty($form->group)) {
+            $gps = Group_privacy_settings::staticGet('group_id', $form->group->id);
+        }
+
+        $form->out->elementStart('li');
+        $form->out->dropdown('allow_privacy',
+                             _('Private messages'),
+                             array(Group_privacy_settings::SOMETIMES => _('Sometimes'),
+                                   Group_privacy_settings::ALWAYS => _('Always'),
+                                   Group_privacy_settings::NEVER => _('Never')),
+                             _('Whether to allow private messages to this group'),
+                             false,
+                             (empty($gps)) ? Group_privacy_settings::SOMETIMES : $gps->allow_privacy);
+        $form->out->elementEnd('li');
+        $form->out->elementStart('li');
+        $form->out->dropdown('allow_sender',
+                             _('Private sender'),
+                             array(Group_privacy_settings::EVERYONE => _('Everyone'),
+                                   Group_privacy_settings::MEMBER => _('Member'),
+                                   Group_privacy_settings::ADMIN => _('Admin')),
+                             _('Who can send private messages to the group'),
+                             false,
+                             (empty($gps)) ? Group_privacy_settings::MEMBER : $gps->allow_sender);
+        $form->out->elementEnd('li');
+        return true;
+    }
+
+    function onEndGroupSaveForm($action)
+    {
+        $gps = null;
+
+        if (!empty($action->group)) {
+            $gps = Group_privacy_settings::staticGet('group_id', $action->group->id);
+        }
+
+        $orig = null;
+
+        if (empty($gps)) {
+            $gps = new Group_privacy_settings();
+            $gps->group_id = $action->group->id;
+        } else {
+            $orig = clone($gps);
+        }
+        
+        $gps->allow_privacy = $action->trimmed('allow_privacy');
+        $gps->allow_sender  = $action->trimmed('allow_sender');
+
+        if (empty($orig)) {
+            $gps->created = common_sql_now();
+            $gps->insert();
+        } else {
+            $gps->update($orig);
+        }
+        
+        return true;
+    }
+
+    /**
+     * Overload 'd' command to send private messages to groups.
+     * 
+     * 'd !group word word word' will send the private message
+     * 'word word word' to the group 'group'.
+     * 
+     * @param string  $cmd     Command being run
+     * @param string  $arg     Rest of the message (including address)
+     * @param User    $user    User sending the message
+     * @param Command &$result The resulting command object to be run.
+     * 
+     * @return boolean hook value
+     */
+    function onStartIntepretCommand($cmd, $arg, $user, &$result)
+    {
+        if ($cmd == 'd' || $cmd == 'dm') {
+
+            $this->debug('Got a d command');
+
+            // Break off the first word as the address
+
+            $pieces = explode(' ', $arg, 2);
+
+            if (count($pieces) == 1) {
+                $pieces[] = null;
+            }
+
+            list($addr, $msg) = $pieces;
+
+            if (!empty($addr) && $addr[0] == '!') {
+                $result = new GroupMessageCommand($user, substr($addr, 1), $msg);
+                Event::handle('EndInterpretCommand', array($cmd, $arg, $user, $result));
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * To add a "Message" button to the group profile page
+     *
+     * @param Action     $action The showgroup action being shown
+     * @param User_group $group  The current group
+     * 
+     * @return boolean hook value
+     */
+    function onEndGroupActionsList($action, $group)
+    {
+        $cur = common_current_user();
+
+        if (empty($cur)) {
+            return true;
+        }
+
+        try {
+            Group_privacy_settings::ensurePost($cur, $group);
+        } catch (Exception $e) {
+            return true;
+        }
+
+        $action->elementStart('li', 'entity_send-a-message');
+        $action->element('a', array('href' => common_local_url('newgroupmessage', array('nickname' => $group->nickname)),
+                                    'title' => _('Send a direct message to this group')),
+                         _('Message'));
+        // $form = new GroupMessageForm($action, $group);
+        // $form->hidden = true;
+        // $form->show();
+        $action->elementEnd('li');
+        return true;
+    }
+
+    /**
+     * When saving a notice, check its groups. If any of them has
+     * privacy == always, force a group private message to all mentioned groups.
+     * If any of the groups disallows private messages, skip it.
+     *
+     * @param 
+     *
+     */
+
+    function onStartNoticeSave(&$notice) {
+
+        // Look for group tags
+        // FIXME: won't work for remote groups
+        // @fixme if Notice::saveNew is refactored so we can just pull its list
+        // of groups between processing and saving, make use of it
+
+        $count = preg_match_all('/(?:^|\s)!(' . Nickname::DISPLAY_FMT . ')/',
+                                strtolower($notice->content),
+                                $match);
+
+        $groups = array();
+        $ignored = array();
+
+        $forcePrivate = false;
+
+        if ($count > 0) {
+
+            /* Add them to the database */
+
+            foreach (array_unique($match[1]) as $nickname) {
+
+                $group = User_group::getForNickname($nickname, $profile);
+
+                if (empty($group)) {
+                    continue;
+                }
+
+                $gps = Group_privacy_settings::forGroup($group);
+
+                switch ($gps->allow_privacy) {
+                case Group_privacy_settings::ALWAYS:
+                    $forcePrivate = true;
+                    // fall through
+                case Group_privacy_settings::SOMETIMES:
+                    $groups[] = $group;
+                    break;
+                case Group_privacy_settings::NEVER:
+                    $ignored[] = $group;
+                    break;
+                }
+            }
+
+            if ($forcePrivate) {
+
+                foreach ($ignored as $group) {
+                    common_log(LOG_NOTICE,
+                               "Notice forced to group direct message ".
+                               "but group ".$group->nickname." does not allow them.");
+                }
+
+                $user = User::staticGet('id', $notice->profile_id);
+
+                if (empty($user)) {
+                    common_log(LOG_WARNING,
+                               "Notice forced to group direct message ".
+                               "but profile ".$notice->profile_id." is not a local user.");
+                } else {
+                    foreach ($groups as $group) {
+                        Group_message::send($user, $group, $notice->content);
+                    }
+                }
+
+                // Don't save the notice!
+                // FIXME: this is probably cheating.
+                throw new ClientException(sprintf(_('Forced notice to private group message.')),
+                                          200);
+            }
+        }
+        
+        return true;
+    }
+
+    /**
+     * Show an indicator that the group is (essentially) private on the group page
+     *
+     * @param Action     $action The action being shown
+     * @param User_group $group  The group being shown
+     *
+     * @return boolean hook value
+     */
+
+    function onEndGroupProfileElements($action, $group)
+    {
+        $gps = Group_privacy_settings::forGroup($group);
+        
+        if ($gps->allow_privacy == Group_privacy_settings::ALWAYS) {
+            $action->element('p', 'privategroupindicator', _('Private'));
+        }
+
+        return true;
+    }
+
+    function onStartShowExportData($action)
+    {
+        if ($action instanceof ShowgroupAction) {
+            $gps = Group_privacy_settings::forGroup($action->group);
+        
+            if ($gps->allow_privacy == Group_privacy_settings::ALWAYS) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    function onPluginVersion(&$versions)
+    {
+        $versions[] = array('name' => 'GroupPrivateMessage',
+                            'version' => STATUSNET_VERSION,
+                            'author' => 'Evan Prodromou',
+                            'homepage' => 'http://status.net/wiki/Plugin:GroupPrivateMessage',
+                            'rawdescription' =>
+                            _m('Allow posting DMs to a group.'));
+        return true;
+    }
+}
diff --git a/plugins/GroupPrivateMessage/Group_message.php b/plugins/GroupPrivateMessage/Group_message.php
new file mode 100644 (file)
index 0000000..f8c0c70
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * Data class for group direct messages
+ *
+ * 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 group direct messages
+ *
+ * @category GroupPrivateMessage
+ * @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 Group_message extends Memcached_DataObject
+{
+    public $__table = 'group_message'; // table name
+    public $id;                        // char(36)  primary_key not_null
+    public $uri;                       // varchar(255)
+    public $from_profile;              // int
+    public $to_group;                  // int
+    public $content;
+    public $rendered;
+    public $url;
+    public $created;
+
+    /**
+     * 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 Group_message object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Group_message', $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('id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'uri' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'from_profile' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'to_group' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'content' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'rendered' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'url' => DB_DATAOBJECT_STR,
+                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+    }
+
+    /**
+     * 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
+     *
+     * @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('id' => 'K', 'uri' => 'U');
+    }
+
+    static function send($user, $group, $text)
+    {
+        if (!$user->hasRight(Right::NEWMESSAGE)) {
+            // XXX: maybe break this out into a separate right
+            throw new Exception(sprintf(_('User %s not allowed to send private messages.'),
+                                        $user->nickname));
+        }
+
+        Group_privacy_settings::ensurePost($user, $group);
+
+        $text = $user->shortenLinks($text);
+
+        // We use the same limits as for 'regular' private messages.
+
+        if (Message::contentTooLong($text)) {
+            throw new Exception(sprintf(_m('That\'s too long. Maximum message size is %d character.',
+                                           'That\'s too long. Maximum message size is %d characters.',
+                                           Message::maxContent()),
+                                        Message::maxContent()));
+        }
+
+        // Valid! Let's do this thing!
+
+        $gm = new Group_message();
+        
+        $gm->id           = UUID::gen();
+        $gm->uri          = common_local_url('showgroupmessage', array('id' => $gm->id));
+        $gm->from_profile = $user->id;
+        $gm->to_group     = $group->id;
+        $gm->content      = $text; // XXX: is this cool?!
+        $gm->rendered     = common_render_text($text);
+        $gm->url          = $gm->uri;
+        $gm->created      = common_sql_now();
+
+        // This throws a conniption if there's a problem
+
+        $gm->insert();
+
+        $gm->distribute();
+
+        return $gm;
+    }
+
+    function distribute()
+    {
+        $group = User_group::staticGet('id', $this->to_group);
+        
+        $member = $group->getMembers();
+
+        while ($member->fetch()) {
+            Group_message_profile::send($this, $member);
+        }
+    }
+
+    function getGroup()
+    {
+        $group = User_group::staticGet('id', $this->to_group);
+        if (empty($group)) {
+            throw new ServerException(_('No group for group message'));
+        }
+        return $group;
+    }
+
+    function getSender()
+    {
+        $sender = Profile::staticGet('id', $this->from_profile);
+        if (empty($sender)) {
+            throw new ServerException(_('No sender for group message'));
+        }
+        return $sender;
+    }
+
+    static function forGroup($group, $offset, $limit)
+    {
+        // XXX: cache
+        $gm = new Group_message();
+
+        $gm->to_group = $group->id;
+        $gm->orderBy('created DESC');
+        $gm->limit($offset, $limit);
+
+        $gm->find();
+
+        return $gm;
+    }
+
+}
diff --git a/plugins/GroupPrivateMessage/Group_message_profile.php b/plugins/GroupPrivateMessage/Group_message_profile.php
new file mode 100644 (file)
index 0000000..bd778b8
--- /dev/null
@@ -0,0 +1,189 @@
+<?php
+/**
+ * Who received a group message
+ *
+ * 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);
+}
+
+require_once INSTALLDIR . '/classes/Memcached_DataObject.php';
+
+/**
+ * Data class for group direct messages for users
+ *
+ * @category GroupPrivateMessage
+ * @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 Group_message_profile extends Memcached_DataObject
+{
+    public $__table = 'group_message_profile'; // table name
+    public $to_profile;                        // int
+    public $group_message_id;                  // char(36)  primary_key not_null
+    public $created;
+
+    /**
+     * 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 Group_message object found, or null for no hits
+     *
+     */
+    function staticGet($k, $v=null)
+    {
+        return Memcached_DataObject::staticGet('Group_message_profile', $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('to_profile' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'group_message_id' => DB_DATAOBJECT_STR + DB_DATAOBJECT_NOTNULL,
+                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+    }
+
+    /**
+     * 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
+     *
+     * @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('to_profile' => 'K', 'group_message_id' => 'K');
+    }
+
+    /**
+     * No sequence keys in this table.
+     */
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function send($gm, $profile)
+    {
+        $gmp = new Group_message_profile();
+        
+        $gmp->group_message_id = $gm->id;
+        $gmp->to_profile       = $profile->id;
+        $gmp->created          = common_sql_now();
+
+        $gmp->insert();
+
+        $gmp->notify();
+
+        return $gmp;
+    }
+
+    function notify()
+    {
+        // XXX: add more here
+        $this->notifyByMail();
+    }
+
+    function notifyByMail() 
+    {
+        $to = User::staticGet('id', $this->to_profile);
+
+        if (empty($to) || is_null($to->email) || !$to->emailnotifymsg) {
+            return true;
+        }
+
+        $gm = Group_message::staticGet('id', $this->group_message_id);
+
+        $from_profile = Profile::staticGet('id', $gm->from_profile);
+
+        $group = $gm->getGroup();
+
+        common_switch_locale($to->language);
+
+        // TRANS: Subject for direct-message notification email.
+        // TRANS: %s is the sending user's nickname.
+        $subject = sprintf(_('New private message from %s to group %s'), $from->nickname, $group->nickname);
+
+        $from_profile = $from->getProfile();
+
+        // TRANS: Body for direct-message notification email.
+        // TRANS: %1$s is the sending user's long name, %2$s is the sending user's nickname,
+        // TRANS: %3$s is the message content, %4$s a URL to the message,
+        // TRANS: %5$s is the StatusNet sitename.
+        $body = sprintf(_("%1\$s (%2\$s) sent a private message to group %3\$s:\n\n".
+                          "------------------------------------------------------\n".
+                          "%4\$s\n".
+                          "------------------------------------------------------\n\n".
+                          "You can reply to their message here:\n\n".
+                          "%5\$s\n\n".
+                          "Don't reply to this email; it won't get to them.\n\n".
+                          "With kind regards,\n".
+                          "%6\$s\n"),
+                        $from_profile->getBestName(),
+                        $from->nickname,
+                        $group->nickname,
+                        $this->content,
+                        common_local_url('newmessage', array('to' => $from->id)),
+                        common_config('site', 'name'));
+
+        $headers = _mail_prepare_headers('message', $to->nickname, $from->nickname);
+
+        common_switch_locale();
+
+        return mail_to_user($to, $subject, $body, $headers);
+    }
+}
diff --git a/plugins/GroupPrivateMessage/Group_privacy_settings.php b/plugins/GroupPrivateMessage/Group_privacy_settings.php
new file mode 100644 (file)
index 0000000..0176d3b
--- /dev/null
@@ -0,0 +1,201 @@
+<?php
+/**
+ * Data class for group privacy settings
+ *
+ * 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 group privacy
+ *
+ * Stores admin preferences about the group.
+ *
+ * @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 Group_privacy_settings extends Memcached_DataObject
+{
+    public $__table = 'group_privacy_settings';
+    /** ID of the group. */
+    public $group_id;      
+    /** When to allow privacy: always, sometimes, or never. */
+    public $allow_privacy;
+    /** Who can send private messages: everyone, member, admin */
+    public $allow_sender; 
+    /** row creation timestamp */
+    public $created;
+    /** Last-modified timestamp */
+    public $modified;
+
+    /** NEVER is */
+
+    const SOMETIMES = -1;
+    const NEVER  = 0;
+    const ALWAYS = 1;
+
+    /** These are bit-mappy, as a hedge against the future. */
+
+    const EVERYONE = 1;
+    const MEMBER   = 2;
+    const ADMIN    = 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('Group_privacy_settings', $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('group_id' => DB_DATAOBJECT_INT + DB_DATAOBJECT_NOTNULL,
+                     'allow_privacy' => DB_DATAOBJECT_INT,
+                     'allow_sender' => DB_DATAOBJECT_INT,
+                     'created' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL,
+                     'modified' => DB_DATAOBJECT_STR + DB_DATAOBJECT_DATE + DB_DATAOBJECT_TIME + DB_DATAOBJECT_NOTNULL);
+                     
+    }
+
+    /**
+     * 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
+     *
+     * @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('group_id' => 'K');
+    }
+
+    /**
+     * Magic formula for non-autoincrementing integer primary keys
+     *
+     * @return array magic three-false array that stops auto-incrementing.
+     */
+
+    function sequenceKey()
+    {
+        return array(false, false, false);
+    }
+
+    function forGroup($group)
+    {
+        $gps = Group_privacy_settings::staticGet('group_id', $group->id);
+
+        if (empty($gps)) {
+            // make a fake one with defaults
+            $gps = new Group_privacy_settings();
+            $gps->allow_privacy = Group_privacy_settings::SOMETIMES;
+            $gps->allow_sender  = Group_privacy_settings::MEMBER;
+        }
+
+        return $gps;
+    }
+
+    function ensurePost($user, $group)
+    {
+        $gps = self::forGroup($group);
+
+        if ($gps->allow_privacy == Group_privacy_settings::NEVER) {
+            throw new Exception(sprintf(_('Group %s does not allow private messages.'),
+                                        $group->nickname));
+        }
+
+        switch ($gps->allow_sender) {
+        case Group_privacy_settings::EVERYONE:
+            $profile = $user->getProfile();
+            if (Group_block::isBlocked($group, $profile)) {
+                throw new Exception(sprintf(_('User %s is blocked from group %s.'),
+                                            $user->nickname,
+                                            $group->nickname));
+            }
+            break;
+        case Group_privacy_settings::MEMBER:
+            if (!$user->isMember($group)) {
+                throw new Exception(sprintf(_('User %s is not a member of group %s.'),
+                                            $user->nickname,
+                                            $group->nickname));
+            }
+            break;
+        case Group_privacy_settings::ADMIN:
+            if (!$user->isAdmin($group)) {
+                throw new Exception(sprintf(_('User %s is not an administrator of group %s.'),
+                                            $user->nickname,
+                                            $group->nickname));
+            }
+            break;
+        default:
+            throw new Exception(sprintf(_('Unknown privacy settings for group %s.'),
+                                        $group->nickname));
+        }
+
+        return true;
+    }
+}
diff --git a/plugins/GroupPrivateMessage/groupinbox.php b/plugins/GroupPrivateMessage/groupinbox.php
new file mode 100644 (file)
index 0000000..39789cc
--- /dev/null
@@ -0,0 +1,208 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * List of private messages to this group
+ * 
+ * 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  GroupPrivateMessage
+ * @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 list of private messages to this group
+ *
+ * @category  GroupPrivateMessage
+ * @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 GroupinboxAction extends GroupDesignAction
+{
+    var $gm;
+
+    /**
+     * For initializing members of the class.
+     *
+     * @param array $argarray misc. arguments
+     *
+     * @return boolean true
+     */
+    function prepare($argarray)
+    {
+        parent::prepare($argarray);
+
+        $cur = common_current_user();
+
+        if (empty($cur)) {
+            throw new ClientException(_('Only for logged-in users'), 403);
+        }
+
+        $nicknameArg = $this->trimmed('nickname');
+
+        $nickname = common_canonical_nickname($nicknameArg);
+
+        if ($nickname != $nicknameArg) {
+            $url = common_local_url('groupinbox', array('nickname' => $nickname));
+            common_redirect($url);
+            return false;
+        }
+
+        $localGroup = Local_group::staticGet('nickname', $nickname);
+
+        if (empty($localGroup)) {
+            throw new ClientException(_('No such group'), 404);
+        }
+
+        $this->group = User_group::staticGet('id', $localGroup->group_id);
+
+        if (empty($this->group)) {
+            throw new ClientException(_('No such group'), 404);
+        }
+
+        if (!$cur->isMember($this->group)) {
+            throw new ClientException(_('Only for members'), 403);
+        }
+
+        $this->page = $this->trimmed('page');
+
+        if (!$this->page) {
+            $this->page = 1;
+        }
+        
+        $this->gm = Group_message::forGroup($this->group, 
+                                            ($this->page - 1) * MESSAGES_PER_PAGE,
+                                            MESSAGES_PER_PAGE + 1);
+        return true;
+    }
+
+    function showLocalNav()
+    {
+        $nav = new GroupNav($this, $this->group);
+        $nav->show();
+    }
+
+    function showNoticeForm()
+    {
+        $form = new GroupMessageForm($this, $this->group);
+        $form->show();
+    }
+
+    function showContent()
+    {
+        $gml = new GroupMessageList($this, $this->gm);
+        $cnt = $gml->show();
+
+        if ($cnt == 0) {
+            $this->element('p', 'guide', _m('This group has not received any private messages.'));
+        }
+        $this->pagination($this->page > 1,
+                          $cnt > MESSAGES_PER_PAGE,
+                          $this->page,
+                          'groupinbox',
+                          array('nickname' => $this->group->nickname));
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+    function handle($argarray=null)
+    {
+        $this->showPage();
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Title of the page
+     *
+     * @return string page title, with page number
+     */
+    function title()
+    {
+        $base = $this->group->getFancyName();
+
+        if ($this->page == 1) {
+            return sprintf(_('%s group inbox'), $base);
+        } else {
+            // TRANS: Page title for any but first group page.
+            // TRANS: %1$s is a group name, $2$s is a page number.
+            return sprintf(_('%1$s group inbox, page %2$d'),
+                           $base,
+                           $this->page);
+        }
+    }
+
+    /**
+     * Show the page notice
+     *
+     * Shows instructions for the page
+     *
+     * @return void
+     */
+
+    function showPageNotice()
+    {
+        $instr  = $this->getInstructions();
+        $output = common_markup_to_html($instr);
+
+        $this->elementStart('div', 'instructions');
+        $this->raw($output);
+        $this->elementEnd('div');
+    }
+
+    /**
+     * Instructions for using this page
+     *
+     * @return string localised instructions for using the page
+     */
+    function getInstructions()
+    {
+        // TRANS: Instructions for user inbox page.
+        return _m('This is the group inbox, which lists all incoming private messages for this group.');
+    }
+}
diff --git a/plugins/GroupPrivateMessage/groupmessagecommand.php b/plugins/GroupPrivateMessage/groupmessagecommand.php
new file mode 100644 (file)
index 0000000..3b3cf4c
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Command object for messages to groups
+ * 
+ * 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  Command
+ * @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);
+}
+
+/**
+ * Command object for messages to groups
+ *
+ * @category  General
+ * @package   StatusNet
+ * @author    Evan Prodromou <evan@status.net>
+ * @copyright 2011 StatusNet, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ * @link      http://status.net/
+ */
+
+class GroupMessageCommand extends Command
+{
+    /** User sending the message. */
+    var $user;
+    /** Nickname of the group they're sending to. */
+    var $nickname;
+    /** Text of the message. */
+    var $text;
+
+    /**
+     * Constructor
+     *
+     * @param User   $user     User sending the message
+     * @param string $nickname Nickname of the group
+     * @param string $text     Text of message
+     */
+
+    function __construct($user, $nickname, $text)
+    {
+        $this->user     = $user;
+        $this->nickname = $nickname;
+        $this->text     = $text;
+    }
+
+    function handle($channel)
+    {
+        // Throws a command exception if group not found
+        $group = $this->getGroup($this->nickname);
+
+        $gm = Group_message::send($this->user, $group, $this->text);
+
+        $channel->output($this->user, 
+                         sprintf(_('Direct message to group %s sent.'), 
+                                 $group->nickname));
+
+        return true;
+    }
+}
diff --git a/plugins/GroupPrivateMessage/groupmessageform.php b/plugins/GroupPrivateMessage/groupmessageform.php
new file mode 100644 (file)
index 0000000..7205d3f
--- /dev/null
@@ -0,0 +1,166 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Form for posting a group message
+ * 
+ * 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  GroupPrivateMessage
+ * @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 posting a group message
+ *
+ * @category  GroupPrivateMessage
+ * @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 GroupMessageForm extends Form
+{
+    var $group;
+    var $content;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out   Output context
+     * @param User_group    $group Group to post to
+     *
+     * @todo add a drop-down list to post to any group
+     */
+
+    function __construct($out, $group, $content=null)
+    {
+        parent::__construct($out);
+
+        $this->group   = $group;
+        $this->content = $content;
+    }
+
+    /**
+     * Action for the form
+     */
+    function action()
+    { 
+        return common_local_url('newgroupmessage',
+                                array('nickname' => $this->group->nickname));
+    }
+
+    /**
+     * Legend for the form
+     *
+     * @param
+     *
+     * @return
+     */
+    function formLegend()
+    {
+        $this->out->element('legend',
+                            null,
+                            sprintf(_('Message to %s'), $this->group->nickname));
+    }
+
+    /**
+     * id for the form
+     *
+     * @param
+     *
+     * @return
+     */
+
+    function id()
+    {
+        return 'form_notice-group-message';
+    }
+
+    /**
+     * class for the form
+     *
+     * @param
+     *
+     * @return
+     */
+
+    function formClass()
+    {
+        return 'form_notice';
+    }
+
+    /**
+     * Entry data
+     *
+     * @param
+     *
+     * @return
+     */
+
+    function formData()
+    {
+        $this->out->element('label', array('for' => 'notice_data-text',
+                                           'id' => 'notice_data-text-label'),
+                            sprintf(_('Direct message to %s'), $this->group->nickname));
+
+        $this->out->element('textarea', array('id' => 'notice_data-text',
+                                              'cols' => 35,
+                                              'rows' => 4,
+                                              'name' => 'content'),
+                            ($this->content) ? $this->content : '');
+
+        $contentLimit = Message::maxContent();
+
+        if ($contentLimit > 0) {
+            $this->out->elementStart('dl', 'form_note');
+            $this->out->element('dt', null, _('Available characters'));
+            $this->out->element('dd', array('id' => 'notice_text-count'),
+                                $contentLimit);
+            $this->out->elementEnd('dl');
+        }
+    }
+
+    /**
+     * Legend for the form
+     *
+     * @param
+     *
+     * @return
+     */
+
+    function formActions()
+    {
+        $this->out->element('input', array('id' => 'notice_action-submit',
+                                           'class' => 'submit',
+                                           'name' => 'message_send',
+                                           'type' => 'submit',
+                                           'value' => _m('Send button for sending notice', 'Send')));
+    }
+}
diff --git a/plugins/GroupPrivateMessage/groupmessagelist.php b/plugins/GroupPrivateMessage/groupmessagelist.php
new file mode 100644 (file)
index 0000000..07f8ed5
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Widget for showing list of group messages
+ * 
+ * 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  GroupPrivateMessage
+ * @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);
+}
+
+/**
+ * Widget for showing list of group messages
+ *
+ * @category  GroupPrivateMessage
+ * @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 GroupMessageList extends Widget
+{
+    var $gm;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out output context
+     * @param Group_message $gm  Group message stream
+     */
+    function __construct($out, $gm)
+    {
+        parent::__construct($out);
+        $this->gm = $gm;
+    }
+
+    /**
+     * Show the list
+     *
+     * @return void
+     */
+    function show()
+    {
+        $this->out->elementStart('ul', 'notices messages group-messages');
+
+        $cnt = 0;
+
+        while ($this->gm->fetch() && $cnt <= MESSAGES_PER_PAGE) {
+
+            $cnt++;
+
+            if ($cnt > MESSAGES_PER_PAGE) {
+                break;
+            }
+
+            $gmli = new GroupMessageListItem($this->out, $this->gm);
+            $gmli->show();
+        }
+
+        $this->out->elementEnd('ul');
+
+        return $cnt;
+    }
+}
diff --git a/plugins/GroupPrivateMessage/groupmessagelistitem.php b/plugins/GroupPrivateMessage/groupmessagelistitem.php
new file mode 100644 (file)
index 0000000..4c0fd2c
--- /dev/null
@@ -0,0 +1,113 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Widget for showing an individual group message
+ * 
+ * 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  GroupPrivateMessage
+ * @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);
+}
+
+/**
+ * Widget for showing a single group message
+ *
+ * @category  GroupPrivateMessage
+ * @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 GroupMessageListItem extends Widget
+{
+    var $gm;
+
+    /**
+     * Constructor
+     *
+     * @param HTMLOutputter $out output context
+     * @param Group_message $gm  Group message
+     */
+    function __construct($out, $gm)
+    {
+        parent::__construct($out);
+        $this->gm = $gm;
+    }
+
+    /**
+     * Show the item
+     *
+     * @return void
+     */
+    function show()
+    {
+        $group  = $this->gm->getGroup();
+        $sender = $this->gm->getSender();
+        $this->out->elementStart('li', array('class' => 'hentry notice message group-message',
+                                         'id' => 'message-' . $this->gm->id));
+
+        $this->out->elementStart('div', 'entry-title');
+        $this->out->elementStart('span', 'vcard author');
+        $this->out->elementStart('a', 
+                                 array('href' => $sender->profileurl,
+                                       'class' => 'url'));
+        $avatar = $sender->getAvatar(AVATAR_STREAM_SIZE);
+        $this->out->element('img', array('src' => ($avatar) ?
+                                    $avatar->displayUrl() :
+                                    Avatar::defaultImage(AVATAR_STREAM_SIZE),
+                                    'width' => AVATAR_STREAM_SIZE,
+                                    'height' => AVATAR_STREAM_SIZE,
+                                    'class' => 'photo avatar',
+                                    'alt' => $sender->getBestName()));
+        $this->out->element('span',
+                            array('class' => 'nickname fn'),
+                            $sender->nickname);
+        $this->out->elementEnd('a');
+        $this->out->elementEnd('span');
+
+        $this->out->elementStart('p', array('class' => 'entry-content message-content'));
+        $this->out->raw($this->gm->rendered);
+        $this->out->elementEnd('p');
+        $this->out->elementEnd('div');
+
+        $this->out->elementStart('div', 'entry-content');
+        $this->out->elementStart('a', array('rel' => 'bookmark',
+                                            'class' => 'timestamp',
+                                            'href' => $this->gm->url));
+        $dt = common_date_iso8601($this->gm->created);
+        $this->out->element('abbr', array('class' => 'published',
+                                          'title' => $dt),
+                            common_date_string($this->gm->created));
+        $this->out->elementEnd('a');
+        $this->out->elementEnd('div');
+
+        $this->out->elementEnd('li');
+    }
+}
diff --git a/plugins/GroupPrivateMessage/newgroupmessage.php b/plugins/GroupPrivateMessage/newgroupmessage.php
new file mode 100644 (file)
index 0000000..1ad24c4
--- /dev/null
@@ -0,0 +1,161 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Action for adding a new group message
+ * 
+ * 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  Cache
+ * @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);
+}
+
+/**
+ * Action for adding a new group message
+ *
+ * @category  Action
+ * @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 NewgroupmessageAction extends Action
+{
+    var $group;
+    var $user;
+    var $text;
+
+    /**
+     * 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)) {
+            throw new ClientException(_('Must be logged in.'), 403);
+        }
+
+        if (!$this->user->hasRight(Right::NEWMESSAGE)) {
+            throw new Exception(sprintf(_('User %s not allowed to send private messages.'),
+                                        $this->user->nickname));
+        }
+
+        $nicknameArg = $this->trimmed('nickname');
+
+        $nickname = common_canonical_nickname($nicknameArg);
+
+        if ($nickname != $nicknameArg) {
+            $url = common_local_url('newgroupmessage', array('nickname' => $nickname));
+            common_redirect($url, 301);
+            return false;
+        }
+
+        $localGroup = Local_group::staticGet('nickname', $nickname);
+
+        if (empty($localGroup)) {
+            throw new ClientException(_('No such group'), 404);
+        }
+
+        $this->group = User_group::staticGet('id', $localGroup->group_id);
+
+        if (empty($this->group)) {
+            throw new ClientException(_('No such group'), 404);
+        }
+
+        // This throws an exception on error
+
+        Group_privacy_settings::ensurePost($this->user, $this->group);
+
+        // If we're posted to, check session token and get text
+
+        if ($this->isPost()) {
+            $this->checkSessionToken();
+            $this->text = $this->trimmed('content');
+        }
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+
+    function handle($argarray=null)
+    {
+        if ($this->isPost()) {
+            $this->sendNewMessage();
+        } else {
+            $this->showPage();
+        }
+    }
+
+    function showNoticeForm()
+    {
+        $form = new GroupMessageForm($this, $this->group);
+        $form->show();
+    }
+
+    function sendNewMessage()
+    {
+        $gm = Group_message::send($this->user, $this->group, $this->text);
+
+        if ($this->boolean('ajax')) {
+            $this->startHTML('text/xml;charset=utf-8');
+            $this->elementStart('head');
+            $this->element('title', null, _('Message sent'));
+            $this->elementEnd('head');
+            $this->elementStart('body');
+            $this->element('p',
+                           array('id' => 'command_result'),
+                           sprintf(_('Direct message to %s sent.'),
+                                   $this->group->nickname));
+            $this->elementEnd('body');
+            $this->elementEnd('html');
+        } else {
+            common_redirect($gm->url, 303);
+        }
+    }
+
+    function title()
+    {
+        return sprintf(_('New message to group %s'), $this->group->nickname);
+    }
+}
diff --git a/plugins/GroupPrivateMessage/showgroupmessage.php b/plugins/GroupPrivateMessage/showgroupmessage.php
new file mode 100644 (file)
index 0000000..7329325
--- /dev/null
@@ -0,0 +1,188 @@
+<?php
+/**
+ * StatusNet - the distributed open-source microblogging tool
+ * Copyright (C) 2011, StatusNet, Inc.
+ *
+ * Show a single group message
+ * 
+ * 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  GroupPrivateMessage
+ * @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 private group message
+ *
+ * @category  GroupPrivateMessage
+ * @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 ShowgroupmessageAction extends Action
+{
+    var $gm;
+    var $group;
+    var $sender;
+    var $user;
+
+    /**
+     * 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)) {
+            throw new ClientException(_('Only logged-in users can view private messages.'),
+                                      403);
+        }
+
+        $id = $this->trimmed('id');
+
+        $this->gm = Group_message::staticGet('id', $id);
+
+        if (empty($this->gm)) {
+            throw new ClientException(_('No such message'), 404);
+        }
+
+        $this->group = User_group::staticGet('id', $this->gm->to_group);
+
+        if (empty($this->group)) {
+            throw new ServerException(_('Group not found.'));
+        }
+
+        if (!$this->user->isMember($this->group)) {
+            throw new ClientException(_('Cannot read message.'), 403);
+        }
+
+        $this->sender = Profile::staticGet('id', $this->gm->from_profile);
+
+        if (empty($this->sender)) {
+            throw new ServerException(_('No sender found.'));
+        }
+
+        return true;
+    }
+
+    /**
+     * Handler method
+     *
+     * @param array $argarray is ignored since it's now passed in in prepare()
+     *
+     * @return void
+     */
+
+    function handle($argarray=null)
+    {
+        $this->showPage();
+    }
+
+    /**
+     * Title of the page
+     */
+
+    function title()
+    {
+        return sprintf(_('Message from %1$s to group %2$s on %3$s'),
+                       $this->sender->nickname,
+                       $this->group->nickname,
+                       common_exact_date($this->gm->created));
+    }
+
+    /**
+     * Show the content area.
+     */
+
+    function showContent()
+    {
+        $this->elementStart('ul', 'notices messages');
+        $gmli = new GroupMessageListItem($this, $this->gm);
+        $gmli->show();
+        $this->elementEnd('ul');
+    }
+
+    /**
+     * Return true if read only.
+     *
+     * MAY override
+     *
+     * @param array $args other arguments
+     *
+     * @return boolean is read only action?
+     */
+
+    function isReadOnly($args)
+    {
+        return true;
+    }
+
+    /**
+     * Return last modified, if applicable.
+     *
+     * MAY override
+     *
+     * @return string last modified http header
+     */
+    function lastModified()
+    {
+        return max(strtotime($this->group->modified),
+                   strtotime($this->sender->modified),
+                   strtotime($this->gm->modified));
+    }
+
+    /**
+     * Return etag, if applicable.
+     *
+     * MAY override
+     *
+     * @return string etag http header
+     */
+    function etag()
+    {
+        $avatar = $this->sender->getAvatar(AVATAR_STREAM_SIZE);
+
+        $avtime = ($avatar) ? strtotime($avatar->modified) : 0;
+
+        return 'W/"' . implode(':', array($this->arg('action'),
+                                          common_user_cache_hash(),
+                                          common_language(),
+                                          $this->gm->id,
+                                          strtotime($this->sender->modified),
+                                          strtotime($this->group->modified),
+                                          $avtime)) . '"';
+    }
+}