]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge remote-tracking branch 'upstream/master' into social-master
authorRoland Haeder <roland@mxchange.org>
Tue, 5 Jan 2016 13:27:33 +0000 (14:27 +0100)
committerRoland Haeder <roland@mxchange.org>
Tue, 5 Jan 2016 13:27:33 +0000 (14:27 +0100)
Signed-off-by: Roland Haeder <roland@mxchange.org>
30 files changed:
actions/subqueue.php
classes/Managed_DataObject.php
classes/Notice.php
classes/Profile.php
classes/Subscription.php
lib/activityutils.php
lib/apiaction.php
lib/approvesubform.php
lib/emptyidexception.php [new file with mode: 0644]
lib/framework.php
lib/language.php
lib/noticelistitem.php
lib/noticestreamaction.php
lib/privatestreamexception.php [new file with mode: 0644]
lib/profilelist.php
lib/profilelistitem.php
lib/profileminilist.php
lib/profilenoticestream.php
lib/subqueuelist.php [new file with mode: 0644]
lib/subqueuelistitem.php [new file with mode: 0644]
plugins/Activity/ActivityPlugin.php
plugins/Event/EventPlugin.php
plugins/Event/classes/RSVP.php
plugins/Favorite/FavoritePlugin.php
plugins/Favorite/classes/Fave.php
plugins/OStatus/actions/ostatussub.php
plugins/Share/SharePlugin.php
theme/base/css/display.css
theme/neo-gnu/css/display.css
theme/neo-quitter/css/display.css

index c87202ddf1cc602ff8bc996c61d435c3824cc1cf..e4dcb025135c910844779c3df63583e50ecf39ff 100644 (file)
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once(INSTALLDIR.'/lib/profilelist.php');
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * List of group members
@@ -50,9 +46,9 @@ class SubqueueAction extends GalleryAction
     {
         parent::prepare($args);
 
-        if ($this->scoped->id != $this->target->id) {
+        if (!$this->target->sameAs($this->scoped)) {
             // TRANS: Client error displayed when trying to approve group applicants without being a group administrator.
-            $this->clientError(_('You may only approve your own pending subscriptions.'));
+            throw new ClientException(_('You may only approve your own pending subscriptions.'));
         }
         return true;
     }
@@ -88,47 +84,21 @@ class SubqueueAction extends GalleryAction
 
         $cnt = 0;
 
-        $members = $this->target->getRequests($offset, $limit);
-
-        if ($members) {
-            // @fixme change!
-            $member_list = new SubQueueList($members, $this);
-            $cnt = $member_list->show();
+        try {
+            $subqueue = $this->target->getRequests($offset, $limit);
+        } catch (NoResultException $e) {
+            // TRANS: If no pending subscription requests are found
+            $this->element('div', null, _m('You have no pending subscription requests.'));
+            return;
         }
 
-        $members->free();
+        $list = new SubQueueList($subqueue, $this);
+        $cnt = $list->show();
+
+        $subqueue->free();
 
         $this->pagination($this->page > 1, $cnt > PROFILES_PER_PAGE,
                           $this->page, 'subqueue',
                           array('nickname' => $this->target->getNickname())); // urgh
     }
 }
-
-class SubQueueList extends ProfileList
-{
-    function newListItem(Profile $profile)
-    {
-        return new SubQueueListItem($profile, $this->action);
-    }
-}
-
-class SubQueueListItem extends ProfileListItem
-{
-    function showActions()
-    {
-        $this->startActions();
-        if (Event::handle('StartProfileListItemActionElements', array($this))) {
-            $this->showApproveButtons();
-            Event::handle('EndProfileListItemActionElements', array($this));
-        }
-        $this->endActions();
-    }
-
-    function showApproveButtons()
-    {
-        $this->out->elementStart('li', 'entity_approval');
-        $form = new ApproveSubForm($this->out, $this->profile);
-        $form->show();
-        $this->out->elementEnd('li');
-    }
-}
index fb18e594f99bdbb93603ca80513c865974f9d4ea..7492d9097814dfa6daf3fc12ef96cd1200cc881a 100644 (file)
@@ -384,7 +384,7 @@ abstract class Managed_DataObject extends Memcached_DataObject
     static function getByID($id)
     {
         if (empty($id)) {
-            throw new ServerException('Empty ID on lookup');
+            throw new EmptyIdException(get_called_class());
         }
         // getByPK throws exception if id is null
         // or if the class does not have a single 'id' column as primary key
index 757205761a3e48ec43eca2eb7a3683ecdb43662b..47f03099a0f009d74f51cdf0713e13914ac80f20 100644 (file)
@@ -90,7 +90,7 @@ class Notice extends Managed_DataObject
                 'source' => array('type' => 'varchar', 'length' => 32, 'description' => 'source of comment, like "web", "im", or "clientname"'),
                 'conversation' => array('type' => 'int', 'description' => 'id of root notice in this conversation'),
                 'repeat_of' => array('type' => 'int', 'description' => 'notice this is a repeat of'),
-                'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => 'http://activitystrea.ms/schema/1.0/note'),
+                'object_type' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams object type', 'default' => null),
                 'verb' => array('type' => 'varchar', 'length' => 191, 'description' => 'URI representing activity streams verb', 'default' => 'http://activitystrea.ms/schema/1.0/post'),
                 'scope' => array('type' => 'int',
                                  'description' => 'bit map for distribution scope; 0 = everywhere; 1 = this server only; 2 = addressees; 4 = followers; null = default'),
index a45511fe54b60a1a02d87258cb61656e2f9d1010..945d1a74ca2cb6b757318af3266bb82561a02ed6 100644 (file)
@@ -233,16 +233,22 @@ class Profile extends Managed_DataObject
      *
      * @return mixed Notice or null
      */
-    function getCurrentNotice()
+    function getCurrentNotice(Profile $scoped=null)
     {
-        $notice = $this->getNotices(0, 1);
+        try {
+            $notice = $this->getNotices(0, 1, 0, 0, $scoped);
 
-        if ($notice->fetch()) {
-            if ($notice instanceof ArrayWrapper) {
-                // hack for things trying to work with single notices
-                return $notice->_items[0];
+            if ($notice->fetch()) {
+                if ($notice instanceof ArrayWrapper) {
+                    // hack for things trying to work with single notices
+                    // ...but this shouldn't happen anymore I think. Keeping it for safety...
+                    return $notice->_items[0];
+                }
+                return $notice;
             }
-            return $notice;
+        } catch (PrivateStreamException $e) {
+            // Maybe we should let this through if it's handled well upstream
+            return null;
         }
         
         return null;
@@ -682,25 +688,16 @@ class Profile extends Managed_DataObject
      */
     function getRequests($offset=0, $limit=null)
     {
-        $qry =
-          'SELECT profile.* ' .
-          'FROM profile JOIN subscription_queue '.
-          'ON profile.id = subscription_queue.subscriber ' .
-          'WHERE subscription_queue.subscribed = %d ' .
-          'ORDER BY subscription_queue.created DESC ';
-
-        if ($limit != null) {
-            if (common_config('db','type') == 'pgsql') {
-                $qry .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
-            } else {
-                $qry .= ' LIMIT ' . $offset . ', ' . $limit;
-            }
+        // FIXME: mysql only
+        $subqueue = new Profile();
+        $subqueue->joinAdd(array('id', 'subscription_queue:subscriber'));
+        $subqueue->whereAdd(sprintf('subscription_queue.subscribed = %d', $this->getID()));
+        $subqueue->limit($offset, $limit);
+        $subqueue->orderBy('subscription_queue.created', 'DESC');
+        if (!$subqueue->find()) {
+            throw new NoResultException($subqueue);
         }
-
-        $members = new Profile();
-
-        $members->query(sprintf($qry, $this->id));
-        return $members;
+        return $subqueue;
     }
 
     function subscriptionCount()
@@ -761,6 +758,36 @@ class Profile extends Managed_DataObject
         return Subscription::exists($this, $other);
     }
 
+    function readableBy(Profile $other=null)
+    {
+        // If it's not a private stream, it's readable by anyone
+        if (!$this->isPrivateStream()) {
+            return true;
+        }
+
+        // If it's a private stream, $other must be a subscriber to $this
+        return is_null($other) ? false : $other->isSubscribed($this);
+    }
+
+    function requiresSubscriptionApproval(Profile $other=null)
+    {
+        if (!$this->isLocal()) {
+            // We don't know for remote users, and we'll always be able to send
+            // the request. Whether it'll work immediately or require moderation
+            // can be determined in another function.
+            return false;
+        }
+
+        // Assume that profiles _we_ subscribe to are permitted. Could be made configurable.
+        if (!is_null($other) && $this->isSubscribed($other)) {
+            return false;
+        }
+
+        // If the local user either has a private stream (implies the following)
+        // or  user has a moderation policy for new subscriptions, return true.
+        return $this->getUser()->private_stream || $this->getUser()->subscribe_policy === User::SUBSCRIBE_POLICY_MODERATE;
+    }
+
     /**
      * Check if a pending subscription request is outstanding for this...
      *
@@ -920,41 +947,48 @@ class Profile extends Managed_DataObject
     function _deleteSubscriptions()
     {
         $sub = new Subscription();
-        $sub->subscriber = $this->id;
-
+        $sub->subscriber = $this->getID();
         $sub->find();
 
         while ($sub->fetch()) {
-            $other = Profile::getKV('id', $sub->subscribed);
-            if (empty($other)) {
-                continue;
-            }
-            if ($other->id == $this->id) {
-                continue;
+            try {
+                $other = $sub->getSubscribed();
+                if (!$other->sameAs($this)) {
+                    Subscription::cancel($this, $other);
+                }
+            } catch (NoResultException $e) {
+                // Profile not found
+                common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+            } catch (ServerException $e) {
+                // Subscription cancel failed
+                common_log(LOG_INFO, 'Subscribed profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...');
             }
-            Subscription::cancel($this, $other);
         }
 
-        $subd = new Subscription();
-        $subd->subscribed = $this->id;
-        $subd->find();
+        $sub = new Subscription();
+        $sub->subscribed = $this->getID();
+        $sub->find();
 
-        while ($subd->fetch()) {
-            $other = Profile::getKV('id', $subd->subscriber);
-            if (empty($other)) {
-                continue;
-            }
-            if ($other->id == $this->id) {
-                continue;
+        while ($sub->fetch()) {
+            try {
+                $other = $sub->getSubscriber();
+                common_log(LOG_INFO, 'Subscriber profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+                if (!$other->sameAs($this)) {
+                    Subscription::cancel($other, $this);
+                }
+            } catch (NoResultException $e) {
+                // Profile not found
+                common_log(LOG_INFO, 'Subscribed profile id=='.$sub->subscribed.' not found when deleting profile id=='.$this->getID().', ignoring...');
+            } catch (ServerException $e) {
+                // Subscription cancel failed
+                common_log(LOG_INFO, 'Subscriber profile id=='.$other->getID().' could not be reached for unsubscription notice when deleting profile id=='.$this->getID().', ignoring...');
             }
-            Subscription::cancel($other, $this);
         }
 
+        // Finally delete self-subscription
         $self = new Subscription();
-
-        $self->subscriber = $this->id;
-        $self->subscribed = $this->id;
-
+        $self->subscriber = $this->getID();
+        $self->subscribed = $this->getID();
         $self->delete();
     }
 
index f77472ae7050447d203c0767944d68fd123786f5..c532a3c3de67474b0ca7a638b4cdac939527bcd3 100644 (file)
@@ -92,8 +92,8 @@ class Subscription extends Managed_DataObject
         }
 
         if (Event::handle('StartSubscribe', array($subscriber, $other))) {
-            $otherUser = User::getKV('id', $other->id);
-            if ($otherUser instanceof User && $otherUser->subscribe_policy == User::SUBSCRIBE_POLICY_MODERATE && !$force) {
+            // unless subscription is forced, the user policy for subscription approvals is tested
+            if (!$force && $other->requiresSubscriptionApproval($subscriber)) {
                 try {
                     $sub = Subscription_queue::saveNew($subscriber, $other);
                     $sub->notify();
@@ -101,6 +101,7 @@ class Subscription extends Managed_DataObject
                     $sub = Subscription_queue::getSubQueue($subscriber, $other);
                 }
             } else {
+                $otherUser = User::getKV('id', $other->id);
                 $sub = self::saveNew($subscriber, $other);
                 $sub->notify();
 
index bb430c6f7849b8f0818b18933d190f1d7005aab9..76e4777deb5feb471dc69960ff1c1feee64c00f4 100644 (file)
@@ -380,32 +380,33 @@ class ActivityUtils
     }
 
     static function findLocalObject(array $uris, $type=ActivityObject::NOTE) {
-        $object = null;
-        // TODO: Extend this in plugins etc.
-        if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$object))) {
+        $obj_class = null;
+        // TODO: Extend this in plugins etc. and describe in EVENTS.txt
+        if (Event::handle('StartFindLocalActivityObject', array($uris, $type, &$obj_class))) {
             switch (self::resolveUri($type)) {
             case ActivityObject::PERSON:
                 // GROUP will also be here in due time...
-                $object = new Profile();
+                $obj_class = 'Profile';
                 break;
             default:
-                $object = new Notice();
+                $obj_class = 'Notice';
             }
         }
-        foreach (array_unique($uris) as $uri) {
+        $object = null;
+        $uris = array_unique($uris);
+        foreach ($uris as $uri) {
             try {
                 // the exception thrown will cancel before reaching $object
-                $object = call_user_func(array($object, 'fromUri'), $uri);
+                $object = call_user_func("{$obj_class}::fromUri", $uri);
                 break;
-            } catch (Exception $e) {
-                common_debug('Could not find local activity object from uri: '.$uri);
+            } catch (UnknownUriException $e) {
+                common_debug('Could not find local activity object from uri: '.$e->object_uri);
             }
         }
-        if (!empty($object)) {
-            Event::handle('EndFindLocalActivityObject', array($object->getUri(), $type, $object));
-        } else {
-            throw new ServerException('Could not find any activityobject stored locally with given URI');
+        if (!$object instanceof Managed_DataObject) {
+            throw new ServerException('Could not find any activityobject stored locally with given URIs: '.var_export($uris,true));
         }
+        Event::handle('EndFindLocalActivityObject', array($object->getUri(), $object->getObjectType(), $object));
         return $object;
     }
 
index 155d91ca7b3953072e6c7f80bffc0b0333340be2..9a574008aacf7bf9474c74d29f546061935d85f2 100644 (file)
@@ -651,6 +651,11 @@ class ApiAction extends Action
                 break;
             default:
                 if (strncmp($element, 'statusnet_', 10) == 0) {
+                    if ($element === 'statusnet_in_groups' && is_array($value)) {
+                        // QVITTERFIX because it would cause an array to be sent as $value
+                        // THIS IS UNDOCUMENTED AND SHOULD NEVER BE RELIED UPON (qvitter uses json output)
+                        $value = json_encode($value);
+                    }
                     $this->element('statusnet:'.substr($element, 10), null, $value);
                 } else {
                     $this->element($element, null, $value);
index 820f648d0fa728e2a37c26c52d53628d184386f9..c034effc0033bbe2443d7fd5b1adcb1d37b1804e 100644 (file)
@@ -2,7 +2,7 @@
 /**
  * StatusNet, the distributed open-source microblogging tool
  *
- * Form for leaving a group
+ * Form for approving or reject a pending subscription request
  *
  * PHP version 5
  *
  * @link      http://status.net/
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
-
-require_once INSTALLDIR.'/lib/form.php';
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
- * Form for leaving a group
+ * Form for approving or reject a pending subscription request
  *
  * @category Form
  * @package  StatusNet
@@ -107,8 +103,8 @@ class ApproveSubForm extends Form
     function formActions()
     {
         // TRANS: Submit button text to accept a subscription request on approve sub form.
-        $this->out->submit('approve', _m('BUTTON','Accept'));
+        $this->out->submit($this->id().'-approve', _m('BUTTON','Accept'), 'submit approve', 'approve');
         // TRANS: Submit button text to reject a subscription request on approve sub form.
-        $this->out->submit('cancel', _m('BUTTON','Reject'));
+        $this->out->submit($this->id().'-cancel', _m('BUTTON','Reject'), 'submit cancel', 'cancel');
     }
 }
diff --git a/lib/emptyidexception.php b/lib/emptyidexception.php
new file mode 100644 (file)
index 0000000..ddf781b
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+/**
+ * StatusNet, the distributed open-source microblogging tool
+ *
+ * Class for an exception when a database lookup returns no results
+ *
+ * PHP version 5
+ *
+ * LICENCE: 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  Exception
+ * @package   GNUsocial
+ * @author    Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2013 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3
+ * @link      http://www.gnu.org/software/social/
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class EmptyIdException extends ServerException
+{
+    public function __construct($called_class)
+    {
+        parent::__construct(sprintf(_('Empty ID value was given to query for a "%s" object'), $called_class));
+    }
+}
index 2ce4ed71f00f79bf04a094c8aac28424f76bf2a4..a159c662a527a46db102e6dcd2479d5dc13cbae4 100644 (file)
@@ -23,7 +23,7 @@ define('GNUSOCIAL_ENGINE', 'GNU social');
 define('GNUSOCIAL_ENGINE_URL', 'https://www.gnu.org/software/social/');
 
 define('GNUSOCIAL_BASE_VERSION', '1.2.0');
-define('GNUSOCIAL_LIFECYCLE', 'beta2'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
+define('GNUSOCIAL_LIFECYCLE', 'beta3'); // 'dev', 'alpha[0-9]+', 'beta[0-9]+', 'rc[0-9]+', 'release'
 
 define('GNUSOCIAL_VERSION', GNUSOCIAL_BASE_VERSION . '-' . GNUSOCIAL_LIFECYCLE);
 
index c47a62100fe58e4f307965a62435625e1fc68f40..05bef60ffc6c2998b1a0c1c82f24951ee4c036c8 100644 (file)
@@ -336,7 +336,8 @@ function get_all_languages() {
         'en-gb'   => array('q' => 1, 'lang' => 'en_GB', 'name' => 'English (British)', 'direction' => 'ltr'),
         'eo'      => array('q' => 0.8, 'lang' => 'eo',    'name' => 'Esperanto', 'direction' => 'ltr'),        
         'fi'      => array('q' => 1, 'lang' => 'fi', 'name' => 'Finnish', 'direction' => 'ltr'),
-        'fr-fr'   => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
+        'fr'      => array('q' => 1, 'lang' => 'fr', 'name' => 'French', 'direction' => 'ltr'),
+        'fr-fr'   => array('q' => 1, 'lang' => 'fr', 'name' => 'French (France)', 'direction' => 'ltr'),
         'fur'     => array('q' => 0.8, 'lang' => 'fur', 'name' => 'Friulian', 'direction' => 'ltr'),                       
         'gl'      => array('q' => 0.8, 'lang' => 'gl', 'name' => 'Galician', 'direction' => 'ltr'),
         'ka'      => array('q' => 0.8, 'lang' => 'ka',    'name' => 'Georgian', 'direction' => 'ltr'),
index 2d538c6e6a9aa6f249cef1878b901acc0993df61..eefde0d4a3fad5835fba8c525861d2b777c8784a 100644 (file)
@@ -158,6 +158,8 @@ class NoticeListItem extends Widget
                 $this->showParent();
             } catch (NoParentNoticeException $e) {
                 // no parent notice
+            } catch (InvalidUrlException $e) {
+                // parent had an invalid URL so we can't show it
             }
             if ($this->addressees) { $this->showAddressees(); }
             $this->elementEnd('div');
index bf09b637805b94c515debcdb311c2141dac0704c..fb592915a7cd3ac9defdbbc7ba519021b3e04a5a 100644 (file)
@@ -13,8 +13,13 @@ abstract class NoticestreamAction extends ProfileAction
         $this->doStreamPreparation();
 
         // fetch the actual stream stuff
-        $stream = $this->getStream();
-        $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        try {
+            $stream = $this->getStream();
+            $this->notice = $stream->getNotices(($this->page-1) * NOTICES_PER_PAGE, NOTICES_PER_PAGE + 1);
+        } catch (PrivateStreamException $e) {
+            $this->notice = new Notice();
+            $this->notice->whereAdd('FALSE');
+        }
 
         if ($this->page > 1 && $this->notice->N == 0) {
             // TRANS: Client error when page not found (404).
diff --git a/lib/privatestreamexception.php b/lib/privatestreamexception.php
new file mode 100644 (file)
index 0000000..ed9f3e0
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * An exception for private streams
+ *
+ * @category  Exception
+ * @package   GNUsocial
+ * @author    Mikael Nordfeldth <mmn@hethane.se>
+ * @copyright 2016 Free Software Foundation, Inc.
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
+ */
+
+class PrivateStreamException extends AuthorizationException
+{
+    var $owner = null;  // owner of the private stream
+    var $reader = null; // reader, may be null if not logged in
+
+    public function __construct(Profile $owner, Profile $reader=null)
+    {
+        $this->owner = $owner;
+        $this->reader = $reader;
+
+        // TRANS: Message when a private stream attemps to be read by unauthorized third party.
+        $msg = sprintf(_m('This stream is protected and only authorized subscribers may see its contents.'));
+
+        // If $reader is a profile, authentication has been made but still not accepted (403),
+        // otherwise authentication may give access to this resource (401).
+        parent::__construct($msg, ($reader instanceof Profile ? 403 : 401));
+    }
+}
index 8b2932f7247f7f546ca7df40a4a4d0675d21c568..9f735b1b88d8d02e61a8be4f91cb34fb01f2f0d4 100644 (file)
@@ -46,7 +46,7 @@ class ProfileList extends Widget
     /** Action object using us. */
     var $action = null;
 
-    function __construct($profile, $action=null)
+    function __construct($profile, HTMLOutputter $action=null)
     {
         parent::__construct($action);
 
@@ -93,9 +93,9 @@ class ProfileList extends Widget
         return $cnt;
     }
 
-    function newListItem(Profile $profile)
+    function newListItem(Profile $target)
     {
-        return new ProfileListItem($profile, $this->action);
+        return new ProfileListItem($target, $this->action);
     }
 
     function maxProfiles()
index 6fe7b99c7b3a967515815df5a3e1bde0c425fa04..e0e2838f58a3245525405035ad4b3f1eb291130b 100644 (file)
@@ -72,7 +72,7 @@ class ProfileListItem extends Widget
     function startItem()
     {
         $this->out->elementStart('li', array('class' => 'profile',
-                                             'id' => 'profile-' . $this->profile->id));
+                                             'id' => 'profile-' . $this->getTarget()->getID()));
     }
 
     function showProfile()
index 976c1819500d08a84dac8d52a48d36998846f7ee..807bed50a2f86d0c5e7a13994f5248d5dd937578 100644 (file)
@@ -48,9 +48,9 @@ class ProfileMiniList extends ProfileList
         $this->out->elementStart('ul', 'entities users xoxo');
     }
 
-    function newListItem(Profile $profile)
+    function newListItem(Profile $target)
     {
-        return new ProfileMiniListItem($profile, $this->action);
+        return new ProfileMiniListItem($target, $this->action);
     }
 
     function maxProfiles()
index 1fa795d32090a55866c49f9221757ab7c089fdcb..3c8de05855bb014eeade3f4f7708d137075be186 100644 (file)
@@ -74,7 +74,7 @@ class ProfileNoticeStream extends ScopingNoticeStream
     function getNotices($offset, $limit, $since_id=null, $max_id=null)
     {
         if ($this->impossibleStream()) {
-            return new ArrayWrapper(array());
+            throw new PrivateStreamException($this->streamProfile, $this->userProfile);
         } else {
             return parent::getNotices($offset, $limit, $since_id, $max_id);
         }
@@ -82,12 +82,8 @@ class ProfileNoticeStream extends ScopingNoticeStream
 
     function impossibleStream() 
     {
-        $user = User::getKV('id', $this->streamProfile->id);
-
-        // If it's a private stream, and no user or not a subscriber
-
-        if (!empty($user) && $user->private_stream && 
-            (empty($this->userProfile) || !$this->userProfile->isSubscribed($this->streamProfile))) {
+        if (!$this->streamProfile->readableBy($this->userProfile)) {
+            // cannot read because it's a private stream and either noone's logged in or they are not subscribers
             return true;
         }
 
diff --git a/lib/subqueuelist.php b/lib/subqueuelist.php
new file mode 100644 (file)
index 0000000..e4f7742
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class SubQueueList extends ProfileList
+{
+    public function newListItem(Profile $target)
+    {
+        return new SubQueueListItem($target, $this->action);
+    }
+}
diff --git a/lib/subqueuelistitem.php b/lib/subqueuelistitem.php
new file mode 100644 (file)
index 0000000..f426241
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class SubQueueListItem extends ProfileListItem
+{
+    public function showActions()
+    {
+        $this->startActions();
+        if (Event::handle('StartProfileListItemActionElements', array($this))) {
+            $this->showApproveButtons();
+            Event::handle('EndProfileListItemActionElements', array($this));
+        }
+        $this->endActions();
+    }
+
+    public function showApproveButtons()
+    {
+        $this->out->elementStart('li', 'entity_approval');
+        $form = new ApproveSubForm($this->out, $this->profile);
+        $form->show();
+        $this->out->elementEnd('li');
+    }
+}
index 0dc67cda637706b5cb47ebf4738db9e2f3a5ad43..7a2d9517302458d1b01c5e186e94d732dc79c9d1 100644 (file)
@@ -183,7 +183,7 @@ class ActivityPlugin extends Plugin
                                         'uri' => $uri,
                                         'verb' => ActivityVerb::UNFAVORITE,
                                         'object_type' => (($notice->verb == ActivityVerb::POST) ?
-                                                         $notice->object_type : ActivityObject::ACTIVITY)));
+                                                         $notice->object_type : null)));
 
         return true;
     }
index a1d0761a4a03ce9042c23eab590b811b765e17da..8470e5bafb7ba4004e331f0ac9792b60ad34f20b 100644 (file)
  * @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);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Event plugin
@@ -125,7 +121,11 @@ class EventPlugin extends MicroAppPlugin
     }
 
     function types() {
-        return array(Happening::OBJECT_TYPE,
+        return array(Happening::OBJECT_TYPE);
+    }
+
+    function verbs() {
+        return array(ActivityVerb::POST,
                      RSVP::POSITIVE,
                      RSVP::NEGATIVE,
                      RSVP::POSSIBLE);
@@ -189,8 +189,6 @@ class EventPlugin extends MicroAppPlugin
             $url = $url_object->item(0)->nodeValue;
         }
 
-        $notice = null;
-
         switch ($activity->verb) {
         case ActivityVerb::POST:
                // FIXME: get startTime, endTime, location and URL
@@ -205,6 +203,22 @@ class EventPlugin extends MicroAppPlugin
             break;
         case RSVP::POSITIVE:
         case RSVP::NEGATIVE:
+        case RSVP::POSSIBLE:
+            return Notice::saveActivity($activity, $actor, $options);
+            break;
+        default:
+            // TRANS: Exception thrown when event plugin comes across a undefined verb.
+            throw new Exception(_m('Unknown verb for events.'));
+        }
+    }
+
+    protected function saveObjectFromActivity(Activity $activity, Notice $stored, array $options=array())
+    {
+        $happeningObj = $activity->objects[0];
+
+        switch ($activity->verb) {
+        case RSVP::POSITIVE:
+        case RSVP::NEGATIVE:
         case RSVP::POSSIBLE:
             $happening = Happening::getKV('uri', $happeningObj->id);
             if (empty($happening)) {
@@ -212,14 +226,15 @@ class EventPlugin extends MicroAppPlugin
                 // TRANS: Exception thrown when trying to RSVP for an unknown event.
                 throw new Exception(_m('RSVP for unknown event.'));
             }
-            $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options);
+            $object = RSVP::saveNewFromNotice($stored, $happening, $activity->verb);
+            // Our data model expects this
+            $stored->object_type = $activity->verb;
+            return $object;
             break;
         default:
-            // TRANS: Exception thrown when event plugin comes across a undefined verb.
-            throw new Exception(_m('Unknown verb for events.'));
+            common_log(LOG_ERR, 'Unknown verb for events.');
+            return NULL;
         }
-
-        return $notice;
     }
 
     /**
index c4ffc3ee340bb74ba09be17eac457161f10167e7..0c39022b140433ccfba4a2cba5b940386a0f8bb4 100644 (file)
@@ -126,14 +126,37 @@ class RSVP extends Managed_DataObject
 
     function saveNew(Profile $profile, $event, $verb, array $options = array())
     {
-        if (array_key_exists('uri', $options)) {
-            $other = RSVP::getKV('uri', $options['uri']);
-            if (!empty($other)) {
-                // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
-                throw new ClientException(_m('RSVP already exists.'));
-            }
+        $eventNotice = $event->getNotice();
+        $options = array_merge(array('source' => 'web'), $options);
+
+        $act = new Activity();
+        $act->type    = ActivityObject::ACTIVITY;
+        $act->verb    = $verb;
+        $act->time    = $options['created'] ? strtotime($options['created']) : time();
+        $act->title   = _m("RSVP");
+        $act->actor   = $profile->asActivityObject();
+        $act->target  = $eventNotice->asActivityObject();
+        $act->objects = array(clone($act->target));
+        $act->content = RSVP::toHTML($profile, $event, self::codeFor($verb));
+
+        $act->id = common_local_url('showrsvp', array('id' => UUID::gen()));
+        $act->link = $act->id;
+
+        $saved = Notice::saveActivity($act, $profile, $options);
+
+        return $saved;
+    }
+
+    function saveNewFromNotice($notice, $event, $verb)
+    {
+        $other = RSVP::getKV('uri', $notice->uri);
+        if (!empty($other)) {
+            // TRANS: Client exception thrown when trying to save an already existing RSVP ("please respond").
+            throw new ClientException(_m('RSVP already exists.'));
         }
 
+        $profile = $notice->getProfile();
+
         try {
             $other = RSVP::getByKeys( [ 'profile_id' => $profile->getID(),
                                         'event_uri' => $event->getUri(),
@@ -146,54 +169,19 @@ class RSVP extends Managed_DataObject
 
         $rsvp = new RSVP();
 
-        $rsvp->id          = UUID::gen();
-        $rsvp->profile_id  = $profile->getID();
-        $rsvp->event_uri    = $event->getUri();
-        $rsvp->response      = self::codeFor($verb);
-
-        if (array_key_exists('created', $options)) {
-            $rsvp->created = $options['created'];
-        } else {
-            $rsvp->created = common_sql_now();
-        }
-
-        if (array_key_exists('uri', $options)) {
-            $rsvp->uri = $options['uri'];
-        } else {
-            $rsvp->uri = common_local_url('showrsvp',
-                                        array('id' => $rsvp->id));
-        }
+        preg_match('/\/([^\/]+)\/*/', $notice->uri, $match);
+        $rsvp->id          = $match[1] ? $match[1] : UUID::gen();
+        $rsvp->profile_id  = $profile->id;
+        $rsvp->event_id    = $event->id;
+        $rsvp->response    = self::codeFor($verb);
+        $rsvp->created     = $notice->created;
+        $rsvp->uri         = $notice->uri;
 
         $rsvp->insert();
 
         self::blow('rsvp:for-event:%s', $event->getUri());
 
-        // XXX: come up with something sexier
-
-        $content = $rsvp->asString();
-
-        $rendered = $rsvp->asHTML();
-
-        $options = array_merge(array('object_type' => $verb),
-                               $options);
-
-        if (!array_key_exists('uri', $options)) {
-            $options['uri'] = $rsvp->uri;
-        }
-
-        $eventNotice = $event->getNotice();
-
-        if (!empty($eventNotice)) {
-            $options['reply_to'] = $eventNotice->getID();
-        }
-
-        $saved = Notice::saveNew($profile->getID(),
-                                 $content,
-                                 array_key_exists('source', $options) ?
-                                 $options['source'] : 'web',
-                                 $options);
-
-        return $saved;
+        return $rsvp;
     }
 
     function codeFor($verb)
index b10a5f06ac9525cef1798250fce92fa6adcc8178..aa1e2e339a904170b5d96547d6777004f678baa1 100644 (file)
@@ -185,16 +185,10 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin
     {
         assert($this->isMyActivity($act));
 
-        // If empty, we should've created it ourselves on our node.
-        if (!isset($options['created'])) {
-            $options['created'] = !empty($act->time) ? common_sql_date($act->time) : common_sql_now();
-        }
-
         // We must have an objects[0] here because in isMyActivity we require the count to be == 1
         $actobj = $act->objects[0];
 
         $object = Fave::saveActivityObject($actobj, $stored);
-        $stored->object_type = $object->getObjectType();
 
         return $object;
     }
index eca4412b23068c0d5a114203269d77bd29c26bce..bad8d42b8c5e747d4434b762b77208574a7349ab 100644 (file)
@@ -114,24 +114,41 @@ class Fave extends Managed_DataObject
 
     public function delete($useWhere=false)
     {
-        $profile = Profile::getKV('id', $this->user_id);
-        $notice  = Notice::getKV('id', $this->notice_id);
-
         $result = null;
 
-        if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
+        try {
+            $profile = $this->getActor();
+            $notice  = $this->getTarget();
 
-            $result = parent::delete($useWhere);
+            if (Event::handle('StartDisfavorNotice', array($profile, $notice, &$result))) {
 
-            self::blowCacheForProfileId($this->user_id);
-            self::blowCacheForNoticeId($this->notice_id);
-            self::blow('popular');
+                $result = parent::delete($useWhere);
 
-            if ($result) {
-                Event::handle('EndDisfavorNotice', array($profile, $notice));
+                if ($result !== false) {
+                    Event::handle('EndDisfavorNotice', array($profile, $notice));
+                }
             }
+
+        } catch (NoResultException $e) {
+            // In case there's some inconsistency where the profile or notice was deleted without losing the fave db entry
+            common_log(LOG_INFO, '"'.get_class($e->obj).'" with id=='.var_export($e->obj->id, true).' object not found when deleting favorite, ignoring...');
+        } catch (EmptyIdException $e) {
+            // Some buggy instances of GNU social have had favroites with notice id==0 stored in the database
+            common_log(LOG_INFO, '"'.get_class($e->obj).'"object had empty id deleting favorite, ignoring...');
+        }
+
+        // If we catch an exception above, then $result===null because parent::delete only returns an int>=0 or boolean false
+        if (is_null($result)) {
+            // Delete it without the event, as something is wrong and we don't want it anyway.
+            $result = parent::delete($useWhere);
         }
 
+        // Err, apparently we can reference $this->user_id after parent::delete,
+        // I guess it's safe because this is the order it was before!
+        self::blowCacheForProfileId($this->user_id);
+        self::blowCacheForNoticeId($this->notice_id);
+        self::blow('popular');
+
         return $result;
     }
 
@@ -391,14 +408,7 @@ class Fave extends Managed_DataObject
 
     public function getTarget()
     {
-        // throws exception on failure
-        $target = new Notice();
-        $target->id = $this->notice_id;
-        if (!$target->find(true)) {
-            throw new NoResultException($target);
-        }
-
-        return $target;
+        return Notice::getByID($this->notice_id);
     }
 
     public function getTargetObject()
index b0c088e55d50872fb10a516a2e6a6229c0a2e574..75c75c54c6e64667e51965d845f328edaf458311 100644 (file)
@@ -193,31 +193,31 @@ class OStatusSubAction extends Action
         $hasFN = ($fullname !== '') ? 'nickname' : 'fn nickname entity_nickname';
         $this->elementStart('a', array('href' => $profile,
                                        'class' => 'url '.$hasFN));
-        $this->raw($nickname);
+        $this->text($nickname);
         $this->elementEnd('a');
 
         if (!is_null($fullname)) {
             $this->elementStart('div', 'fn entity_fn');
-            $this->raw($fullname);
+            $this->text($fullname);
             $this->elementEnd('div');
         }
 
         if (!is_null($location)) {
             $this->elementStart('div', 'label entity_location');
-            $this->raw($location);
+            $this->text($location);
             $this->elementEnd('div');
         }
 
         if (!is_null($homepage)) {
             $this->elementStart('a', array('href' => $homepage,
                                            'class' => 'url entity_url'));
-            $this->raw($homepage);
+            $this->text($homepage);
             $this->elementEnd('a');
         }
 
         if (!is_null($note)) {
             $this->elementStart('div', 'note entity_note');
-            $this->raw($note);
+            $this->text($note);
             $this->elementEnd('div');
         }
         $this->elementEnd('div');
index 84174a0440895d597e657d99f014b04bf66e573b..d1aece5c3df49d5f694bad85a85212e63fc2362d 100644 (file)
@@ -128,7 +128,6 @@ class SharePlugin extends ActivityVerbHandlerPlugin
         // Notice::saveActivity it will update the Notice object.
         $stored->repeat_of = $sharedNotice->getID();
         $stored->conversation = $sharedNotice->conversation;
-        $stored->object_type = ActivityUtils::resolveUri(ActivityObject::ACTIVITY, true);
 
         // We don't have to save a repeat in a separate table, we can
         // find repeats by just looking at the notice.repeat_of field.
index cfcf51683ead1a4dd5e68a6e026c3e0dd41b297b..234bd9c9130b5519435e7542138b813b54eb6f24 100644 (file)
@@ -1611,12 +1611,13 @@ ul.profile_list li {
     margin-bottom: 20px;
 }
 
+ul.profiles.groups {
+    list-style-type:none;
+}
+
 .profile_list .h-card .u-photo {
     margin-right: 4px;
 }
-.profile_list .h-card .p-nickname {
-    display: block;
-}
 
 table.profile_list tbody tr:nth-child(2n+1) {
     background-color: #fafafa !important;
@@ -1629,14 +1630,17 @@ margin-left:0;
 }
 
 .entity_profile .p-nickname {
-    font-size:1.4em;
+    font-weight: bold;
 }
 
 .entity_profile .p-name {
-    font-size: 1.2em;
     clear: left;
 }
 
+.entity_profile .label {
+    display: block;
+}
+
 .entity_profile .p-name:before {
 content: "(";
 font-weight:normal;
@@ -1707,7 +1711,7 @@ display:block;
     padding: 4px 4px 4px 26px;
 }
 
-.entity_actions a, .entity_actions p, .entity_actions .entity_subscribe input, .entity_actions .entity_block input, .entity_actions .entity_moderation input, .entity_actions .entity_role input, .entity_actions .entity_nudge input, .entity_actions .entity_delete input, .entity_actions input.submit {
+.entity_actions a, .entity_actions p, .entity_actions .entity_approval input, .entity_actions .entity_subscribe input, .entity_actions .entity_block input, .entity_actions .entity_moderation input, .entity_actions .entity_role input, .entity_actions .entity_nudge input, .entity_actions .entity_delete input, .entity_actions input.submit {
     background-color: #ccc !important;
     border: none;
 }
@@ -1716,7 +1720,7 @@ display:block;
     padding: 2px 4px 4px 28px;
 }
 
-.entity_actions a:hover, .entity_actions p:hover, .entity_actions .entity_subscribe input:hover, .entity_actions .entity_block input:hover, .entity_actions .entity_moderation input:hover, .entity_actions .entity_role input:hover, .entity_actions .entity_nudge input:hover, .entity_actions .entity_delete input:hover, .entity_actions input.submit:hover {
+.entity_actions a:hover, .entity_actions p:hover, .entity_actions, .entity_subscribe input:hover, .entity_actions .entity_subscribe input:hover, .entity_actions .entity_block input:hover, .entity_actions .entity_moderation input:hover, .entity_actions .entity_role input:hover, .entity_actions .entity_nudge input:hover, .entity_actions .entity_delete input:hover, .entity_actions input.submit:hover {
     background-color: #f2f2f2 !important;
 }
 
@@ -1882,7 +1886,7 @@ left:3px;
 }
 
 .profile .entity_profile {
-margin-bottom:0;
+margin-bottom:10px;
 min-height:60px;
 }
 
@@ -2252,6 +2256,7 @@ button.minimize,
 .entity_clear input.submit,
 .entity_flag input.submit,
 .entity_flag p,
+.entity_approval input.submit,
 .entity_subscribe input.submit,
 #realtime_play,
 #realtime_pause,
@@ -2373,9 +2378,11 @@ background-position: 5px -2039px;
 .entity_flag p {
 background-position: 5px -2105px;
 }
+.entity_approval input.approve,
 .entity_subscribe input.accept {
 background-position: 5px -2171px;
 }
+.entity_approval input.cancel,
 .entity_subscribe input.reject {
 background-position: 5px -2237px;
 }
index db814aa28e6d808b3c3a7622222181dd6306cb6f..334177164e438dc6b32fb45d8ac325868ded3079 100644 (file)
@@ -1158,11 +1158,6 @@ td.entity_profile {
     font-size: 1.4em;
 }
 
-.profile_list .label {
-    display: block;
-    margin-left: 59px !important;
-}
-
 .profile_list .note {
     font-size: 0.88em;
     line-height: 1.36em;
index 967b6d62ffbd63df9abf282b23492958c59c9699..0500395514ccb081cbc70d6325dc2c1a291b2a3e 100644 (file)
@@ -585,24 +585,39 @@ ul.profile_list li {
     margin-right: 10px;
 }
 
-.profile .entity_profile .p-name,
-.profile .entity_profile .p-locality,
-.profile .entity_profile .role,
-.profile .entity_profile > span,
-.profile .entity_profile .u-url[rel~="contact"] {
+
+/* these apply to both profiles and groups */
+.entity_profile .p-name,
+.entity_profile .p-locality,
+.entity_profile .role,
+.entity_profile > span,
+.entity_profile .u-url[rel~="contact"] {
     display: inline;
-    margin-left: 0;
-    font-size:0.88em;
+    font-size:1.0em;
     color:#9197a3;
 }
 
-.entity_profile .p-name:before,
+.entity_profile .p-nickname {
+    color:#666 !important;
+    display: block !important;
+}
+
+.entity_profile .label {
+    display: inline !important;
+}
+
 .entity_profile .p-name:after {
     content: "";
 }
 
 .profile .entity_profile .u-url {
-    font-size:0.88em;
+    font-size:1.0em;
+}
+
+/* clear parethesis from base theme */
+.entity_profile .p-name:before,
+.entity_profile .p-name:after {
+    content: "";
 }
 
 .entity_profile .role:before {
@@ -613,6 +628,7 @@ ul.profile_list li {
     content: ")";
 }
 
+.entity_profile .label:before,
 .profile .entity_profile .p-locality:before {
     content:" ยท ";
 }