]> git.mxchange.org Git - quix0rs-gnu-social.git/commitdiff
Merge remote-tracking branch 'upstream/master' into social-master
authorRoland Haeder <roland@mxchange.org>
Sat, 27 Feb 2016 21:09:23 +0000 (22:09 +0100)
committerRoland Haeder <roland@mxchange.org>
Sat, 27 Feb 2016 21:09:23 +0000 (22:09 +0100)
Signed-off-by: Roland Haeder <roland@mxchange.org>
41 files changed:
actions/foaf.php
actions/newnotice.php
actions/profilesettings.php
actions/shownotice.php
classes/Managed_DataObject.php
classes/Notice.php
classes/User.php
lib/apiauthaction.php
lib/attachmentlist.php
lib/conversationnoticestream.php
lib/default.php
lib/fullnoticestream.php [new file with mode: 0644]
lib/inboxnoticestream.php
lib/networkpublicnoticestream.php
lib/noticestream.php
lib/profilenoticestream.php
lib/publicnoticestream.php
lib/replynoticestream.php
lib/tagnoticestream.php
lib/toselector.php
lib/util.php
plugins/Favorite/lib/favenoticestream.php
plugins/OStatus/OStatusPlugin.php
plugins/OStatus/actions/usersalmon.php
plugins/OStatus/classes/Ostatus_profile.php
plugins/OStatus/lib/salmonaction.php
plugins/Oembed/OembedPlugin.php
plugins/OpenID/actions/openidserver.php
plugins/RegisterThrottle/RegisterThrottlePlugin.php
plugins/RegisterThrottle/actions/ipregistrations.php [new file with mode: 0644]
plugins/WebFinger/WebFingerPlugin.php
plugins/WebFinger/lib/webfingerresource.php
socialfy-another-domain/README.txt [new file with mode: 0644]
socialfy-another-domain/dot-well-known/host-meta [new file with mode: 0644]
socialfy-another-domain/dot-well-known/webfinger/example@example.com.xml [new file with mode: 0644]
socialfy-another-domain/dot-well-known/webfinger/index.php [new file with mode: 0644]
socialfy-your-domain/README.txt [deleted file]
socialfy-your-domain/dot-well-known/host-meta [deleted file]
socialfy-your-domain/xrd/example@example.com.xml [deleted file]
socialfy-your-domain/xrd/index.php [deleted file]
theme/base/css/display.css

index 4420c2d5036cc59bf6bb4ad08fbcacce749e436a..997be9c735780700fbe057ca478cac45e69cf5f1 100644 (file)
@@ -90,7 +90,7 @@ class FoafAction extends ManagedAction
 
         // Would be nice to tell if they were a Person or not (e.g. a #person usertag?)
         $this->elementStart('Agent', array('rdf:about' => $this->user->getUri()));
-        if ($this->user->email) {
+        if (common_config('foaf', 'mbox_sha1sum') && $this->user->email) {
             $this->element('mbox_sha1sum', null, sha1('mailto:' . $this->user->email));
         }
         if ($this->profile->fullname) {
index 298361d6003a614a2be287ffbdd9c2bc66fe3672..6ee209206110ccbfcca611837a08e2e2f82aee1b 100644 (file)
@@ -190,6 +190,9 @@ class NewnoticeAction extends FormAction
             // and maybe even directly save whether they're local or not!
             $act->context->attention = common_get_attentions($content, $this->scoped, $parent);
 
+            // $options gets filled with possible scoping settings
+            ToSelector::fillActivity($this, $act, $options);
+
             $actobj = new ActivityObject();
             $actobj->type = ActivityObject::NOTE;
             $actobj->content = common_render_content($content, $this->scoped, $parent);
index 5804f21ca59d817d37642a3ccea537d5b6bdd607..a1d947530c6b284ee59a3e230d061dfbcbed91dd 100644 (file)
@@ -110,7 +110,10 @@ class ProfilesettingsAction extends SettingsAction
             $this->elementStart('li');
             // TRANS: Field label in form for profile settings.
             $this->input('fullname', _('Full name'),
-                         $this->trimmed('fullname') ?: $this->scoped->getFullname());
+                         $this->trimmed('fullname') ?: $this->scoped->getFullname(),
+                         // TRANS: Instructions for full name text field on profile settings
+                         _('A full name is required, if empty it will be set to your nickname.'),
+                         null, true);
             $this->elementEnd('li');
             $this->elementStart('li');
             // TRANS: Field label in form for profile settings.
@@ -204,13 +207,15 @@ class ProfilesettingsAction extends SettingsAction
                             (empty($user->subscribe_policy)) ? User::SUBSCRIBE_POLICY_OPEN : $user->subscribe_policy);
             $this->elementEnd('li');
         }
-        $this->elementStart('li');
-        $this->checkbox('private_stream',
-                        // TRANS: Checkbox label in profile settings.
-                        _('Make updates visible only to my followers'),
-                        ($this->arg('private_stream')) ?
-                        $this->boolean('private_stream') : $user->private_stream);
-        $this->elementEnd('li');
+        if (common_config('profile', 'allowprivate') || $user->private_stream) {
+            $this->elementStart('li');
+            $this->checkbox('private_stream',
+                            // TRANS: Checkbox label in profile settings.
+                            _('Make updates visible only to my followers'),
+                            ($this->arg('private_stream')) ?
+                            $this->boolean('private_stream') : $user->private_stream);
+            $this->elementEnd('li');
+        }
         $this->elementEnd('ul');
         // TRANS: Button to save input in profile settings.
         $this->submit('save', _m('BUTTON','Save'));
@@ -252,7 +257,6 @@ class ProfilesettingsAction extends SettingsAction
             $location = $this->trimmed('location');
             $autosubscribe = $this->booleanintstring('autosubscribe');
             $subscribe_policy = $this->trimmed('subscribe_policy');
-            $private_stream = $this->booleanintstring('private_stream');
             $language = $this->trimmed('language');
             $timezone = $this->trimmed('timezone');
             $tagstring = $this->trimmed('tags');
@@ -307,6 +311,15 @@ class ProfilesettingsAction extends SettingsAction
             $user = $this->scoped->getUser();
             $user->query('BEGIN');
 
+            // Only allow setting private_stream if site policy allows it
+            // (or user already _has_ a private stream, then you can unset it)
+            if (common_config('profile', 'allowprivate') || $user->private_stream) {
+                $private_stream = $this->booleanintstring('private_stream');
+            } else {
+                // if not allowed, we set to the existing value
+                $private_stream = $user->private_stream;
+            }
+
             // $user->nickname is updated through Profile->update();
 
             // XXX: XOR
@@ -345,7 +358,7 @@ class ProfilesettingsAction extends SettingsAction
                 $this->scoped->nickname = $nickname;
                 $this->scoped->profileurl = common_profile_url($this->scoped->getNickname());
             }
-            $this->scoped->fullname = $fullname;
+            $this->scoped->fullname = (mb_strlen($fullname)>0 ? $fullname : $this->scoped->nickname);
             $this->scoped->homepage = $homepage;
             $this->scoped->bio = $bio;
             $this->scoped->location = $location;
index 86c55c0d012f933cfc0917857c83a764dda54aee..72e2669f8313f49b7f20b5b0b6ac6856382c15d6 100644 (file)
@@ -74,6 +74,7 @@ class ShownoticeAction extends ManagedAction
         }
 
         $this->notice = $this->getNotice();
+        $this->target = $this->notice;
 
         if (!$this->notice->inScope($this->scoped)) {
             // TRANS: Client exception thrown when trying a view a notice the user has no access to.
@@ -213,12 +214,24 @@ class ShownoticeAction extends ManagedAction
     {
     }
 
-    /**
-     * Don't show aside
-     *
-     * @return void
-     */
-    function showAside() {
+    function getFeeds()
+    {
+        return array(new Feed(Feed::JSON,
+                              common_local_url('ApiStatusesShow',
+                                               array(
+                                                    'id' => $this->target->getID(),
+                                                    'format' => 'json')),
+                              // TRANS: Title for link to single notice representation.
+                              // TRANS: %s is a user nickname.
+                              sprintf(_('Single notice (JSON)'))),
+                     new Feed(Feed::ATOM,
+                              common_local_url('ApiStatusesShow',
+                                               array(
+                                                    'id' => $this->target->getID(),
+                                                    'format' => 'atom')),
+                              // TRANS: Title for link to notice feed.
+                              // TRANS: %s is a user nickname.
+                              sprintf(_('Single notice (Atom)'))));
     }
 
     /**
index 31ae6614fbd971872a3da0f27e1261c1cd076ac9..0857bb11f6c277612b69f809c749c93acd61400a 100644 (file)
@@ -412,6 +412,60 @@ abstract class Managed_DataObject extends Memcached_DataObject
         return intval($this->id);
     }
 
+    /**
+     * WARNING: Only use this on Profile and Notice. We should probably do
+     * this with traits/"implements" or whatever, but that's over the top
+     * right now, I'm just throwing this in here to avoid code duplication
+     * in Profile and Notice classes.
+     */
+    public function getAliases()
+    {
+        return array_keys($this->getAliasesWithIDs());
+    }
+
+    public function getAliasesWithIDs()
+    {
+        $aliases = array();
+        $aliases[$this->getUri()] = $this->getID();
+
+        try {
+            $aliases[$this->getUrl()] = $this->getID();
+        } catch (InvalidUrlException $e) {
+            // getUrl failed because no valid URL could be returned, just ignore it
+        }
+
+        if (common_config('fix', 'fancyurls')) {
+            /**
+             * Here we add some hacky hotfixes for remote lookups that have been taught the
+             * (at least now) wrong URI but it's still obviously the same user. Such as:
+             * - https://site.example/user/1 even if the client requests https://site.example/index.php/user/1
+             * - https://site.example/user/1 even if the client requests https://site.example//index.php/user/1
+             * - https://site.example/index.php/user/1 even if the client requests https://site.example/user/1
+             * - https://site.example/index.php/user/1 even if the client requests https://site.example///index.php/user/1
+             */
+            foreach ($aliases as $alias=>$id) {
+                try {
+                    // get a "fancy url" version of the alias, even without index.php/
+                    $alt_url = common_fake_local_fancy_url($alias);
+                    // store this as well so remote sites can be sure we really are the same profile
+                    $aliases[$alt_url] = $id;
+                } catch (Exception $e) {
+                    // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+                }
+
+                try {
+                    // get a non-"fancy url" version of the alias, i.e. add index.php/
+                    $alt_url = common_fake_local_nonfancy_url($alias);
+                    // store this as well so remote sites can be sure we really are the same profile
+                    $aliases[$alt_url] = $id;
+                } catch (Exception $e) {
+                    // Apparently we couldn't rewrite that, the $alias was as the function wanted it to be
+                }
+            }
+        }
+        return $aliases;
+    }
+
     // 'update' won't write key columns, so we have to do it ourselves.
     // This also automatically calls "update" _before_ it sets the keys.
     // FIXME: This only works with single-column primary keys so far! Beware!
index f9c4458d6aa47ce0d7f7a12bd2c747abfa445714..b21e21b086098d7351db1f23a2972638e398aa33 100644 (file)
@@ -260,7 +260,8 @@ class Notice extends Managed_DataObject
 
     public function getRendered()
     {
-        if (is_null($this->rendered) || $this->rendered === '') {
+        // we test $this->id because if it's not inserted yet, we can't update the field
+        if (!empty($this->id) && (is_null($this->rendered) || $this->rendered === '')) {
             // update to include rendered content on-the-fly, so we don't have to have a fix-up script in upgrade.php
             common_debug('Rendering notice '.$this->getID().' as it had no rendered HTML content.');
             $orig = clone($this);
@@ -856,8 +857,7 @@ class Notice extends Managed_DataObject
         }
         // Strip out any bad HTML
         $stored->rendered = common_purify($content);
-        // yeah, just don't use getRendered() here since it's not inserted yet ;)
-        $stored->content = common_strip_html($stored->rendered);
+        $stored->content  = common_strip_html($stored->getRendered(), true, true);
         if (trim($stored->content) === '') {
             // TRANS: Error message when the plain text content of a notice has zero length.
             throw new ClientException(_('Empty notice content, will not save this.'));
index c232b2b12f307c8c2ee2e0d60cc069a3746612f4..40e1a1b6443a2503371e1e81c680ea73d26cce58 100644 (file)
@@ -140,6 +140,16 @@ class User extends Managed_DataObject
         return $this->uri;
     }
 
+    static function getByUri($uri)
+    {
+        $user = new User();
+        $user->uri = $uri;
+        if (!$user->find(true)) {
+            throw new NoResultException($user);
+        }
+        return $user;
+    }
+
     public function getNickname()
     {
         return $this->getProfile()->getNickname();
index 0e81082c35ea79a322a3dc26d92f6848074036cc..a3deccd3da0e05aa25808b6ab9296a4790d76a8d 100644 (file)
@@ -85,8 +85,10 @@ class ApiAuthAction extends ApiAction
         // NOTE: $this->scoped and $this->auth_user has to get set in
         // prepare(), not handle(), as subclasses use them in prepares.
 
-        // Allow regular login session
-        if (common_logged_in()) {
+        // Allow regular login session, but we have to double-check the
+        // HTTP_REFERER value to avoid cross domain POSTing since the API
+        // doesn't use the "token" form field.
+        if (common_logged_in() && common_local_referer()) {
             $this->scoped = Profile::current();
             $this->auth_user = $this->scoped->getUser();
             if (!$this->auth_user->hasRight(Right::API)) {
index 4e39e59ca59519c00ea804ce9ab7e2c4496aa692..93398028a420fc4c56583bbf434d8352838d3980 100644 (file)
@@ -85,6 +85,12 @@ class AttachmentList extends Widget
             return 0;
         }
 
+        if ($this->notice->getProfile()->isSilenced()) {
+            // TRANS: Message for inline attachments list in notices when the author has been silenced.
+            $this->element('div', ['class'=>'error'], _('Attachments are hidden because this profile has been silenced.'));
+            return 0;
+        }
+
         $this->showListStart();
 
         foreach ($attachments as $att) {
index 9c32159d42aabe90de6fa2284e3c0ac281671fca..21b2d7f0be59a0561e6ecf1994cfcc7e89a451dd 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); }
 
 /**
  * Notice stream for a conversation
@@ -96,9 +92,7 @@ class RawConversationNoticeStream extends NoticeStream
             $notice->limit($offset, $limit);
         }
 
-        if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
+        self::filterVerbs($notice, $this->selectVerbs);
 
         // ORDER BY
         // currently imitates the previously used "_reverseChron" sorting
index 2dd35583c1b535cf37299bef699f18dba021da88..f114408091805359a50ae6d18da18d30283e9ecf 100644 (file)
@@ -81,6 +81,9 @@ $default =
               'log_queries' => false, // true to log all DB queries
               'log_slow_queries' => 0, // if set, log queries taking over N seconds
               'mysql_foreign_keys' => false), // if set, enables experimental foreign key support on MySQL
+        'fix' =>
+        array('fancyurls' => true,   // makes sure aliases in WebFinger etc. are not f'd by index.php/ URLs
+              ),
         'syslog' =>
         array('appname' => 'statusnet', # for syslog
               'priority' => 'debug', # XXX: currently ignored
@@ -129,6 +132,7 @@ $default =
         array('banned' => array(),
               'biolimit' => null,
               'changenick' => false,
+              'allowprivate' => false,  // whether to allow setting stream to private ("only followers can read")
               'backup' => false,    // can cause DoS, so should be done via CLI
               'restore' => false,
               'delete' => false,
@@ -141,6 +145,10 @@ $default =
               'path' => $_path . '/avatar/',
               'ssl' => null,
               'maxsize' => 300),
+        'foaf' =>
+        array(
+              'mbox_sha1sum' => false,
+            ),
         'public' =>
         array('localonly' => false,
               'blacklist' => array(),
@@ -291,6 +299,7 @@ $default =
         ),
         'notice' =>
         array('contentlimit' => null,
+              'allowprivate' => false,  // whether to allow users to "check the padlock" to publish notices available for their subscribers.
               'defaultscope' => null, // null means 1 if site/private, 0 otherwise
               'hidespam' => true), // Whether to hide silenced users from timelines
         'message' =>
diff --git a/lib/fullnoticestream.php b/lib/fullnoticestream.php
new file mode 100644 (file)
index 0000000..2f83007
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+/**
+ * Class for notice streams that does not filter anything out.
+ */
+abstract class FullNoticeStream extends NoticeStream
+{
+    protected $selectVerbs = [];
+}
index 496fe0d05d5425f6c784a0cf448b4fe34e4bb52d..ccf0f460c971adfde9f5cfd2f71712a633cfff30 100644 (file)
@@ -30,7 +30,7 @@
  * @link      http://status.net/
  */
 
-if (!defined('GNUSOCIAL') && !defined('STATUSNET')) { exit(1); }
+if (!defined('GNUSOCIAL')) { exit(1); }
 
 /**
  * Stream of notices for a profile's "all" feed
@@ -72,7 +72,7 @@ class InboxNoticeStream extends ScopingNoticeStream
  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
  * @link      http://status.net/
  */
-class RawInboxNoticeStream extends NoticeStream
+class RawInboxNoticeStream extends FullNoticeStream
 {
     protected $target  = null;
     protected $inbox = null;
@@ -84,8 +84,8 @@ class RawInboxNoticeStream extends NoticeStream
      */
     function __construct(Profile $target)
     {
+        parent::__construct();
         $this->target  = $target;
-        $this->unselectVerbs = array(ActivityVerb::DELETE);
     }
 
     /**
@@ -119,12 +119,9 @@ class RawInboxNoticeStream extends NoticeStream
         if (!empty($max_id)) {
             $notice->whereAdd(sprintf('notice.id <= %d', $max_id));
         }
-        if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
-        if (!empty($this->unselectVerbs)) {
-            $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
-        }
+
+        self::filterVerbs($notice, $this->selectVerbs);
+
         $notice->limit($offset, $limit);
         // notice.id will give us even really old posts, which were
         // recently imported. For example if a remote instance had
index 3320b7cd5a65e89d60e5f3a1d81ed7faab7d3367..bd4da5d0751e5ba4c913c5ca043df8a9e436ffa6 100644 (file)
@@ -23,7 +23,7 @@ class NetworkPublicNoticeStream extends ScopingNoticeStream
  * @link      http://status.net/
  */
 
-class RawNetworkPublicNoticeStream extends NoticeStream
+class RawNetworkPublicNoticeStream extends FullNoticeStream
 {
     function getNoticeIds($offset, $limit, $since_id, $max_id)
     {
@@ -46,9 +46,7 @@ class RawNetworkPublicNoticeStream extends NoticeStream
         Notice::addWhereSinceId($notice, $since_id);
         Notice::addWhereMaxId($notice, $max_id);
 
-        if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
+        self::filterVerbs($notice, $this->selectVerbs);
 
         $ids = array();
 
index 01c5ee4a72e56e970aec2db641f26daf63b3300d..2b04a89ca4297c4ba905634af275f2f4ec188626 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); }
 
 /**
  * Class for notice streams
@@ -46,16 +42,15 @@ if (!defined('STATUSNET')) {
  */
 abstract class NoticeStream
 {
-    protected $selectVerbs   = null;    // must be set to array
-    protected $unselectVerbs = null;    // must be set to array
+    protected $selectVerbs   = array(ActivityVerb::POST => true,
+                                     ActivityVerb::SHARE => true);
 
     public function __construct()
     {
-        if ($this->selectVerbs === null) {
-            $this->selectVerbs = array(ActivityVerb::POST, ActivityUtils::resolveUri(ActivityVerb::POST, true));
-        }
-        if ($this->unselectVerbs === null) {
-            $this->unselectVerbs = array(ActivityVerb::DELETE);
+        foreach ($this->selectVerbs as $key=>$val) {
+            // to avoid database inconsistency issues we select both relative and absolute verbs
+            $this->selectVerbs[ActivityUtils::resolveUri($key)] = $val;
+            $this->selectVerbs[ActivityUtils::resolveUri($key, true)] = $val;
         }
     }
 
@@ -74,4 +69,21 @@ abstract class NoticeStream
     {
        return Notice::multiGet('id', $ids);
     }
+
+    static function filterVerbs(Notice $notice, array $selectVerbs)
+    {
+        $filter = array_keys(array_filter($selectVerbs));
+        if (!empty($filter)) {
+            // include verbs in selectVerbs with values that equate to true
+            $notice->whereAddIn('verb', $filter, $notice->columnType('verb'));
+        }
+
+        $filter = array_keys(array_filter($selectVerbs, function ($v) { return !$v; }));
+        if (!empty($filter)) {
+            // exclude verbs in selectVerbs with values that equate to false
+            $notice->whereAddIn('!verb', $filter, $notice->columnType('verb'));
+        }
+
+        unset($filter);
+    }
 }
index a31fb585d18af16c7fc397964997557a8acd4b8a..7ff4163fcbc96dbdde5b583219872ef46a886591 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); }
 
 /**
  * Stream of notices by a profile
@@ -134,12 +130,7 @@ class RawProfileNoticeStream extends NoticeStream
         Notice::addWhereSinceId($notice, $since_id);
         Notice::addWhereMaxId($notice, $max_id);
 
-        if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
-        if (!empty($this->unselectVerbs)) {
-            $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
-        }
+        self::filterVerbs($notice, $this->selectVerbs);
 
         $notice->orderBy('created DESC, id DESC');
 
index 757c2164c08fc69af8e00bb9a24418d4b13899ec..4a16cbd235b789a6465b23ced61ee4feef9b1c1d 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); }
 
 /**
  * Public stream
@@ -66,7 +62,7 @@ class PublicNoticeStream extends ScopingNoticeStream
  * @link      http://status.net/
  */
 
-class RawPublicNoticeStream extends NoticeStream
+class RawPublicNoticeStream extends FullNoticeStream
 {
     function getNoticeIds($offset, $limit, $since_id, $max_id)
     {
@@ -87,9 +83,7 @@ class RawPublicNoticeStream extends NoticeStream
         Notice::addWhereSinceId($notice, $since_id);
         Notice::addWhereMaxId($notice, $max_id);
 
-        if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
+        self::filterVerbs($notice, $this->selectVerbs);
 
         $ids = array();
 
index 9fea5cac1e5accdc62a77b65526d468c2ae7d8ea..9eb188d54db2d43d0105a4ea48d2b395b911f64d 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); }
 
 /**
  * Stream of mentions of me
@@ -92,8 +88,20 @@ class RawReplyNoticeStream extends NoticeStream
         Notice::addWhereMaxId($reply, $max_id, 'notice_id', 'reply.modified');
 
         if (!empty($this->selectVerbs)) {
+            // this is a little special since we have to join in Notice
             $reply->joinAdd(array('notice_id', 'notice:id'));
-            $reply->whereAddIn('notice.verb', $this->selectVerbs, 'string');
+
+            $filter = array_keys(array_filter($this->selectVerbs));
+            if (!empty($filter)) {
+                // include verbs in selectVerbs with values that equate to true
+                $reply->whereAddIn('notice.verb', $filter, 'string');
+            }
+
+            $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+            if (!empty($filter)) {
+                // exclude verbs in selectVerbs with values that equate to false
+                $reply->whereAddIn('!notice.verb', $filter, 'string');
+            }
         }
 
         $reply->orderBy('reply.modified DESC, reply.notice_id DESC');
index d24907fa7ee5176acd45a9433a6439a456a4ef8e..28f5d0e82483ab4536b4427c07002f0c80e00e12 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); }
 
 /**
  * Stream of notices with a given tag
@@ -90,13 +86,22 @@ class RawTagNoticeStream extends NoticeStream
         Notice::addWhereMaxId($nt, $max_id, 'notice_id');
 
         if (!empty($this->selectVerbs)) {
-            $notice->whereAddIn('verb', $this->selectVerbs, $notice->columnType('verb'));
-        }
-        if (!empty($this->unselectVerbs)) {
-            $notice->whereAddIn('!verb', $this->unselectVerbs, $notice->columnType('verb'));
+            $nt->joinAdd(array('notice_id', 'notice:id'));
+
+            $filter = array_keys(array_filter($this->selectVerbs));
+            if (!empty($filter)) {
+                // include verbs in selectVerbs with values that equate to true
+                $nt->whereAddIn('notice.verb', $filter, 'string');
+            }
+
+            $filter = array_keys(array_filter($this->selectVerbs, function ($v) { return !$v; }));
+            if (!empty($filter)) {
+                // exclude verbs in selectVerbs with values that equate to false
+                $nt->whereAddIn('!notice.verb', $filter, 'string');
+            }
         }
 
-        $nt->orderBy('created DESC, notice_id DESC');
+        $nt->orderBy('notice.created DESC, notice_id DESC');
 
         if (!is_null($offset)) {
             $nt->limit($offset, $limit);
index 153d9001b54aef0882e291168083db9882d01638..7a959ff8b518e9e48ba23d997fbc651a3294803f 100644 (file)
@@ -80,44 +80,59 @@ class ToSelector extends Widget
     function show()
     {
         $choices = array();
-        $default = 'public:site';
-
-        if (!common_config('site', 'private')) {
-            // TRANS: Option in drop-down of potential addressees.
-            $choices['public:everyone'] = _m('SENDTO','Everyone');
-            $default = 'public:everyone';
-        }
-        // TRANS: Option in drop-down of potential addressees.
-        // TRANS: %s is a StatusNet sitename.
-        $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+        $default = common_config('site', 'private') ? 'public:site' : 'public:everyone';
 
         $groups = $this->user->getGroups();
 
         while ($groups instanceof User_group && $groups->fetch()) {
-            $value = 'group:'.$groups->id;
+            $value = 'group:'.$groups->getID();
             if (($this->to instanceof User_group) && $this->to->id == $groups->id) {
                 $default = $value;
             }
-            $choices[$value] = $groups->getBestName();
+            $choices[$value] = "!{$groups->getNickname()} [{$groups->getBestName()}]";
         }
 
         // Add subscribed users to dropdown menu
         $users = $this->user->getSubscribed();
         while ($users->fetch()) {
-            $value = 'profile:'.$users->id;
-            if ($this->user->streamNicknames()) {
-                $choices[$value] = $users->getNickname();
-            } else {
-                $choices[$value] = $users->getBestName();
+            $value = 'profile:'.$users->getID();
+            try {
+                $choices[$value] = substr($users->getAcctUri(), 5) . " [{$users->getBestName()}]";
+            } catch (ProfileNoAcctUriException $e) {
+                $choices[$value] = "[?@?] " . $e->profile->getBestName();
             }
         }
 
         if ($this->to instanceof Profile) {
-            $value = 'profile:'.$this->to->id;
+            $value = 'profile:'.$this->to->getID();
             $default = $value;
-            $choices[$value] = $this->to->getBestName();
+            try {
+                $choices[$value] = substr($this->to->getAcctUri(), 5) . " [{$this->to->getBestName()}]";
+            } catch (ProfileNoAcctUriException $e) {
+                $choices[$value] = "[?@?] " . $e->profile->getBestName();
+            }
+        }
+
+        // alphabetical order
+        asort($choices);
+
+        // Reverse so we can add entries at the end (can't unshift with a key)
+        $choices = array_reverse($choices);
+
+        if (common_config('notice', 'allowprivate')) {
+            // TRANS: Option in drop-down of potential addressees.
+            // TRANS: %s is a StatusNet sitename.
+            $choices['public:site'] = sprintf(_('Everyone at %s'), common_config('site', 'name'));
+        }
+
+        if (!common_config('site', 'private')) {
+            // TRANS: Option in drop-down of potential addressees.
+            $choices['public:everyone'] = _m('SENDTO','Everyone');
         }
 
+        // Return the order
+        $choices = array_reverse($choices);
+
         $this->out->dropdown($this->id,
                              // TRANS: Label for drop-down of potential addressees.
                              _m('LABEL','To:'),
@@ -127,18 +142,40 @@ class ToSelector extends Widget
                              $default);
 
         $this->out->elementStart('span', 'checkbox-wrapper');
-        $this->out->checkbox('notice_private',
-                             // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
-                             _('Private?'),
-                             $this->private);
+        if (common_config('notice', 'allowprivate')) {
+            $this->out->checkbox('notice_private',
+                                 // TRANS: Checkbox label in widget for selecting potential addressees to mark the notice private.
+                                 _('Private?'),
+                                 $this->private);
+        }
         $this->out->elementEnd('span');
     }
 
+    static function fillActivity(Action $action, Activity $act, array &$options)
+    {
+        if (!$act->context instanceof ActivityContext) {
+            $act->context = new ActivityContext();
+        }
+        self::fillOptions($action, $options);
+        if (isset($options['groups'])) {
+            foreach ($options['groups'] as $group_id) {
+                $group = User_group::getByID($group_id);
+                $act->context->attention[$group->getUri()] = $group->getObjectType();
+            }
+        }
+        if (isset($options['replies'])) {
+            foreach ($options['replies'] as $profile_uri) {
+                $profile = Profile::fromUri($profile_uri);
+                $act->context->attention[$profile->getUri()] = $profile->getObjectType();
+            }
+        }
+    }
+
     static function fillOptions($action, &$options)
     {
         // XXX: make arg name selectable
         $toArg = $action->trimmed('notice_to');
-        $private = $action->boolean('notice_private');
+        $private = common_config('notice', 'allowprivate') ? $action->boolean('notice_private') : false;
 
         if (empty($toArg)) {
             return;
index 23a169d006333171da5bef629cf1dcb8fdf924dd..e07dc47f409b0c06610c180250146a2800b0b98b 100644 (file)
@@ -264,6 +264,11 @@ function common_logged_in()
     return (!is_null(common_current_user()));
 }
 
+function common_local_referer()
+{
+    return parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST) === common_config('site', 'server');
+}
+
 function common_have_session()
 {
     return (0 != strcmp(session_id(), ''));
@@ -1391,6 +1396,74 @@ function common_path($relative, $ssl=false, $addSession=true)
     return $proto.'://'.$serverpart.'/'.$pathpart.$relative;
 }
 
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_fancy_url($url)
+{
+    /**
+     * This is a hacky fix to make URIs generated with "index.php/" match against
+     * locally stored URIs without that. So for example if the remote site is looking
+     * up the webfinger for some user and for some reason knows about https://some.example/user/1
+     * but we locally store and report only https://some.example/index.php/user/1 then they would
+     * dismiss the profile for not having an identified alias.
+     *
+     * There are various live instances where these issues occur, for various reasons.
+     * Most of them being users fiddling with configuration while already having
+     * started federating (distributing the URI to other servers) or maybe manually
+     * editing the local database.
+     */
+    if (!preg_match(
+                // [1] protocol part, we can only rewrite http/https anyway.
+                '/^(https?:\/\/)' .
+                // [2] site name.
+                // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+                '('.preg_quote(common_config('site', 'server'), '/').')' .
+                // [3] site path, or if that is empty just '/' (to retain the /)
+                '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+                // [4] + [5] extract index.php (+ possible leading double /) and the rest of the URL separately.
+                '(\/?index\.php\/)(.*)$/', $url, $matches)) {
+        // if preg_match failed to match
+        throw new Exception('No known change could be made to the URL.');
+    }
+
+    // now reconstruct the URL with everything except the "index.php/" part
+    $fancy_url = '';
+    foreach ([1,2,3,5] as $idx) {
+        $fancy_url .= $matches[$idx];
+    }
+    return $fancy_url;
+}
+
+// FIXME: Maybe this should also be able to handle non-fancy URLs with index.php?p=...
+function common_fake_local_nonfancy_url($url)
+{
+    /**
+     * This is a hacky fix to make URIs NOT generated with "index.php/" match against
+     * locally stored URIs WITH that. The reverse from the above.
+     *
+     * It will also "repair" index.php URLs with multiple / prepended. Like https://some.example///index.php/user/1
+     */
+    if (!preg_match(
+                // [1] protocol part, we can only rewrite http/https anyway.
+                '/^(https?:\/\/)' .
+                // [2] site name.
+                // FIXME: Dunno how this acts if we're aliasing ourselves with a .onion domain etc.
+                '('.preg_quote(common_config('site', 'server'), '/').')' .
+                // [3] site path, or if that is empty just '/' (to retain the /)
+                '('.preg_quote(common_config('site', 'path') ?: '/', '/').')' .
+                // [4] should be empty (might contain one or more / and then maybe also index.php). Will be overwritten.
+                // [5] will have the extracted actual URL part (besides site path)
+                '((?!index.php\/)\/*(?:index.php\/)?)(.*)$/', $url, $matches)) {
+        // if preg_match failed to match
+        throw new Exception('No known change could be made to the URL.');
+    }
+
+    $matches[4] = 'index.php/'; // inject the index.php/ rewritethingy
+
+    // remove the first element, which is the full matching string
+    array_shift($matches);
+    return implode($matches);
+}
+
 function common_inject_session($url, $serverpart = null)
 {
     if (!common_have_session()) {
index 6294c8cdda44f9d7378ae8cbe5f34bdeebd14af4..d10272ac91578478d58aa589bbcfa16791d53912 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); }
 
 /**
  * Notice stream for favorites
@@ -77,14 +73,14 @@ class RawFaveNoticeStream extends NoticeStream
     protected $user_id;
     protected $own;
 
+    protected $selectVerbs = array();
+
     function __construct($user_id, $own)
     {
         parent::__construct();
 
         $this->user_id = $user_id;
         $this->own     = $own;
-
-        $this->selectVerbs = array();
     }
 
     /**
index b199263870c8669bd8798ed756ac01c7a915405b..9cb48a1ca961c3fced6483da88da84eaf7f4688d 100644 (file)
@@ -669,7 +669,7 @@ class OStatusPlugin extends Plugin
                                $other->getBestName());
 
         $act->actor   = $profile->asActivityObject();
-        $act->object  = $other->asActivityObject();
+        $act->objects[] = $other->asActivityObject();
 
         $oprofile->notifyActivity($act, $profile);
 
@@ -707,7 +707,7 @@ class OStatusPlugin extends Plugin
 
         $act->actor = $profile->asActivityObject();
         $act->verb = ActivityVerb::JOIN;
-        $act->object = $oprofile->asActivityObject();
+        $act->objects[] = $oprofile->asActivityObject();
 
         $act->time = time();
         // TRANS: Title for joining a remote groep.
@@ -761,7 +761,7 @@ class OStatusPlugin extends Plugin
 
         $act->actor = $member->asActivityObject();
         $act->verb = ActivityVerb::LEAVE;
-        $act->object = $oprofile->asActivityObject();
+        $act->objects[] = $oprofile->asActivityObject();
 
         $act->time = time();
         // TRANS: Title for leaving a remote group.
@@ -807,7 +807,7 @@ class OStatusPlugin extends Plugin
 
         $act->actor = $sub->asActivityObject();
         $act->verb = ActivityVerb::FOLLOW;
-        $act->object = $oprofile->asActivityObject();
+        $act->objects[] = $oprofile->asActivityObject();
 
         $act->time = time();
         // TRANS: Title for following a remote list.
@@ -859,7 +859,7 @@ class OStatusPlugin extends Plugin
 
         $act->actor = $member->asActivityObject();
         $act->verb = ActivityVerb::UNFOLLOW;
-        $act->object = $oprofile->asActivityObject();
+        $act->objects[] = $oprofile->asActivityObject();
 
         $act->time = time();
         // TRANS: Title for unfollowing a remote list.
@@ -1051,7 +1051,7 @@ class OStatusPlugin extends Plugin
                                $notice->getUrl());
 
         $act->actor   = $profile->asActivityObject();
-        $act->object  = $notice->asActivityObject();
+        $act->objects[]  = $notice->asActivityObject();
 
         $oprofile->notifyActivity($act, $profile);
 
@@ -1173,7 +1173,7 @@ class OStatusPlugin extends Plugin
                                $profile->getBestName());
 
         $act->actor   = $profile->asActivityObject();
-        $act->object  = $act->actor;
+        $act->objects[]  = $act->actor;
 
         while ($oprofile->fetch()) {
             $oprofile->notifyDeferred($act, $profile);
index 5d2a92d99b3d93da1d821fc8bfc9931d190f9122..ea5262aa3bc0a351ee0e20212724d4ba9f7da0b7 100644 (file)
@@ -29,21 +29,43 @@ class UsersalmonAction extends SalmonAction
     {
         parent::prepare($args);
 
-        $id = $this->trimmed('id');
+        $this->user = User::getByID($this->trimmed('id'));
 
-        if (!$id) {
-            // TRANS: Client error displayed trying to perform an action without providing an ID.
-            $this->clientError(_m('No ID.'));
-        }
+        $this->target = $this->user->getProfile();
 
-        $this->user = User::getKV('id', $id);
+        // Notice must either be a) in reply to a notice by this user
+        // or b) in reply to a notice to the attention of this user
+        // or c) to the attention of this user
+        // or d) reference the user as an activity:object
+
+        $notice = null;
 
-        if (!$this->user instanceof User) {
-            // TRANS: Client error displayed when referring to a non-existing user.
-            $this->clientError(_m('No such user.'));
+        if (!empty($this->activity->context->replyToID)) {
+            try {
+                $notice = Notice::getByUri($this->activity->context->replyToID);
+            } catch (NoResultException $e) {
+                $notice = false;
+            }
         }
 
-        $this->target = $this->user->getProfile();
+        if ($notice instanceof Notice &&
+                ($this->target->sameAs($notice->getProfile())
+                    || in_array($this->target->getID(), $notice->getAttentionProfileIDs())
+                )) {
+            // In reply to a notice either from or mentioning this user.
+            common_debug('User is the owner or was in the attention list of thr:in-reply-to activity.');
+        } elseif (!empty($this->activity->context->attention) &&
+                   array_key_exists($this->target->getUri(), $this->activity->context->attention)) {
+            // To the attention of this user.
+            common_debug('User was in attention list of salmon slap.');
+        } elseif (!empty($this->activity->objects) && $this->activity->objects[0]->id === $this->target->getUri()) {
+            // The user is the object of this slap (unfollow for example)
+            common_debug('User URI was the id of the salmon slap object.');
+        } else {
+            common_debug('User was NOT found in salmon slap context.');
+            // TRANS: Client exception.
+            throw new ClientException(_m('The owner of this salmon endpoint was not in the context of the carried slap.'));
+        }
 
         return true;
     }
@@ -71,30 +93,6 @@ class UsersalmonAction extends SalmonAction
             throw new ClientException(_m('Cannot handle that kind of post.'));
         }
 
-        // Notice must either be a) in reply to a notice by this user
-        // or b) in reply to a notice to the attention of this user
-        // or c) to the attention of this user
-
-        $context = $this->activity->context;
-        $notice = false;
-
-        if (!empty($context->replyToID)) {
-            $notice = Notice::getKV('uri', $context->replyToID);
-        }
-
-        if ($notice instanceof Notice &&
-            ($notice->profile_id == $this->target->id ||
-             array_key_exists($this->target->id, $notice->getReplies())))
-        {
-            // In reply to a notice either from or mentioning this user.
-        } elseif (!empty($context->attention) &&
-                   array_key_exists($this->target->getUri(), $context->attention)) {
-            // To the attention of this user.
-        } else {
-            // TRANS: Client exception.
-            throw new ClientException(_m('Not to anyone in reply to anything.'));
-        }
-
         try {
             $this->saveNotice();
         } catch (AlreadyFulfilledException $e) {
index 20bed3915637a660688302c7a8934946040aa504..9a3b533aa82848bc4064015e71afb1d724c49c73 100644 (file)
@@ -1300,7 +1300,7 @@ class Ostatus_profile extends Managed_DataObject
             try {
                 $this->updateAvatar($avatar);
             } catch (Exception $ex) {
-                common_log(LOG_WARNING, "Exception saving OStatus profile avatar: " . $ex->getMessage());
+                common_log(LOG_WARNING, "Exception updating OStatus profile avatar: " . $ex->getMessage());
             }
         }
     }
index ef6099c883886bb692a2b01aab34ef8bb1ba80c8..c8d77a99a689effc6656b8613c4ab8fff07d33ad 100644 (file)
@@ -98,6 +98,7 @@ class SalmonAction extends Action
         assert($this->target instanceof Profile);
 
         common_debug('Got a ' . $this->activity->verb);
+
         try {
             $options = [ 'source' => 'ostatus' ];
             common_debug('Save salmon slap directly with Notice::saveActivity for actor=='.$this->actor->getID());
index c90f57439b3a2983ab9037ca43bce0e820603619..44e4ac93180679d1baccba90fb6bb66bc74a25b8 100644 (file)
@@ -73,18 +73,20 @@ class OembedPlugin extends Plugin
             $metadata = OpenGraphHelper::ogFromHtml($dom);
         }
 
-        // sometimes sites serve the path, not the full URL, for images
-        // let's "be liberal in what you accept from others"!
-        // add protocol and host if the thumbnail_url starts with /
-        if(substr($metadata->thumbnail_url,0,1) == '/') {
-            $thumbnail_url_parsed = parse_url($metadata->url);
-            $metadata->thumbnail_url = $thumbnail_url_parsed['scheme']."://".$thumbnail_url_parsed['host'].$metadata->thumbnail_url;
-        } 
+        if (isset($metadata->thumbnail_url)) {
+            // sometimes sites serve the path, not the full URL, for images
+            // let's "be liberal in what you accept from others"!
+            // add protocol and host if the thumbnail_url starts with /
+            if(substr($metadata->thumbnail_url,0,1) == '/') {
+                $thumbnail_url_parsed = parse_url($metadata->url);
+                $metadata->thumbnail_url = $thumbnail_url_parsed['scheme']."://".$thumbnail_url_parsed['host'].$metadata->thumbnail_url;
+            }
         
-        // some wordpress opengraph implementations sometimes return a white blank image
-        // no need for us to save that!
-        if($metadata->thumbnail_url == 'https://s0.wp.com/i/blank.jpg') {
-            unset($metadata->thumbnail_url);
+            // some wordpress opengraph implementations sometimes return a white blank image
+            // no need for us to save that!
+            if($metadata->thumbnail_url == 'https://s0.wp.com/i/blank.jpg') {
+                unset($metadata->thumbnail_url);
+            }
         }
 
     }
index 512dc37467c78bad44ed3da3c9c2275f5b983966..ecfc732e6cf121654a133120f99460df1ae172d6 100644 (file)
@@ -63,8 +63,7 @@ class OpenidserverAction extends Action
         $request = $this->oserver->decodeRequest();
         if (in_array($request->mode, array('checkid_immediate',
             'checkid_setup'))) {
-            $user = common_current_user();
-            if(!$user){
+            if (!$this->scoped instanceof Profile) {
                 if($request->immediate){
                     //cannot prompt the user to login in immediate mode, so answer false
                     $response = $this->generateDenyResponse($request);
@@ -77,9 +76,9 @@ class OpenidserverAction extends Action
                     common_set_returnto($_SERVER['REQUEST_URI']);
                     common_redirect(common_local_url('login'), 303);
                 }
-            }else if(common_profile_url($user->nickname) == $request->identity || $request->idSelect()){
+            } elseif (in_array($request->identity, $this->scoped->getAliases()) || $request->idSelect()) {
                 $user_openid_trustroot = User_openid_trustroot::pkeyGet(
-                                                array('user_id'=>$user->id, 'trustroot'=>$request->trust_root));
+                                                array('user_id'=>$this->scoped->getID(), 'trustroot'=>$request->trust_root));
                 if(empty($user_openid_trustroot)){
                     if($request->immediate){
                         //cannot prompt the user to trust this trust root in immediate mode, so answer false
@@ -87,7 +86,7 @@ class OpenidserverAction extends Action
                     }else{
                         common_ensure_session();
                         $_SESSION['openid_trust_root'] = $request->trust_root;
-                        $allowResponse = $this->generateAllowResponse($request, $user);
+                        $allowResponse = $this->generateAllowResponse($request, $this->scoped);
                         $this->oserver->encodeResponse($allowResponse); //sign the response
                         $denyResponse = $this->generateDenyResponse($request);
                         $this->oserver->encodeResponse($denyResponse); //sign the response
@@ -101,12 +100,11 @@ class OpenidserverAction extends Action
                         // were POSTed here.
                         common_redirect(common_local_url('openidtrust'), 303);
                     }
-                }else{
+                } else {
                     //user has previously authorized this trust root
-                    $response = $this->generateAllowResponse($request, $user);
-                    //$response = $request->answer(true, null, common_profile_url($user->nickname));
+                    $response = $this->generateAllowResponse($request, $this->scoped);
                 }
-            } else if ($request->immediate) {
+            } elseif ($request->immediate) {
                 $response = $this->generateDenyResponse($request);
             } else {
                 //invalid
@@ -137,14 +135,14 @@ class OpenidserverAction extends Action
         }
     }
 
-    function generateAllowResponse($request, $user){
-        $response = $request->answer(true, null, common_profile_url($user->nickname));
+    function generateAllowResponse($request, Profile $profile){
+        $response = $request->answer(true, null, $profile->getUrl());
+        $user = $profile->getUser();
 
-        $profile = $user->getProfile();
         $sreg_data = array(
-            'fullname' => $profile->fullname,
-            'nickname' => $user->nickname,
-            'email' => $user->email,
+            'fullname' => $profile->getFullname(),
+            'nickname' => $profile->getNickname(),
+            'email' => $user->email,    // FIXME: Should we make the email optional?
             'language' => $user->language,
             'timezone' => $user->timezone);
         $sreg_request = Auth_OpenID_SRegRequest::fromOpenIDRequest($request);
index d09642a6330872d0206d5a00406674c87ae4a3ee..dc9d8b8627dcb688c4cde14423ecb7bd526419ac 100644 (file)
@@ -81,6 +81,13 @@ class RegisterThrottlePlugin extends Plugin
         return true;
     }
 
+    public function onRouterInitialized(URLMapper $m)
+    {
+        $m->connect('main/ipregistrations/:ipaddress',
+                    array('action'      => 'ipregistrations'),
+                    array('ipaddress'   => '[0-9a-f\.\:]+'));
+    }
+
     /**
      * Called when someone tries to register.
      *
@@ -134,6 +141,52 @@ class RegisterThrottlePlugin extends Plugin
         return true;
     }
 
+    function onEndShowSections(Action $action)
+    {
+        if (!$action instanceof ShowstreamAction) {
+            // early return for actions we're not interested in
+            return true;
+        }
+
+        $target = $action->getTarget();
+        if (!$target->isSilenced()) {
+            // Only show the IP of users who are not silenced.
+            return true;
+        }
+
+        $scoped = $action->getScoped();
+        if (!$scoped->hasRight(Right::SILENCEUSER)) {
+            // only show registration IP if we have the right to silence users
+            return true;
+        }
+
+        $ri = Registration_ip::getKV('user_id', $target->getID());
+        $ipaddress = null;
+        if ($ri instanceof Registration_ip) {
+            $ipaddress = $ri->ipaddress;
+            unset($ri);
+        }
+
+        $action->elementStart('div', array('id' => 'entity_mod_log',
+                                           'class' => 'section'));
+
+        $action->element('h2', null, _('Registration IP'));
+
+        // TRANS: Label for the information about which IP a users registered from.
+        $action->element('strong', null, _('Registered from:'));
+        $el = 'span';
+        $attrs = ['class'=>'ipaddress'];
+        if (!is_null($ipaddress)) {
+            $el = 'a';
+            $attrs['href'] = common_local_url('ipregistrations', array('ipaddress'=>$ipaddress));
+        }
+        $action->element($el, $attrs,
+                            // TRANS: Unknown IP address.
+                            $ipaddress ?: _('unknown'));
+
+        $action->elementEnd('div');
+    }
+
     /**
      * Called after someone registers, by any means.
      *
@@ -154,8 +207,8 @@ class RegisterThrottlePlugin extends Plugin
 
         $reg = new Registration_ip();
 
-        $reg->user_id   = $profile->id;
-        $reg->ipaddress = $ipaddress;
+        $reg->user_id   = $profile->getID();
+        $reg->ipaddress = mb_strtolower($ipaddress);
         $reg->created   = common_sql_now();
 
         $result = $reg->insert();
diff --git a/plugins/RegisterThrottle/actions/ipregistrations.php b/plugins/RegisterThrottle/actions/ipregistrations.php
new file mode 100644 (file)
index 0000000..46f1ed8
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class IpregistrationsAction extends ManagedAction
+{
+    protected $needLogin = true;
+
+    protected $ipaddress = null;
+
+    function title()
+    {
+        return sprintf(_('Registrations from IP %s'), $this->ipaddress);
+    }
+
+    protected function doPreparation()
+    {
+        if (!$this->scoped->hasRight(Right::SILENCEUSER) && !$this->scoped->hasRole(Profile_role::ADMINISTRATOR)) {
+            throw new AuthorizationException(_('You are not authorized to view this page.'));
+        }
+
+        $this->ipaddress    = $this->trimmed('ipaddress');
+        $this->profile_ids  = Registration_ip::usersByIP($this->ipaddress);
+    }
+
+    public function showContent()
+    {
+        $this->elementStart('ul');
+        $profile = Profile::multiGet('id', $this->profile_ids);
+        while ($profile->fetch()) {
+            $this->elementStart('li');
+            try {
+                $this->element('a', ['href'=>$profile->getUrl()], $profile->getFancyName());
+            } catch (InvalidUrlException $e) {
+                $this->element('span', null, $profile->getFancyName());
+            }
+            $this->elementEnd('li');
+        }
+        $this->elementEnd('ul');
+    }
+}
index 1edc3d8971351790ac70f9fc22418800814a6237..942eab27cfe4394d77b69d66559b21e67ce4ce43 100644 (file)
@@ -100,15 +100,36 @@ class WebFingerPlugin extends Plugin
                 }
             }
         } else {
-            $user = User::getKV('uri', $resource);
-            if ($user instanceof User) {
+            try {
+                $user = User::getByUri($resource);
                 $profile = $user->getProfile();
-            } else {
-                // try and get it by profile url
-                $profile = Profile::getKV('profileurl', $resource);
+            } catch (NoResultException $e) {
+                if (common_config('fix', 'fancyurls')) {
+                    try {
+                        try {   // if it's a /index.php/ url
+                            // common_fake_local_fancy_url can throw an exception
+                            $alt_url = common_fake_local_fancy_url($resource);
+                        } catch (Exception $e) {    // let's try to create a fake local /index.php/ url
+                            // this too if it can't do anything about the URL
+                            $alt_url = common_fake_local_nonfancy_url($resource);
+                        }
+
+                        // and this will throw a NoResultException if not found
+                        $user = User::getByUri($alt_url);
+                        $profile = $user->getProfile();
+                    } catch (Exception $e) {
+                        // apparently we didn't get any matches with that, so continue...
+                    }
+                }
             }
         }
 
+        // if we still haven't found a match...
+        if (!$profile instanceof Profile) {
+            // if our rewrite hack didn't work, try to get something by profile URL
+            $profile = Profile::getKV('profileurl', $resource);
+        }
+
         if ($profile instanceof Profile) {
             $target = new WebFingerResource_Profile($profile);
             return false;   // We got our target, stop handler execution
@@ -144,8 +165,8 @@ class WebFingerPlugin extends Plugin
     public function onStartShowHTML(Action $action)
     {
         if ($action instanceof ShowstreamAction) {
-            $acct = 'acct:'. $action->getTarget()->getNickname() .'@'. common_config('site', 'server');
-            $url = common_local_url('webfinger') . '?resource='.$acct;
+            $resource = $action->getTarget()->getUri();
+            $url = common_local_url('webfinger') . '?resource='.urlencode($resource);
 
             foreach (array(Discovery::JRD_MIMETYPE, Discovery::XRD_MIMETYPE) as $type) {
                 header('Link: <'.$url.'>; rel="'. Discovery::LRDD_REL.'"; type="'.$type.'"', false);
index 61b2cc09adbedcd81350ed2d82db256aa59fb151..b7bace36d25abbabad835416e1dfc152718c3f8a 100644 (file)
@@ -31,23 +31,23 @@ abstract class WebFingerResource
 
     public function getAliases()
     {
-        $aliases = array();
-
-        // Add the URI as an identity, this is _not_ necessarily an HTTP url
-        $uri = $this->object->getUri();
-        $aliases[] = $uri;
-        if (common_config('webfinger', 'http_alias')
-                && strtolower(parse_url($uri, PHP_URL_SCHEME)) === 'https') {
-            $aliases[] = preg_replace('/^https:/', 'http:', $uri, 1);
+        $aliases = $this->object->getAliasesWithIDs();
+
+        // Some sites have changed from http to https and still want
+        // (because remote sites look for it) verify that they are still
+        // the same identity as they were on HTTP. Should NOT be used if
+        // you've run HTTPS all the time!
+        if (common_config('webfinger', 'http_alias')) {
+            foreach ($aliases as $alias=>$id) {
+                if (!strtolower(parse_url($alias, PHP_URL_SCHEME)) === 'https') {
+                    continue;
+                }
+                $aliases[preg_replace('/^https:/', 'http:', $alias, 1)] = $id;
+            }
         }
 
-        try {
-            $aliases[] = $this->object->getUrl();
-        } catch (InvalidUrlException $e) {
-            // getUrl failed because no valid URL could be returned, just ignore it
-        }
-
-        return $aliases;
+        // return a unique set of aliases by extracting only the keys
+        return array_keys($aliases);
     }
 
     abstract public function updateXRD(XML_XRD $xrd);
diff --git a/socialfy-another-domain/README.txt b/socialfy-another-domain/README.txt
new file mode 100644 (file)
index 0000000..7cb01ed
--- /dev/null
@@ -0,0 +1,56 @@
+Initial simple way to Webfinger enable your domain -- needs PHP.
+================================================================
+
+This guide needs some updating, since it will only guide you to present
+XML data (while the curl command likely gives you JSON). The workaround
+is to simply make curl get 'webfinger.xml' instead, and/or have another
+file that contains JSON, but that requires editing the PHP file as well.
+
+Step 1
+======
+
+Put the 'dot-well-known' on your website, so it loads at:
+
+     https://example.com/.well-known/
+
+(Remember the . at the beginning of this one, which is common practice
+for "hidden" files and why we have renamed it "dot-")
+
+Step 2
+======
+
+Edit the .well-known/host-meta file and replace "example.com" with the
+domain name you're hosting the .well-known directory on.
+
+Using vim you can do this as a quick method:
+    $ vim .well-known/host-meta [ENTER]
+    :%s/example.com/domain.com/    [ENTER]
+    :wq [ENTER]
+
+Step 3
+======
+
+For each user on your site, and this might only be you...
+
+In the webfinger directory, make a copy of the example@example.com.xml file
+so that it's called (replace username and example.com with appropriate
+values, the domain name should be the same as you're "socialifying"):
+
+     username@example.com.xml
+
+Then edit the file contents, replacing "social.example.com" with your
+GNU social instance's base path, and change the user ID number (and
+nickname for the FOAF link) to that of your account on your social
+site. If you don't know your user ID number, you can see this on your
+GNU social profile page by looking at the destination URLs in the
+Feeds links.
+
+PROTIP: You can get the bulk of the contents (note the <Subject> element though)
+        from curling down your real webfinger data:
+$ curl https://social.example.com/.well-known/webfinger?resource=acct:username@social.example.com
+
+Finally
+=======
+
+Using this method, though fiddly, you can now be @user@domain without
+the need for any prefixes for subdomains, etc.
diff --git a/socialfy-another-domain/dot-well-known/host-meta b/socialfy-another-domain/dot-well-known/host-meta
new file mode 100644 (file)
index 0000000..bba942f
--- /dev/null
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0" xmlns:hm="http://host-meta.net/xrd/1.0">  
+ <Link rel="lrdd" type="application/xrd+xml"
+        template="https://example.com/.well-known/webfinger?resource={uri}"/>
+</XRD>
diff --git a/socialfy-another-domain/dot-well-known/webfinger/example@example.com.xml b/socialfy-another-domain/dot-well-known/webfinger/example@example.com.xml
new file mode 100644 (file)
index 0000000..e95662b
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
+  <Subject>acct:username@example.com</Subject>
+  <Alias>acct:username@social.example.com</Alias>
+  <Alias>https://social.example.com/user/1</Alias>
+  <Link rel="http://webfinger.net/rel/profile-page"
+        type="text/html"
+       href="https://social.example.com/user/1"/>
+    
+  <Link rel="http://schemas.google.com/g/2010#updates-from"
+       type="application/atom+xml"
+       href="https://social.example.com/api/statuses/user_timeline/1.atom"/>
+    
+  <!-- Is this/was this ever supported?
+       <Link rel="http://microformats.org/profile/hcard"
+       type="text/html"
+       href="https://social.example.com/hcard"/> -->
+  
+  <Link rel="http://gmpg.org/xfn/11"
+       type="text/html"
+       href="https://social.example.com/user/1"/>
+    
+  <Link rel="describedby"
+       type="application/rdf+xml"
+       href="https://social.example.com/username/foaf"/>
+
+  <Link rel="http://salmon-protocol.org/ns/salmon-replies"
+       href="https://social.example.com/main/salmon/user/1"/>
+    
+  <Link rel="http://salmon-protocol.org/ns/salmon-mention"
+       href="https://social.example.com/main/salmon/user/1"/>
+    
+  <Link rel="http://ostatus.org/schema/1.0/subscribe"
+       template="https://social.example.com/main/ostatussub?profile={uri}"/>
+</XRD>
diff --git a/socialfy-another-domain/dot-well-known/webfinger/index.php b/socialfy-another-domain/dot-well-known/webfinger/index.php
new file mode 100644 (file)
index 0000000..91071bc
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+
+/*
+ * GNU social
+ * Copyright (C) 2010, Free Software Foundation, 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/>.
+ */
+
+
+// basename should make sure we can't escape this directory
+$u = basename($_GET['resource']);
+
+if (!strpos($u, '@')) {
+    throw new Exception('Bad resource');
+    exit(1);
+}
+
+if (mb_strpos($u, 'acct:')===0) {
+    $u = substr($u, 5);
+}
+
+// Just to be a little bit safer, you know, with all the unicode stuff going on
+$u = filter_var($u, FILTER_SANITIZE_EMAIL);
+
+$f = $u . ".xml";
+
+if (file_exists($f)) {
+  header('Content-Disposition: attachment; filename="'.urlencode($f).'"');
+  header('Content-type: application/xrd+xml');
+  echo file_get_contents($f);
+}
diff --git a/socialfy-your-domain/README.txt b/socialfy-your-domain/README.txt
deleted file mode 100644 (file)
index 3e2688f..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-Initial simple way to Webfinger enable your domain -- needs PHP.
-================================================================
-
-Step 1
-======
-
-First, put the folders 'xrd' and 'dot-well-known' on your website, so
-they load at:
-
-     http://yourname.com/xrd/
-
-     and
-
-     http://yourname.com/.well-known/
-
-     (Remember the . at the beginning of this one)
-
-NOTE: If you're using https, make sure each instance of http:// for
-      your own domain ("example.com") is replaced with https://
-
-Step 2
-======
-
-Next, edit xrd/index.php and enter a secret in this line:
-
-$s = "";
-
-This can be anything you like...
-
-$s = "johnny5";
-
-or 
-
-$s = "12345";
-
-It really doesn't matter too much.
-
-
-Step 3
-======
-
-Edit the .well-known/host-meta file and replace all occurrences of
-"example.com" with your domain name.
-
-Step 4
-======
-
-For each user on your site, and this might only be you...
-
-In the xrd directory, make a copy of the example@example.com.xml file
-so that it's called...
-
-     yoursecretusername@domain.com.xml
-
-So, if your secret from step 2 is 'johnny5' and your name is 'ben' and
-your domain is 'titanictoycorp.biz', your file should be called
-johnny5ben@titanictoycorp.biz.xml
-
-Then edit the file, replacing "social.example.com" with your GNU
-social instance's base path, and change the user ID number (and
-nickname for the FOAF link) to that of your account on your social
-site. If you don't know your user ID number, you can see this on your
-GNU social profile page by looking at the destination URLs in the
-Feeds links.
-
-Finally
-=======
-
-Using this method, though fiddly, you can now be @user@domain without
-the need for any prefixes for subdomains, etc.
diff --git a/socialfy-your-domain/dot-well-known/host-meta b/socialfy-your-domain/dot-well-known/host-meta
deleted file mode 100644 (file)
index 1929b2e..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"
-     xmlns:hm="http://host-meta.net/xrd/1.0">  
-  <hm:Host>example.com</hm:Host>
-  <Link rel="lrdd" template="http://example.com/.well-known/xrd?uri={uri}">
-    <Title>WebFinger resource descriptor</Title>
-  </Link>
-</XRD>
diff --git a/socialfy-your-domain/xrd/example@example.com.xml b/socialfy-your-domain/xrd/example@example.com.xml
deleted file mode 100644 (file)
index b713efe..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
-  <Subject>acct:example@example.com</Subject>
-  <Alias>acct:example@social.example.com</Alias>
-  <Alias>http://social.example.com/user/1</Alias>
-  <Link rel="http://webfinger.net/rel/profile-page"
-        type="text/html"
-       href="http://social.example.com/user/1"/>
-    
-  <Link rel="http://schemas.google.com/g/2010#updates-from"
-       type="application/atom+xml"
-       href="http://social.example.com/api/statuses/user_timeline/1.atom"/>
-    
-  <!-- Is this/was this ever supported?
-       <Link rel="http://microformats.org/profile/hcard"
-       type="text/html"
-       href="http://social.example.com/hcard"/> -->
-  
-  <Link rel="http://gmpg.org/xfn/11"
-       type="text/html"
-       href="http://social.example.com/user/1"/>
-    
-  <Link rel="describedby"
-       type="application/rdf+xml"
-       href="http://social.example.com/username/foaf"/>
-
-  <Link rel="http://salmon-protocol.org/ns/salmon-replies"
-       href="http://social.example.com/main/salmon/user/1"/>
-    
-  <Link rel="http://salmon-protocol.org/ns/salmon-mention"
-       href="http://social.example.com/main/salmon/user/1"/>
-    
-  <Link rel="http://ostatus.org/schema/1.0/subscribe"
-       template="http://social.example.com/main/ostatussub?profile={uri}"/>
-</XRD>
diff --git a/socialfy-your-domain/xrd/index.php b/socialfy-your-domain/xrd/index.php
deleted file mode 100644 (file)
index 25f1d8b..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?php
-
-/*
- * GNU social
- * Copyright (C) 2010, Free Software Foundation, 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/>.
- */
-
-
-$s = "";
-
-/* this should be a secret */
-
-$u = $_GET['uri'];
-
-$u = substr($u, 5);
-
-$f = $s . $u . ".xml";
-
-if (file_exists($f)) {
-  $fh = fopen($f, 'r');
-  $c = fread($fh, filesize($f));
-  fclose($fh);
-  header('Content-type: text/xml');
-  echo $c;
-}
-
-
-?>
\ No newline at end of file
index 61696e6f11a7dbfdaa5ef45be92b8cabdc8eaa8d..295916d78ec8a2449e4b7a8a4fedf56487ae282c 100644 (file)
@@ -503,6 +503,10 @@ address .poweredby {
     z-index: 99;
 }
 
+.form_notice .to-selector > select {
+    max-width: 300px;
+}
+
 .form_settings label[for=notice_to] {
     left: 5px;
     margin-left: 0px;