]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge remote-tracking branch 'upstream/master' into social-master
authorRoland Haeder <roland@mxchange.org>
Tue, 10 Nov 2015 18:05:53 +0000 (19:05 +0100)
committerRoland Haeder <roland@mxchange.org>
Tue, 10 Nov 2015 18:05:53 +0000 (19:05 +0100)
Signed-off-by: Roland Haeder <roland@mxchange.org>
28 files changed:
.gitignore
UPGRADE
actions/doc.php
classes/Deleted_notice.php
classes/Notice.php
classes/Profile.php
lib/activity.php
lib/activityhandlerplugin.php
lib/activityobject.php
lib/activitystreamjsondocument.php
lib/activitystreamslink.php [new file with mode: 0644]
lib/activitystreamsmedialink.php [new file with mode: 0644]
lib/activityutils.php
lib/activityverb.php
lib/apiaction.php
lib/jsonactivitycollection.php [new file with mode: 0644]
lib/noticelistitem.php
plugins/Favorite/FavoritePlugin.php
plugins/Favorite/classes/Fave.php
plugins/Linkback/LinkbackPlugin.php
plugins/Linkback/actions/linkbacksettings.php [new file with mode: 0644]
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/classes/FeedSub.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/OStatus/lib/pushrenewqueuehandler.php [new file with mode: 0644]
plugins/Share/SharePlugin.php
plugins/WebFinger/WebFingerPlugin.php
theme/base/css/display.css

index b9289b7c2af432167ff68d572b7aad8610965f3e..51ac39e6f29f103367ad7ef80db8d1f8a7b28ed8 100644 (file)
@@ -1,36 +1,11 @@
-avatar/*
-background/*
-files/*
-file/*
-local/*
-_darcs/*
-logs/*
-log/*
-run/*
-config.php
-.htaccess
-httpd.conf
-*.tmproj
-dataobject.ini
-*~
-*.bak
-*.orig
-*.rej
-.#*
-*.swp
-.buildpath
-.project
-.settings
-TODO.rym
-config-*.php
-good-config.php
-lac08.log
-php.log
-.DS_Store
-nbproject
-*.mo
-*log*
-htaccess-sample
-installer.txt
-extlib/DB.php
-.gitmodules
+/nbproject/private/
+/nbproject/*~
+/manifest.mf
+/build/
+/dist/
+/data/*
+/*.properties
+/*-ejb/nbproject/private/
+/*-ejb/nbproject/*~
+/*-ejb/build/
+/*-ejb/dist/
diff --git a/UPGRADE b/UPGRADE
index d2cd365e55a9deb1e4bce5ed1acd339543994d8c..3a1dd8a9b9a3b324ababe0256476790c57992b29 100644 (file)
--- a/UPGRADE
+++ b/UPGRADE
@@ -27,13 +27,13 @@ and follow this procedure:
    The upgrade script will likely take a long time because it will
     upgrade the tables to another character encoding and make other
     automated upgrades. Make sure it ends without errors. If you get
-    errors, create a new task on https://bugz.foocorp.net/
+    errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
 
 4. Start your queue daemons again (you can run this command even if you
     do not use the queue daemons):
     $ bash scripts/startdaemons.sh
 
-5. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
+5. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
 
 If you are using ssh keys to log in to your server, you can make this
 procedure pretty painless (assuming you have automated backups already).
@@ -69,7 +69,7 @@ variant of this command (you will be prompted for the database password):
 
 2. Unpack your GNU social code to a fresh directory. You can do this
     by cloning our git repository:
-    $ git clone https://gitorious.org/social/mainline.git gnusocial
+    $ git clone https://git.gnu.io/gnu/gnu-social.git gnusocial
 
 3. Synchronize your local files to the GNU social directory. These 
     will be the local files such as avatars, config and files:
@@ -91,8 +91,8 @@ variant of this command (you will be prompted for the database password):
    The upgrade script will likely take a long time because it will
     upgrade the tables to another character encoding and make other
     automated upgrades. Make sure it ends without errors. If you get
-    errors, create a new task on https://bugz.foocorp.net/
+    errors, create a new task on https://git.gnu.io/gnu/gnu-social/issues
 
 6. Start your queue daemons: 'bash scripts/startdaemons.sh'
 
-7. Report any issues at https://bugz.foocorp.net/ (tag GNU social)
+7. Report any issues at https://git.gnu.io/gnu/gnu-social/issues
index d59c63631a4c02049f8b29ae4042c4ee37110b65..d897b4e58c5131d452066c57c52a58a456847bd9 100644 (file)
@@ -28,9 +28,7 @@
  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('STATUSNET') && !defined('LACONICA')) {
-    exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Documentation class.
@@ -42,16 +40,14 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
  * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
  * @link     http://status.net/
  */
-class DocAction extends Action
+class DocAction extends ManagedAction
 {
     var $output   = null;
     var $filename = null;
     var $title    = null;
 
-    function prepare(array $args=array())
+    protected function doPreparation()
     {
-        parent::prepare($args);
-
         $this->title  = $this->trimmed('title');
         if (!preg_match('/^[a-zA-Z0-9_-]*$/', $this->title)) {
             $this->title = 'help';
@@ -59,52 +55,11 @@ class DocAction extends Action
         $this->output = null;
 
         $this->loadDoc();
-        return true;
-    }
-
-    /**
-     * Handle a request
-     *
-     * @param array $args array of arguments
-     *
-     * @return nothing
-     */
-    function handle(array $args=array())
-    {
-        parent::handle($args);
-        $this->showPage();
-    }
-
-    /**
-     * Page title
-     *
-     * Gives the page title of the document. Override default for hAtom entry.
-     *
-     * @return void
-     */
-    function showPageTitle()
-    {
-        $this->element('h1', array('class' => 'entry-title'), $this->title());
     }
 
-    /**
-     * Block for content.
-     *
-     * Overrides default from Action to wrap everything in an hAtom entry.
-     *
-     * @return void.
-     */
-    function showContentBlock()
+    public function title()
     {
-        $this->elementStart('div', array('id' => 'content', 'class' => 'h-entry'));
-        $this->showPageTitle();
-        $this->showPageNoticeBlock();
-        $this->elementStart('div', array('id' => 'content_inner',
-                                         'class' => 'e-content'));
-        // show the actual content (forms, lists, whatever)
-        $this->showContent();
-        $this->elementEnd('div');
-        $this->elementEnd('div');
+        return ucfirst($this->title);
     }
 
     /**
@@ -119,16 +74,9 @@ class DocAction extends Action
         $this->raw($this->output);
     }
 
-    /**
-     * Page title.
-     *
-     * Uses the title of the document.
-     *
-     * @return page title
-     */
-    function title()
+    function showNoticeForm()
     {
-        return ucfirst($this->title);
+        // no notice form
     }
 
     /**
index a9167f19a48ee2c5069123567562dfcc13d1e70f..23bbea1bab4525279740bdd6b679e31c23f68352 100644 (file)
  * along with this program.     If not, see <http://www.gnu.org/licenses/>.
  */
 
-if (!defined('STATUSNET')) {
-    exit(1);
-}
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
- * Table Definition for notice
+ * Table Definition for deleted_notice
  */
-require_once INSTALLDIR.'/classes/Memcached_DataObject.php';
 
 class Deleted_notice extends Managed_DataObject
 {
-    ###START_AUTOCODE
-    /* the code below is auto generated do not remove the above tag */
-
-    public $__table = 'deleted_notice';                  // table name
+    public $__table = 'deleted_notice';      // table name
     public $id;                              // int(4)  primary_key not_null
     public $profile_id;                      // int(4)   not_null
     public $uri;                             // varchar(191)  unique_key   not 255 because utf8mb4 takes more space
     public $created;                         // datetime()   not_null
     public $deleted;                         // datetime()   not_null
 
-    /* the code above is auto generated do not remove the tag below */
-    ###END_AUTOCODE
-
     public static function schemaDef()
     {
         return array(
index 0fe1296bc3f8624573941e4219b29b8d26a440f2..37310af8ef638190df12783aa65bccf52c93b5a9 100644 (file)
@@ -158,6 +158,14 @@ class Notice extends Managed_DataObject
         $this->_profile[$this->profile_id] = $profile;
     }
 
+    public function deleteAs(Profile $actor)
+    {
+        if ($this->getProfile()->sameAs($actor) || $actor->hasRight(Right::DELETEOTHERSNOTICE)) {
+            return $this->delete();
+        }
+        throw new AuthorizationException('You are not allowed to delete other user\'s notices');
+    }
+
     function delete($useWhere=false)
     {
         // For auditing purposes, save a record that the notice
index 09f9ca71d109933db6509cb883ec1bee866d222c..5ef77a9506fb5c48b63ba966f2bd69b3c05be11b 100644 (file)
@@ -144,7 +144,7 @@ class Profile extends Managed_DataObject
     public function hasPassword()
     {
         try {
-            return !empty($this->getUser()->hasPassword());
+            return $this->getUser()->hasPassword();
         } catch (NoSuchUserException $e) {
             return false;
         }
index 6b3ccf3519dd496b98b95dafe6107eeea1772bb5..49e7fdbcd4020c57ad2cd3c362156cc7e368a126 100644 (file)
@@ -245,7 +245,7 @@ class Activity
 
         if (!empty($targetEl)) {
             $this->target = new ActivityObject($targetEl);
-        } elseif (ActivityUtils::compareTypes($this->verb, array(ActivityVerb::FAVORITE))) {
+        } elseif (ActivityUtils::compareVerbs($this->verb, array(ActivityVerb::FAVORITE))) {
             // StatusNet didn't send a 'target' for their Favorite atom entries
             $this->target = clone($this->objects[0]);
         }
index 529749cc1ddc46b32373a6f12b65744db19657c2..6639e2782280903094d56078fd1ad71148bff6ac 100644 (file)
@@ -104,7 +104,7 @@ abstract class ActivityHandlerPlugin extends Plugin
 
     function isMyVerb($verb) {
         $verb = $verb ?: ActivityVerb::POST;    // post is the default verb
-        return ActivityUtils::compareTypes($verb, $this->verbs());
+        return ActivityUtils::compareVerbs($verb, $this->verbs());
     }
 
     function isMyType($type) {
@@ -389,7 +389,7 @@ abstract class ActivityHandlerPlugin extends Plugin
         } elseif ($target instanceof Profile && $target->isLocal()) {
             $original = null;
             // FIXME: Shouldn't favorites show up with a 'target' activityobject?
-            if (!ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) {
+            if (!ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST)) && isset($activity->objects[0])) {
                 // If this is not a post, it's a verb targeted at something (such as a Favorite attached to a note)
                 if (!empty($activity->objects[0]->id)) {
                     $activity->context->replyToID = $activity->objects[0]->id;
@@ -413,7 +413,7 @@ abstract class ActivityHandlerPlugin extends Plugin
         $actor = $oactor->localProfile();
 
         // FIXME: will this work in all cases? I made it work for Favorite...
-        if (ActivityUtils::compareTypes($activity->verb, array(ActivityVerb::POST))) {
+        if (ActivityUtils::compareVerbs($activity->verb, array(ActivityVerb::POST))) {
             $object = $activity->objects[0];
         } else {
             $object = $activity;
index 2fe52eefcc5a84f883757f9aba8818446d1a657b..87eea13727e3261c8829a8459dad19b42113caab 100644 (file)
@@ -298,7 +298,7 @@ class ActivityObject
         if (!empty($guidEl)) {
             $this->id = $guidEl->textContent;
 
-            if ($guidEl->hasAttribute('isPermaLink')) {
+            if ($guidEl->hasAttribute('isPermaLink') && $guidEl->getAttribute('isPermaLink') != 'false') {
                 // overwrites <link>
                 $this->link = $this->id;
             }
index 0466045fef46c059a7e9a65c6bf2a4d47552e88e..12c3882c25d444b299293f27544294762719010d 100644 (file)
@@ -175,121 +175,3 @@ class ActivityStreamJSONDocument extends JSONActivityCollection
     }
 
 }
-
-/**
- * A class for representing MediaLinks in JSON Activities
- *
- * @category Feed
- * @package  StatusNet
- * @author   Zach Copley <zach@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 ActivityStreamsMediaLink extends ActivityStreamsLink
-{
-    private $linkDict;
-
-    function __construct(
-        $url       = null,
-        $width     = null,
-        $height    = null,
-        $mediaType = null, // extension
-        $rel       = null, // extension
-        $duration  = null
-    )
-    {
-        parent::__construct($url, $rel, $mediaType);
-        $this->linkDict = array(
-            'width'      => intval($width),
-            'height'     => intval($height),
-            'duration'   => intval($duration)
-        );
-    }
-
-    function asArray()
-    {
-        return array_merge(
-            parent::asArray(),
-            array_filter($this->linkDict)
-        );
-    }
-}
-
-/*
- * Collection primarily as the root of an Activity Streams doc but can be used as the value
- * of extension properties in a variety of situations.
- *
- * A valid Collection object serialization MUST contain at least the url or items properties.
- */
-class JSONActivityCollection {
-
-    /* Non-negative integer specifying the total number of activities within the stream */
-    protected $totalItems;
-
-    /* An array containing a listing of Objects of any object type */
-    protected $items;
-
-    /* IRI referencing a JSON document containing the full listing of objects in the collection */
-    protected $url;
-
-    /**
-     * Constructor
-     *
-     * @param array  $items       array of activity items
-     * @param string $url         url of a doc list all the objs in the collection
-     * @param int    $totalItems  total number of items in the collection
-     */
-    function __construct($items = null, $url = null)
-    {
-        $this->items      = empty($items) ? array() : $items;
-        $this->totalItems = count($items);
-        $this->url        = $url;
-    }
-
-    /**
-     * Get the total number of items in the collection
-     *
-     * @return int total the total
-     */
-    public function getTotalItems()
-    {
-        $this->totalItems = count($items);
-        return $this->totalItems;
-    }
-}
-
-/**
- * A class for representing links in JSON Activities
- *
- * @category Feed
- * @package  StatusNet
- * @author   Zach Copley <zach@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 ActivityStreamsLink
-{
-    private $linkDict;
-
-    function __construct($url = null, $rel = null, $mediaType = null)
-    {
-        // links MUST have a URL
-        if (empty($url)) {
-            throw new Exception('Links must have a URL.');
-        }
-
-        $this->linkDict = array(
-            'url'   => $url,
-            'rel'   => $rel,      // extension
-            'type'  => $mediaType // extension
-        );
-    }
-
-    function asArray()
-    {
-        return array_filter($this->linkDict);
-    }
-}
-
diff --git a/lib/activitystreamslink.php b/lib/activitystreamslink.php
new file mode 100644 (file)
index 0000000..2c91ded
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * A class for representing links in JSON Activities
+ *
+ * @category Feed
+ * @package  StatusNet
+ * @author   Zach Copley <zach@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 ActivityStreamsLink
+{
+    private $linkDict;
+
+    function __construct($url = null, $rel = null, $mediaType = null)
+    {
+        // links MUST have a URL
+        if (empty($url)) {
+            throw new Exception('Links must have a URL.');
+        }
+
+        $this->linkDict = array(
+            'url'   => $url,
+            'rel'   => $rel,      // extension
+            'type'  => $mediaType // extension
+        );
+    }
+
+    function asArray()
+    {
+        return array_filter($this->linkDict);
+    }
+}
diff --git a/lib/activitystreamsmedialink.php b/lib/activitystreamsmedialink.php
new file mode 100644 (file)
index 0000000..c8612af
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * A class for representing MediaLinks in JSON Activities
+ *
+ * @category Feed
+ * @package  StatusNet
+ * @author   Zach Copley <zach@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 ActivityStreamsMediaLink extends ActivityStreamsLink
+{
+    private $linkDict;
+
+    function __construct(
+        $url       = null,
+        $width     = null,
+        $height    = null,
+        $mediaType = null, // extension
+        $rel       = null, // extension
+        $duration  = null
+    )
+    {
+        parent::__construct($url, $rel, $mediaType);
+        $this->linkDict = array(
+            'width'      => intval($width),
+            'height'     => intval($height),
+            'duration'   => intval($duration)
+        );
+    }
+
+    function asArray()
+    {
+        return array_merge(
+            parent::asArray(),
+            array_filter($this->linkDict)
+        );
+    }
+}
index ac7eb9421334cee2b508aca2bb827fb2cc1faf17..bb430c6f7849b8f0818b18933d190f1d7005aab9 100644 (file)
@@ -348,7 +348,7 @@ class ActivityUtils
         return null;
     }
 
-    static function compareTypes($type, array $objects)    // this does verbs too!
+    static function compareTypes($type, array $objects)
     {
         $type = self::resolveUri($type);
         foreach ($objects as $object) {
@@ -359,6 +359,11 @@ class ActivityUtils
         return false;
     }
 
+    static function compareVerbs($type, $objects)
+    {
+        return self::compareTypes($type, $objects);
+    }
+
     static function resolveUri($uri, $make_relative=false)
     {
         if (empty($uri)) {
index dc6f9c93f4719b50b733c021ff1b83ed08613084..187962d617e6f442bee041fb60258a5390ff8ac4 100644 (file)
@@ -54,9 +54,9 @@ class ActivityVerb
     const FRIEND   = 'http://activitystrea.ms/schema/1.0/make-friend';
     const JOIN     = 'http://activitystrea.ms/schema/1.0/join';
     const TAG      = 'http://activitystrea.ms/schema/1.0/tag';
+    const DELETE   = 'delete';  // the url part is not used anymore, and this feature is new enough to avoid problems with legacy nodes if used without http://...
 
     // Custom OStatus verbs for the flipside until they're standardized
-    const DELETE     = 'http://ostatus.org/schema/1.0/unfollow';
     const UNFAVORITE = 'http://activitystrea.ms/schema/1.0/unfavorite';
     const UNLIKE     = 'http://activitystrea.ms/schema/1.0/unlike'; // This is a synonym of unfavorite
     const UNFOLLOW   = 'http://ostatus.org/schema/1.0/unfollow';
index fae8f33d0e0707150612f70c7e2be0cb56d30893..3564709e5bc2c0242c14af8fb84a06effaf9b72e 100644 (file)
@@ -795,7 +795,12 @@ class ApiAction extends Action
                                               'xmlns:statusnet' => 'http://status.net/schema/api/1/'));
 
         if (is_array($notice)) {
-            $notice = new ArrayWrapper($notice);
+            //FIXME: make everything calling showJsonTimeline use only Notice objects
+            $ids = array();
+            foreach ($notice as $n) {
+                $ids[] = $n->getID();
+            }
+            $notice = Notice::multiGet('id', $ids);
         }
 
         while ($notice->fetch()) {
@@ -851,7 +856,12 @@ class ApiAction extends Action
         $this->element('ttl', null, '40');
 
         if (is_array($notice)) {
-            $notice = new ArrayWrapper($notice);
+            //FIXME: make everything calling showJsonTimeline use only Notice objects
+            $ids = array();
+            foreach ($notice as $n) {
+                $ids[] = $n->getID();
+            }
+            $notice = Notice::multiGet('id', $ids);
         }
 
         while ($notice->fetch()) {
@@ -895,7 +905,12 @@ class ApiAction extends Action
         $this->element('subtitle', null, $subtitle);
 
         if (is_array($notice)) {
-            $notice = new ArrayWrapper($notice);
+            //FIXME: make everything calling showJsonTimeline use only Notice objects
+            $ids = array();
+            foreach ($notice as $n) {
+                $ids[] = $n->getID();
+            }
+            $notice = Notice::multiGet('id', $ids);
         }
 
         while ($notice->fetch()) {
@@ -1000,7 +1015,6 @@ class ApiAction extends Action
 
         if (is_array($notice)) {
             //FIXME: make everything calling showJsonTimeline use only Notice objects
-            common_debug('ArrayWrapper avoidance in progress! Beep boop, make showJsonTimeline only receive Notice objects!');
             $ids = array();
             foreach ($notice as $n) {
                 $ids[] = $n->getID();
diff --git a/lib/jsonactivitycollection.php b/lib/jsonactivitycollection.php
new file mode 100644 (file)
index 0000000..1d1b482
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+
+/*
+ * Collection primarily as the root of an Activity Streams doc but can be used as the value
+ * of extension properties in a variety of situations.
+ *
+ * A valid Collection object serialization MUST contain at least the url or items properties.
+ */
+class JSONActivityCollection {
+
+    /* Non-negative integer specifying the total number of activities within the stream */
+    protected $totalItems;
+
+    /* An array containing a listing of Objects of any object type */
+    protected $items;
+
+    /* IRI referencing a JSON document containing the full listing of objects in the collection */
+    protected $url;
+
+    /**
+     * Constructor
+     *
+     * @param array  $items       array of activity items
+     * @param string $url         url of a doc list all the objs in the collection
+     * @param int    $totalItems  total number of items in the collection
+     */
+    function __construct($items = null, $url = null)
+    {
+        $this->items      = empty($items) ? array() : $items;
+        $this->totalItems = count($items);
+        $this->url        = $url;
+    }
+
+    /**
+     * Get the total number of items in the collection
+     *
+     * @return int total the total
+     */
+    public function getTotalItems()
+    {
+        $this->totalItems = count($items);
+        return $this->totalItems;
+    }
+}
index 36668a8f77bf7685b45cd12b23cc36ee7e562f0b..88bb8bf92710429aad5f6f94d932bf9b404db8d3 100644 (file)
@@ -64,6 +64,7 @@ class NoticeListItem extends Widget
     protected $options = true;
     protected $maxchars = 0;   // if <= 0 it means use full posts
     protected $item_tag = 'li';
+    protected $pa = null;
 
     /**
      * constructor
@@ -150,7 +151,13 @@ class NoticeListItem extends Widget
         $this->elementStart('section', array('class'=>'notice-headers'));
         $this->showNoticeTitle();
         $this->showAuthor();
-        if ($this->addressees) { $this->showAddressees(); }
+
+        if (!empty($this->notice->reply_to) || count($this->getProfileAddressees()) > 0) {
+            $this->elementStart('div', array('class' => 'parents'));
+            if (!empty($this->notice->reply_to)) { $this->showParent(); }
+            if ($this->addressees) { $this->showAddressees(); }
+            $this->elementEnd('div');
+        }
         $this->elementEnd('section');
     }
 
@@ -237,8 +244,9 @@ class NoticeListItem extends Widget
     function showAuthor()
     {
         $attrs = array('href' => $this->profile->profileurl,
-                       'class' => 'h-card p-author',
+                       'class' => 'h-card',
                        'title' => $this->profile->getNickname());
+        if(empty($this->repeat)) { $attrs['class'] .= ' p-author'; }
 
         if (Event::handle('StartShowNoticeItemAuthor', array($this->profile, $this->out, &$attrs))) {
             $this->out->elementStart('a', $attrs);
@@ -249,6 +257,19 @@ class NoticeListItem extends Widget
         }
     }
 
+    function showParent()
+    {
+        $this->out->element(
+            'a',
+            array(
+                'href' => $this->notice->getParent()->getUrl(),
+                'class' => 'u-in-reply-to',
+                'rel' => 'in-reply-to'
+            ),
+            'in reply to'
+        );
+    }
+
     function showAddressees()
     {
         $pa = $this->getProfileAddressees();
@@ -269,19 +290,20 @@ class NoticeListItem extends Widget
 
     function getProfileAddressees()
     {
-        $pa = array();
+        if($this->pa) { return $this->pa; }
+        $this->pa = array();
 
         $attentions = $this->getReplyProfiles();
 
         foreach ($attentions as $attn) {
             $class = $attn->isGroup() ? 'group' : 'account';
-            $pa[] = array('href' => $attn->profileurl,
-                          'title' => $attn->getNickname(),
-                          'class' => "addressee {$class}",
-                          'text' => $attn->getStreamName());
+            $this->pa[] = array('href' => $attn->profileurl,
+                                'title' => $attn->getNickname(),
+                                'class' => "addressee {$class}",
+                                'text' => $attn->getStreamName());
         }
 
-        return $pa;
+        return $this->pa;
     }
 
     function getReplyProfiles()
index eb73bbe43c9a4d40137688bea56894868c679bda..afe905a109ee18e6b62bd6abd0286b07d4a78b62 100644 (file)
@@ -194,7 +194,7 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin
         $actobj = $act->objects[0];
 
         $object = Fave::saveActivityObject($actobj, $stored);
-        $stored->object_type = ActivityUtils::resolveUri($object->getObjectType(), true);
+        $stored->object_type = $object->getObjectType();
 
         return $object;
     }
@@ -527,8 +527,8 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin
         $expected_verb = $exists ? ActivityVerb::UNFAVORITE : ActivityVerb::FAVORITE;
 
         switch (true) {
-        case $exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
-        case !$exists && ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+        case $exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+        case !$exists && ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
             common_redirect(common_local_url('activityverb',
                                 array('id'   => $target->getID(),
                                       'verb' => ActivityUtils::resolveUri($expected_verb, true))));
@@ -543,10 +543,10 @@ class FavoritePlugin extends ActivityVerbHandlerPlugin
     protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
     {
         switch (true) {
-        case ActivityUtils::compareTypes($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
+        case ActivityUtils::compareVerbs($verb, array(ActivityVerb::FAVORITE, ActivityVerb::LIKE)):
             Fave::addNew($scoped, $target);
             break;
-        case ActivityUtils::compareTypes($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
+        case ActivityUtils::compareVerbs($verb, array(ActivityVerb::UNFAVORITE, ActivityVerb::UNLIKE)):
             Fave::removeEntry($scoped, $target);
             break;
         default:
index 310679ef637941fe34cd174e663bd6661bf759a6..51e11153dbc8d57588b7f13f04dce358b827a3da 100644 (file)
@@ -357,7 +357,7 @@ class Fave extends Managed_DataObject
         $target = self::getTargetFromStored($stored);
 
         // The following logic was copied from StatusNet's Activity plugin
-        if (ActivityUtils::compareTypes($target->verb, array(ActivityVerb::POST))) {
+        if (ActivityUtils::compareVerbs($target->verb, array(ActivityVerb::POST))) {
             // "I like the thing you posted"
             $act->objects = $target->asActivity()->objects;
         } else {
index a710abd7bf8c8c5f80c59b16db87143323638d67..e3519dac9ee9c7eada8e30654a07bbeee9b3faa5 100644 (file)
@@ -65,9 +65,27 @@ class LinkbackPlugin extends Plugin
             // notice content
             $c = $notice->content;
             $this->notice = $notice;
-            // Ignoring results
-            common_replace_urls_callback($c,
-                                         array($this, 'linkbackUrl'));
+
+            if(!$notice->getProfile()->
+                getPref("linkbackplugin", "disable_linkbacks")
+            ) {
+                // Ignoring results
+                common_replace_urls_callback($c,
+                                             array($this, 'linkbackUrl'));
+            }
+
+            if($notice->isRepeat()) {
+                $repeat = Notice::getByID($notice->repeat_of);
+                $this->linkbackUrl($repeat->getUrl());
+            } else if(!empty($notice->reply_to)) {
+                $parent = $notice->getParent();
+                $this->linkbackUrl($parent->getUrl());
+            }
+
+            $replyProfiles = Profile::multiGet('id', $notice->getReplies());
+            foreach($replyProfiles->fetchAll('profileurl') as $profileurl) {
+                $this->linkbackUrl($profileurl);
+            }
         }
         return true;
     }
@@ -95,32 +113,89 @@ class LinkbackPlugin extends Plugin
             return $orig;
         }
 
-        $pb = null;
-        $tb = null;
+        // XXX: Should handle relative-URI resolution in these detections
 
-        if (array_key_exists('X-Pingback', $result->headers)) {
-            $pb = $result->headers['X-Pingback'];
-        } else if (preg_match('/<link rel="pingback" href="([^"]+)" ?\/?>/',
-                              $result->body,
-                              $match)) {
-            $pb = $match[1];
-        }
-
-        if (!empty($pb)) {
-            $this->pingback($result->final_url, $pb);
+        $wm = $this->getWebmention($result);
+        if(!empty($wm)) {
+            // It is the webmention receiver's job to resolve source
+            // Ref: https://github.com/converspace/webmention/issues/43
+            $this->webmention($url, $wm);
         } else {
-            $tb = $this->getTrackback($result->body, $result->final_url);
-            if (!empty($tb)) {
-                $this->trackback($result->final_url, $tb);
+            $pb = $this->getPingback($result);
+            if (!empty($pb)) {
+                // Pingback still looks for exact URL in our source, so we
+                // must send what we have
+                $this->pingback($url, $pb);
+            } else {
+                $tb = $this->getTrackback($result);
+                if (!empty($tb)) {
+                    $this->trackback($result->final_url, $tb);
+                }
             }
         }
 
         return $orig;
     }
 
+    // Based on https://github.com/indieweb/mention-client-php
+    // which is licensed Apache 2.0
+    function getWebmention($result) {
+        // XXX: the fetcher only gives back one of each header, so this may fail on multiple Link headers
+        if(preg_match('~<((?:https?://)?[^>]+)>; rel="webmention"~', $result->headers['Link'], $match)) {
+            return $match[1];
+        } elseif(preg_match('~<((?:https?://)?[^>]+)>; rel="http://webmention.org/?"~', $result->headers['Link'], $match)) {
+            return $match[1];
+        }
+
+        if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]*\/?>/i', $result->body, $match)
+           || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?webmention ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+            return $match[1];
+        } elseif(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="http:\/\/webmention\.org\/?"[ ]*\/?>/i', $result->body, $match)
+                 || preg_match('/<(?:link|a)[ ]+rel="http:\/\/webmention\.org\/?"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+            return $match[1];
+        }
+    }
+
+    function webmention($url, $endpoint) {
+        $source = $this->notice->getUrl();
+
+        $payload = array(
+            'source' => $source,
+            'target' => $url
+        );
+
+        $request = HTTPClient::start();
+        try {
+            $response = $request->post($endpoint,
+                array(
+                    'Content-type: application/x-www-form-urlencoded',
+                    'Accept: application/json'
+                ),
+                $payload
+            );
+
+            if(!in_array($response->getStatus(), array(200,202))) {
+                common_log(LOG_WARNING,
+                           "Webmention request failed for '$url' ($endpoint)");
+            }
+        } catch (HTTP_Request2_Exception $e) {
+            common_log(LOG_WARNING,
+                       "Webmention request failed for '$url' ($endpoint)");
+        }
+    }
+
+    function getPingback($result) {
+        if (array_key_exists('X-Pingback', $result->headers)) {
+            return $result->headers['X-Pingback'];
+        } else if(preg_match('/<(?:link|a)[ ]+href="([^"]+)"[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]*\/?>/i', $result->body, $match)
+                  || preg_match('/<(?:link|a)[ ]+rel="[^" ]* ?pingback ?[^" ]*"[ ]+href="([^"]+)"[ ]*\/?>/i', $result->body, $match)) {
+            return $match[1];
+        }
+    }
+
     function pingback($url, $endpoint)
     {
-        $args = array($this->notice->uri, $url);
+        $args = array($this->notice->getUrl(), $url);
 
         if (!extension_loaded('xmlrpc')) {
             if (!dl('xmlrpc.so')) {
@@ -131,9 +206,10 @@ class LinkbackPlugin extends Plugin
 
         $request = HTTPClient::start();
         try {
+            $request->setBody(xmlrpc_encode_request('pingback.ping', $args));
             $response = $request->post($endpoint,
                 array('Content-Type: text/xml'),
-                xmlrpc_encode_request('pingback.ping', $args));
+                false);
             $response = xmlrpc_decode($response->getBody());
             if (xmlrpc_is_fault($response)) {
                 common_log(LOG_WARNING,
@@ -153,8 +229,11 @@ class LinkbackPlugin extends Plugin
     // Largely cadged from trackback_cls.php by
     // Ran Aroussi <ran@blogish.org>, GPL2 or any later version
     // http://phptrackback.sourceforge.net/
-    function getTrackback($text, $url)
+    function getTrackback($result)
     {
+        $text = $result->body;
+        $url = $result->final_url;
+
         if (preg_match_all('/(<rdf:RDF.*?<\/rdf:RDF>)/sm', $text, $match, PREG_SET_ORDER)) {
             for ($i = 0; $i < count($match); $i++) {
                 if (preg_match('|dc:identifier="' . preg_quote($url) . '"|ms', $match[$i][1])) {
@@ -246,4 +325,23 @@ class LinkbackPlugin extends Plugin
                                'or <a href="http://www.movabletype.org/docs/mttrackback.html">Trackback</a> protocols.'));
         return true;
     }
+
+    public function onStartInitializeRouter(URLMapper $m)
+    {
+        $m->connect('settings/linkback', array('action' => 'linkbacksettings'));
+        return true;
+    }
+
+    function onEndAccountSettingsNav($action)
+    {
+        $action_name = $action->trimmed('action');
+
+        $action->menuItem(common_local_url('linkbacksettings'),
+                          // TRANS: OpenID plugin menu item on user settings page.
+                          _m('MENU', 'Send Linkbacks'),
+                          // TRANS: OpenID plugin tooltip for user settings menu item.
+                          _m('Opt-out of sending linkbacks.'),
+                          $action_name === 'linkbacksettings');
+        return true;
+    }
 }
diff --git a/plugins/Linkback/actions/linkbacksettings.php b/plugins/Linkback/actions/linkbacksettings.php
new file mode 100644 (file)
index 0000000..261e297
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+/**
+ * Settings for Linkback
+ *
+ * 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  Settings
+ * @package   StatusNet
+ * @author    Stephen Paul Weber <singpolyma@singpolyma.net>
+ * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ */
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Settings for Linkback
+ *
+ * Lets users opt out of sending linkbacks
+ *
+ * @category Settings
+ * @author   Stephen Paul Weber <singpolyma@singpolyma.net>
+ * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
+ */
+class LinkbacksettingsAction extends SettingsAction
+{
+    /**
+     * Title of the page
+     *
+     * @return string Page title
+     */
+    function title()
+    {
+        // TRANS: Title of Linkback settings page for a user.
+        return _m('TITLE','Linkback settings');
+    }
+
+    /**
+     * Instructions for use
+     *
+     * @return string Instructions for use
+     */
+    function getInstructions()
+    {
+        // TRANS: Form instructions for Linkback settings.
+        return _m('Linkbacks inform post authors when you link to them. ' .
+                 'You can disable this feature here.');
+    }
+
+    function showContent()
+    {
+        $this->elementStart('form', array('method' => 'post',
+                                          'class' => 'form_settings',
+                                          'action' =>
+                                          common_local_url('linkbacksettings')));
+        $this->hidden('token', common_session_token());
+
+        $this->elementStart('fieldset');
+        $this->element('legend', null, _m('LEGEND','Preferences'));
+        $this->checkbox('disable_linkbacks', "Opt out of sending linkbacks for URLs you post", $this->scoped->getPref("linkbackplugin", "disable_linkbacks"));
+        // TRANS: Button text to save OpenID prefs
+        $this->submit('settings_linkback_prefs_save', _m('BUTTON','Save'), 'submit', 'save_prefs');
+        $this->elementEnd('fieldset');
+
+        $this->elementEnd('form');
+    }
+
+    /**
+     * Handle a POST request
+     *
+     * @return void
+     */
+    protected function doPost()
+    {
+        $x = $this->scoped->setPref("linkbackplugin", "disable_linkbacks", $this->boolean('disable_linkbacks'));
+
+        return _m('Linkback preferences saved.');
+    }
+}
index 41f9abfcdff2383e3bbcab0cae3d58e08f0440f6..8f729bef6f57aae03900dd90aadc88c88d4a8ec6 100644 (file)
@@ -104,6 +104,9 @@ class OStatusPlugin extends Plugin
 
         // Incoming from a foreign PuSH hub
         $qm->connect('pushin', 'PushInQueueHandler');
+
+        // Re-subscribe feeds that need renewal
+        $qm->connect('pushrenew', 'PushRenewQueueHandler');
         return true;
     }
 
@@ -1351,4 +1354,20 @@ class OStatusPlugin extends Plugin
         }
         return true;
     }
+
+    public function onCronDaily()
+    {
+        try {
+            $sub = FeedSub::renewalCheck();
+        } catch (NoResultException $e) {
+            common_log(LOG_INFO, "There were no expiring feeds.");
+            return;
+        }
+
+        $qm = QueueManager::get();
+        while ($sub->fetch()) {
+            $item = array('feedsub_id' => $sub->id);
+            $qm->enqueue($item, 'pushrenew');
+        }
+    }
 }
index 149d57f63cc76b996e9bd4701c3c8f51bb366e9c..b0875c298865b86f70fa1d8c57c272368d73d25a 100644 (file)
@@ -295,7 +295,7 @@ class FeedSub extends Managed_DataObject
     {
         $fs = new FeedSub();
         // the "" empty string check is because we historically haven't saved unsubscribed feeds as NULL
-        $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() - INTERVAL 1 day');
+        $fs->whereAdd('sub_end IS NOT NULL AND sub_end!="" AND sub_end < NOW() + INTERVAL 1 day');
         if (!$fs->find()) { // find can be both false and 0, depending on why nothing was found
             throw new NoResultException($fs);
         }
@@ -355,7 +355,7 @@ class FeedSub extends Managed_DataObject
             $response = $client->post($hub, $headers, $post);
             $status = $response->getStatus();
             // PuSH specificed response status code
-            if ($status == 202) {
+            if ($status == 202  || $status == 204) {
                 common_log(LOG_INFO, __METHOD__ . ': sub req ok, awaiting verification callback');
                 return;
             } else if ($status >= 200 && $status < 300) {
index be8a6f66a710a37c42c39f716eb427d146d9b090..ddd1a2a38dfe145ba881e3f6d94e9ea254edcb8e 100644 (file)
@@ -1004,11 +1004,13 @@ class Ostatus_profile extends Managed_DataObject
             }
         }
 
+        $obj = ActivityUtils::getFeedAuthor($feedEl);
+
         // @todo FIXME: We should check whether this feed has elements
         // with different <author> or <dc:creator> elements, and... I dunno.
         // Do something about that.
 
-        $obj = ActivityObject::fromRssChannel($feedEl);
+        if(empty($obj)) { $obj = ActivityObject::fromRssChannel($feedEl); }
 
         return self::ensureActivityObjectProfile($obj, $hints);
     }
diff --git a/plugins/OStatus/lib/pushrenewqueuehandler.php b/plugins/OStatus/lib/pushrenewqueuehandler.php
new file mode 100644 (file)
index 0000000..d79cbe5
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+/*
+ * 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);
+}
+
+/**
+ * Renew an expiring feedsub
+ * @package FeedSub
+ * @author Stephen Paul Weber <singpolyma@singpolyma.net>
+ */
+class PushRenewQueueHandler extends QueueHandler
+{
+    function transport()
+    {
+        return 'pushrenew';
+    }
+
+    function handle($data)
+    {
+        $feedsub_id = $data['feedsub_id'];
+        $feedsub = FeedSub::getKV('id', $feedsub_id);
+        if ($feedsub instanceof FeedSub) {
+            try {
+                common_log(LOG_INFO, "Renewing feed subscription\n\tExp.: {$feedsub->sub_end}\n\tFeed: {$feedsub->uri}\n\tHub:  {$feedsub->huburi}");
+                $feedsub->renew();
+            } catch(Exception $e) {
+                common_log(LOG_ERR, "Exception during PuSH renew processing for $feedsub->uri: " . $e->getMessage());
+            }
+        } else {
+            common_log(LOG_ERR, "Discarding renew for unknown feed subscription id $feedsub_id");
+        }
+        return true;
+    }
+}
index c337efbaeca10660b02376212f006df46cee722e..c31686e4451b5556c2488ce24c7dbe41b4d812a0 100644 (file)
@@ -204,7 +204,7 @@ class SharePlugin extends ActivityVerbHandlerPlugin
                            'class' => 'h-card p-author',
                            'title' => $repeater->getFancyName());
 
-            $nli->out->elementStart('span', 'repeat h-entry');
+            $nli->out->elementStart('span', 'repeat');
 
             // TRANS: Addition in notice list item if notice was repeated. Followed by a span with a nickname.
             $nli->out->raw(_('Repeated by').' ');
index 28dc1bf0798bbbf52fc9348c6c6338f955040af6..1edc3d8971351790ac70f9fc22418800814a6237 100644 (file)
@@ -148,7 +148,7 @@ class WebFingerPlugin extends Plugin
             $url = common_local_url('webfinger') . '?resource='.$acct;
 
             foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
-                header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"');
+                header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"', false);
             }
         }
     }
index 20f9aa775fe16613735e3d360d29009fcdbc72cb..0d3515ba3321885a0eccb99ef871f6b78cbffe7c 100644 (file)
@@ -648,12 +648,12 @@ address .poweredby {
     width:100%;
 }
 
-.notice .p-author {
-    margin-right: 8px;
+.notice .parents {
+    display: inline;
 }
 
-.notice .addressees::before {
-    content: '\25B8';
+.notice .parents::before {
+    content: '\25B8  ';
 }
 .notice .addressees, .notice .addressees li {
     display: inline;