]> git.mxchange.org Git - friendica.git/commitdiff
Merge pull request #7184 from annando/add-tag
authorPhilipp <admin+Github@philipp.info>
Sun, 26 May 2019 06:14:18 +0000 (08:14 +0200)
committerGitHub <noreply@github.com>
Sun, 26 May 2019 06:14:18 +0000 (08:14 +0200)
AP: We now transmit "add tag" activity

25 files changed:
include/api.php
mod/cal.php
mod/events.php
mod/notes.php
mod/photos.php
mod/videos.php
mod/viewcontacts.php [deleted file]
src/App/Router.php
src/Content/Widget.php
src/Content/Widget/ContactBlock.php
src/Model/Contact.php
src/Model/Profile.php
src/Module/Contact.php
src/Module/Profile.php
src/Module/Profile/Contacts.php [new file with mode: 0644]
src/Protocol/ActivityPub/Processor.php
src/Protocol/ActivityPub/Transmitter.php
src/Protocol/DFRN.php
src/Protocol/OStatus.php
src/Util/JsonLD.php
view/templates/profile/contacts.tpl [new file with mode: 0644]
view/theme/duepuntozero/style.css
view/theme/frio/css/style.css
view/theme/frio/templates/profile/contacts.tpl [new file with mode: 0644]
view/theme/smoothly/style.css

index eccd77675e91125ed3eae26191bb31b7e678ea07..0ca0bf838c983179e219b883dcfc3cf1c7aa19dc 100644 (file)
@@ -361,7 +361,7 @@ function api_call(App $a)
                        }
                }
 
-               Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call']);
+               Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call', 'query' => $a->query_string]);
                throw new NotImplementedException();
        } catch (HTTPException $e) {
                header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
index 3f3cba466f5d8cbc5c63f55ab6cc3809daa917ac..0a2a02e53c49e1613b8c02c6702c34aeb3f6afe6 100644 (file)
@@ -147,7 +147,7 @@ function cal_content(App $a)
        $sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
 
        // get the tab navigation bar
-       $tabs = Profile::getTabs($a, false, $a->data['user']['nickname']);
+       $tabs = Profile::getTabs($a, 'cal', false, $a->data['user']['nickname']);
 
        // The view mode part is similiar to /mod/events.php
        if ($mode == 'view') {
index 6569653a063f01779e411c2f84a696a7632e2a62..8e9606b203cb8e15b236a6c45f28e494ab409635 100644 (file)
@@ -246,7 +246,7 @@ function events_content(App $a)
        $tabs = '';
        // tabs
        if ($a->theme_events_in_profile) {
-               $tabs = Profile::getTabs($a, true);
+               $tabs = Profile::getTabs($a, 'events', true);
        }
 
        $mode = 'view';
index fdb12d6cc5a6d0300aabe03671fad0759f69499e..1f67e486d6a6d06ab98bddb4116dd70874575611 100644 (file)
@@ -28,7 +28,7 @@ function notes_content(App $a, $update = false)
                return;
        }
 
-       $o = Profile::getTabs($a, true);
+       $o = Profile::getTabs($a, 'notes', true);
 
        if (!$update) {
                $o .= '<h3>' . L10n::t('Personal Notes') . '</h3>';
index 7c0ca1b7badc4465b7276052100ad6530a8db8cf..61f1b795301836f96687f3bbc38d8d27d974bf91 100644 (file)
@@ -950,7 +950,7 @@ function photos_content(App $a)
 
        // tabs
        $is_owner = (local_user() && (local_user() == $owner_uid));
-       $o .= Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
+       $o .= Profile::getTabs($a, 'photos', $is_owner, $a->data['user']['nickname']);
 
        // Display upload form
        if ($datatype === 'upload') {
index 3fb36a73e4d319e20b04d8d634c03e3e44cf4436..9e19ecf11744d4d53da9b6d163095007919b2e52 100644 (file)
@@ -217,7 +217,7 @@ function videos_content(App $a)
 
        // tabs
        $_is_owner = (local_user() && (local_user() == $owner_uid));
-       $o .= Profile::getTabs($a, $_is_owner, $a->data['user']['nickname']);
+       $o .= Profile::getTabs($a, 'videos', $_is_owner, $a->data['user']['nickname']);
 
        //
        // dispatch request
diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php
deleted file mode 100644 (file)
index 1491982..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * @file mod/viewcontacts.php
- */
-
-use Friendica\App;
-use Friendica\Content\ContactSelector;
-use Friendica\Content\Nav;
-use Friendica\Content\Pager;
-use Friendica\Core\Config;
-use Friendica\Core\L10n;
-use Friendica\Core\Protocol;
-use Friendica\Core\Renderer;
-use Friendica\Core\System;
-use Friendica\Database\DBA;
-use Friendica\Model\Contact;
-use Friendica\Model\Profile;
-use Friendica\Util\Proxy as ProxyUtils;
-
-function viewcontacts_init(App $a)
-{
-       if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
-               throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
-       }
-
-       if ($a->argc < 2) {
-               throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
-       }
-
-       Nav::setSelected('home');
-
-       $user = DBA::selectFirst('user', [], ['nickname' => $a->argv[1], 'blocked' => false]);
-       if (!DBA::isResult($user)) {
-               throw new \Friendica\Network\HTTPException\NotFoundException();
-       }
-
-       $a->data['user'] = $user;
-       $a->profile_uid  = $user['uid'];
-
-       Profile::load($a, $a->argv[1]);
-}
-
-function viewcontacts_content(App $a)
-{
-       if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
-               notice(L10n::t('Public access denied.') . EOL);
-               return;
-       }
-
-       $is_owner = $a->profile['profile_uid'] == local_user();
-
-       // tabs
-       $o = Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
-
-       if (!count($a->profile) || $a->profile['hide-friends']) {
-               notice(L10n::t('Permission denied.') . EOL);
-               return $o;
-       }
-
-       $condition = [
-               'uid'     => $a->profile['uid'],
-               'blocked' => false,
-               'pending' => false,
-               'hidden'  => false,
-               'archive' => false,
-               'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]
-       ];
-
-       $total = DBA::count('contact', $condition);
-
-       $pager = new Pager($a->query_string);
-
-       $params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
-
-       $contacts_stmt = DBA::select('contact', [], $condition, $params);
-
-       if (!DBA::isResult($contacts_stmt)) {
-               info(L10n::t('No contacts.') . EOL);
-               return $o;
-       }
-
-       $contacts = [];
-
-       while ($contact = DBA::fetch($contacts_stmt)) {
-               /// @TODO This triggers an E_NOTICE if 'self' is not there
-               if ($contact['self']) {
-                       continue;
-               }
-
-               $contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
-
-               $contacts[] = [
-                       'id'           => $contact['id'],
-                       'img_hover'    => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
-                       'photo_menu'   => Contact::photoMenu($contact),
-                       'thumb'        => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
-                       'name'         => substr($contact_details['name'], 0, 20),
-                       'username'     => $contact_details['name'],
-                       'details'      => $contact_details['location'],
-                       'tags'         => $contact_details['keywords'],
-                       'about'        => $contact_details['about'],
-                       'account_type' => Contact::getAccountType($contact_details),
-                       'url'          => Contact::magicLink($contact['url']),
-                       'sparkle'      => '',
-                       'itemurl'      => (($contact_details['addr'] != "") ? $contact_details['addr'] : $contact['url']),
-                       'network'      => ContactSelector::networkToName($contact['network'], $contact['url']),
-               ];
-       }
-
-       DBA::close($contacts_stmt);
-
-       $tpl = Renderer::getMarkupTemplate("viewcontact_template.tpl");
-       $o .= Renderer::replaceMacros($tpl, [
-               '$title'    => L10n::t('Contacts'),
-               '$contacts' => $contacts,
-               '$paginate' => $pager->renderFull($total),
-       ]);
-
-       return $o;
-}
index be81c143d1aa845c32f520573077b3fd7170b565..f7ee24730dfed3ea0bb7fd2b576887daaf4978b5 100644 (file)
@@ -187,6 +187,7 @@ class Router
                $this->routeCollector->addRoute(['GET'],         '/probe',               Module\Debug\Probe::class);
                $this->routeCollector->addGroup('/profile', function (RouteCollector $collector) {
                        $collector->addRoute(['GET'], '/{nickname}',                         Module\Profile::class);
+                       $collector->addRoute(['GET'], '/{nickname}/contacts[/{type}]',       Module\Profile\Contacts::class);
                        $collector->addRoute(['GET'], '/{profile:\d+}/view',                 Module\Profile::class);
                });
                $this->routeCollector->addGroup('/proxy', function (RouteCollector $collector) {
index b5f83a803e1e12ccdab0af78c0f7ef0dd4d3feaf..e89245cd2bc40b0c4ed012fc6595159dde61c827 100644 (file)
@@ -121,17 +121,28 @@ class Widget
        }
 
        /**
-        * @param string $type
+        * Display a generic filter widget based on a list of options
+        *
+        * The options array must be the following format:
+        * [
+        *    [
+        *      'ref' => {filter value},
+        *      'name' => {option name}
+        *    ],
+        *    ...
+        * ]
+        *
+        * @param string $type The filter query string key
         * @param string $title
         * @param string $desc
-        * @param string $all
-        * @param string $baseUrl
+        * @param string $all The no filter label
+        * @param string $baseUrl The full page request URI
         * @param array  $options
-        * @param string $selected
+        * @param string $selected The currently selected filter option value
         * @return string
         * @throws \Exception
         */
-       public static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
+       private static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
        {
                $queryString = parse_url($baseUrl, PHP_URL_QUERY);
                $queryArray = [];
@@ -160,6 +171,37 @@ class Widget
                ]);
        }
 
+       /**
+        * Return networks widget
+        *
+        * @param string $baseurl  baseurl
+        * @param string $selected optional, default empty
+        * @return string
+        * @throws \Exception
+        */
+       public static function contactRels($baseurl, $selected = '')
+       {
+               if (!local_user()) {
+                       return '';
+               }
+
+               $options = [
+                       ['ref' => 'followers', 'name' => L10n::t('Followers')],
+                       ['ref' => 'following', 'name' => L10n::t('Following')],
+                       ['ref' => 'mutuals', 'name' => L10n::t('Mutual friends')],
+               ];
+
+               return self::filter(
+                       'rel',
+                       L10n::t('Relationships'),
+                       '',
+                       L10n::t('All Contacts'),
+                       $baseurl,
+                       $options,
+                       $selected
+               );
+       }
+
        /**
         * Return networks widget
         *
index f4fdea2fb4c3b6be90212defe83ddadf87d1e19d..bc33b9c9c928672a24de0d9712bf0a52562a0c73 100644 (file)
@@ -52,7 +52,7 @@ class ContactBlock
                        'pending' => false,
                        'hidden' => false,
                        'archive' => false,
-                       'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA],
+                       'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::FEED],
                ]);
 
                $contacts_title = L10n::t('No contacts');
index ce3aeac5608f357891ec14fb865c4f62541b13bd..a0bbdd10499f43abbf6138d976c7a145e6d66402 100644 (file)
@@ -2120,17 +2120,33 @@ class Contact extends BaseObject
                return $contact;
        }
 
-       public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false, $note = '') {
+       /**
+        * @param array  $importer Owner (local user) data
+        * @param array  $contact  Existing owner-specific contact data we want to expand the relationship with. Optional.
+        * @param array  $datarray An item-like array with at least the 'author-id' and 'author-url' keys for the contact. Mandatory.
+        * @param bool   $sharing  True: Contact is now sharing with Owner; False: Contact is now following Owner (default)
+        * @param string $note     Introduction additional message
+        * @return bool|null True: follow request is accepted; False: relationship is rejected; Null: relationship is pending
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public static function addRelationship(array $importer, array $contact, array $datarray, $sharing = false, $note = '')
+       {
                // Should always be set
                if (empty($datarray['author-id'])) {
-                       return;
+                       return false;
                }
 
-               $fields = ['url', 'name', 'nick', 'photo', 'network'];
+               $fields = ['url', 'name', 'nick', 'photo', 'network', 'blocked'];
                $pub_contact = DBA::selectFirst('contact', $fields, ['id' => $datarray['author-id']]);
                if (!DBA::isResult($pub_contact)) {
                        // Should never happen
-                       return;
+                       return false;
+               }
+
+               // Contact is blocked at node-level
+               if (self::isBlocked($datarray['author-id'])) {
+                       return false;
                }
 
                $url = defaults($datarray, 'author-link', $pub_contact['url']);
@@ -2139,44 +2155,45 @@ class Contact extends BaseObject
                $nick = $pub_contact['nick'];
                $network = $pub_contact['network'];
 
-               if (is_array($contact)) {
+               if (!empty($contact)) {
+            // Contact is blocked at user-level
+                   if (self::isBlockedByUser($contact['id'], $importer['id'])) {
+                       return false;
+            }
+
                        // Make sure that the existing contact isn't archived
                        self::unmarkForArchival($contact);
 
-                       $protocol = self::getProtocol($url, $contact['network']);
-
                        if (($contact['rel'] == self::SHARING)
                                || ($sharing && $contact['rel'] == self::FOLLOWER)) {
                                DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true, 'pending' => false],
                                                ['id' => $contact['id'], 'uid' => $importer['uid']]);
                        }
 
-                       if ($protocol == Protocol::ACTIVITYPUB) {
-                               ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
-                       }
-
-                       // send email notification to owner?
+                       return true;
                } else {
-                       $protocol = self::getProtocol($url, $network);
-
+                       // send email notification to owner?
                        if (DBA::exists('contact', ['nurl' => Strings::normaliseLink($url), 'uid' => $importer['uid'], 'pending' => true])) {
                                Logger::log('ignoring duplicated connection request from pending contact ' . $url);
-                               return;
+                               return null;
                        }
+
                        // create contact record
-                       q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
-                               `blocked`, `readonly`, `pending`, `writable`)
-                               VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
-                               intval($importer['uid']),
-                               DBA::escape(DateTimeFormat::utcNow()),
-                               DBA::escape($url),
-                               DBA::escape(Strings::normaliseLink($url)),
-                               DBA::escape($name),
-                               DBA::escape($nick),
-                               DBA::escape($photo),
-                               DBA::escape($network),
-                               intval(self::FOLLOWER)
-                       );
+                       DBA::insert('contact', [
+                               'uid'      => $importer['uid'],
+                               'created'  => DateTimeFormat::utcNow(),
+                               'url'      => $url,
+                               'nurl'     => Strings::normaliseLink($url),
+                               'name'     => $name,
+                               'nick'     => $nick,
+                               'photo'    => $photo,
+                               'network'  => $network,
+                               'rel'      => self::FOLLOWER,
+                               'blocked'  => 0,
+                               'readonly' => 0,
+                               'pending'  => 1,
+                               'writable' => 1,
+                       ]);
 
                        $contact_record = [
                                'id' => DBA::lastInsertId(),
@@ -2220,20 +2237,16 @@ class Contact extends BaseObject
                                                'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
                                                'otype'        => 'intro'
                                        ]);
-
                                }
                        } elseif (DBA::isResult($user) && in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) {
                                $condition = ['uid' => $importer['uid'], 'url' => $url, 'pending' => true];
                                DBA::update('contact', ['pending' => false], $condition);
 
-                               $contact = DBA::selectFirst('contact', ['url', 'network', 'hub-verify'], ['id' => $contact_record['id']]);
-                               $protocol = self::getProtocol($contact['url'], $contact['network']);
-
-                               if ($protocol == Protocol::ACTIVITYPUB) {
-                                       ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
-                               }
+                               return true;
                        }
                }
+
+               return null;
        }
 
        public static function removeFollower($importer, $contact, array $datarray = [], $item = "")
index a854b1c9e019d3c6c11e753eb1cd94a13aabf6bd..9068943dfe17dc0e58b65e012ced357db0eecfe0 100644 (file)
@@ -877,32 +877,35 @@ class Profile
                return '';
        }
 
-       public static function getTabs($a, $is_owner = false, $nickname = null)
+    /**
+     * @param App    $a
+     * @param string $current
+     * @param bool   $is_owner
+     * @param string $nickname
+     * @return string
+     * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+     */
+       public static function getTabs(App $a, string $current, bool $is_owner, string $nickname = null)
        {
                if (is_null($nickname)) {
                        $nickname = $a->user['nickname'];
                }
 
-               $tab = false;
-               if (!empty($_GET['tab'])) {
-                       $tab = Strings::escapeTags(trim($_GET['tab']));
-               }
-
-               $url = System::baseUrl() . '/profile/' . $nickname;
+               $baseProfileUrl = System::baseUrl() . '/profile/' . $nickname;
 
                $tabs = [
                        [
                                'label' => L10n::t('Status'),
-                               'url'   => $url,
-                               'sel'   => !$tab && $a->argv[0] == 'profile' ? 'active' : '',
+                               'url'   => $baseProfileUrl,
+                               'sel'   => !$current ? 'active' : '',
                                'title' => L10n::t('Status Messages and Posts'),
                                'id'    => 'status-tab',
                                'accesskey' => 'm',
                        ],
                        [
                                'label' => L10n::t('Profile'),
-                               'url'   => $url . '/?tab=profile',
-                               'sel'   => $tab == 'profile' ? 'active' : '',
+                               'url'   => $baseProfileUrl . '/?tab=profile',
+                               'sel'   => $current == 'profile' ? 'active' : '',
                                'title' => L10n::t('Profile Details'),
                                'id'    => 'profile-tab',
                                'accesskey' => 'r',
@@ -910,7 +913,7 @@ class Profile
                        [
                                'label' => L10n::t('Photos'),
                                'url'   => System::baseUrl() . '/photos/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'photos' ? 'active' : '',
+                               'sel'   => $current == 'photos' ? 'active' : '',
                                'title' => L10n::t('Photo Albums'),
                                'id'    => 'photo-tab',
                                'accesskey' => 'h',
@@ -918,7 +921,7 @@ class Profile
                        [
                                'label' => L10n::t('Videos'),
                                'url'   => System::baseUrl() . '/videos/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'videos' ? 'active' : '',
+                               'sel'   => $current == 'videos' ? 'active' : '',
                                'title' => L10n::t('Videos'),
                                'id'    => 'video-tab',
                                'accesskey' => 'v',
@@ -930,7 +933,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Events'),
                                'url'   => System::baseUrl() . '/events',
-                               'sel'   => !$tab && $a->argv[0] == 'events' ? 'active' : '',
+                               'sel'   => $current == 'events' ? 'active' : '',
                                'title' => L10n::t('Events and Calendar'),
                                'id'    => 'events-tab',
                                'accesskey' => 'e',
@@ -941,7 +944,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Events'),
                                'url'   => System::baseUrl() . '/cal/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'cal' ? 'active' : '',
+                               'sel'   => $current == 'cal' ? 'active' : '',
                                'title' => L10n::t('Events and Calendar'),
                                'id'    => 'events-tab',
                                'accesskey' => 'e',
@@ -952,7 +955,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Personal Notes'),
                                'url'   => System::baseUrl() . '/notes',
-                               'sel'   => !$tab && $a->argv[0] == 'notes' ? 'active' : '',
+                               'sel'   => $current == 'notes' ? 'active' : '',
                                'title' => L10n::t('Only You Can See This'),
                                'id'    => 'notes-tab',
                                'accesskey' => 't',
@@ -969,18 +972,18 @@ class Profile
                        ];
                }
 
-               if (!$is_owner && empty($a->profile['hide-friends'])) {
+               if ($is_owner || empty($a->profile['hide-friends'])) {
                        $tabs[] = [
                                'label' => L10n::t('Contacts'),
-                               'url'   => System::baseUrl() . '/viewcontacts/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'viewcontacts' ? 'active' : '',
+                               'url'   => $baseProfileUrl . '/contacts',
+                               'sel'   => $current == 'contacts' ? 'active' : '',
                                'title' => L10n::t('Contacts'),
                                'id'    => 'viewcontacts-tab',
                                'accesskey' => 'k',
                        ];
                }
 
-               $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $tab, 'tabs' => $tabs];
+               $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $current, 'tabs' => $tabs];
                Hook::callAll('profile_tabs', $arr);
 
                $tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
index 8d50e964ea3620b147218a2b2d6d804abd1e1072..847455f498927f0d6444c5a88dcbc858cd780c18 100644 (file)
@@ -266,6 +266,7 @@ class Contact extends BaseModule
                $a = self::getApp();
 
                $nets = defaults($_GET, 'nets', '');
+               $rel  = defaults($_GET, 'rel' , '');
 
                if (empty($a->page['aside'])) {
                        $a->page['aside'] = '';
@@ -321,6 +322,7 @@ class Contact extends BaseModule
                        $findpeople_widget = '';
                        $follow_widget = '';
                        $networks_widget = '';
+                       $rel_widget = '';
                } else {
                        $vcard_widget = '';
                        $findpeople_widget = Widget::findPeople();
@@ -331,6 +333,7 @@ class Contact extends BaseModule
                        }
 
                        $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
+                       $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
                }
 
                if ($contact['uid'] != 0) {
@@ -339,7 +342,7 @@ class Contact extends BaseModule
                        $groups_widget = null;
                }
 
-               $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget;
+               $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget;
 
                $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
                $a->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
@@ -678,6 +681,7 @@ class Contact extends BaseModule
 
                $search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
                $nets   = Strings::escapeTags(trim(defaults($_GET, 'nets'  , '')));
+               $rel    = Strings::escapeTags(trim(defaults($_GET, 'rel'   , '')));
 
                $tabs = [
                        [
@@ -747,6 +751,12 @@ class Contact extends BaseModule
                        $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
                }
 
+               switch ($rel) {
+                       case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
+                       case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
+                       case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
+               }
+
                $sql_extra .=  " AND NOT `deleted` ";
 
                $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
@@ -777,6 +787,13 @@ class Contact extends BaseModule
                        }
                }
 
+               switch ($rel) {
+                       case 'followers': $header = L10n::t('Followers'); break;
+                       case 'following': $header = L10n::t('Following'); break;
+                       case 'mutuals':   $header = L10n::t('Mutual friends'); break;
+                       default:          $header = L10n::t('Contacts');
+               }
+
                switch ($type) {
                        case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
                        case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
index fceea726b4cfe863d203673dbf11095810c0595b..1a515073ddd845adde68edbeac15dcabf3fbcf50 100644 (file)
@@ -178,12 +178,9 @@ class Profile extends BaseModule
                }
 
                if (!$update) {
-                       $tab = false;
-                       if (!empty($_GET['tab'])) {
-                               $tab = Strings::escapeTags(trim($_GET['tab']));
-                       }
+            $tab = Strings::escapeTags(trim(defaults($_GET, 'tab', '')));
 
-                       $o .= ProfileModel::getTabs($a, $is_owner, $a->profile['nickname']);
+                       $o .= ProfileModel::getTabs($a, $tab, $is_owner, $a->profile['nickname']);
 
                        if ($tab === 'profile') {
                                $o .= ProfileModel::getAdvanced($a);
diff --git a/src/Module/Profile/Contacts.php b/src/Module/Profile/Contacts.php
new file mode 100644 (file)
index 0000000..346622b
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+
+namespace Friendica\Module\Profile;
+
+use Friendica\BaseModule;
+use Friendica\Content\ContactSelector;
+use Friendica\Content\Nav;
+use Friendica\Content\Pager;
+use Friendica\Core\Config;
+use Friendica\Core\L10n;
+use Friendica\Core\Protocol;
+use Friendica\Core\Renderer;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Model\Profile;
+use Friendica\Util\Proxy as ProxyUtils;
+
+class Contacts extends BaseModule
+{
+       public static function content()
+       {
+               if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+                       throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
+               }
+
+               $a = self::getApp();
+
+               //@TODO: Get value from router parameters
+               $nickname = $a->argv[1];
+               $type = defaults($a->argv, 3, 'all');
+
+               Nav::setSelected('home');
+
+               $user = DBA::selectFirst('user', [], ['nickname' => $nickname, 'blocked' => false]);
+               if (!DBA::isResult($user)) {
+                       throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
+               }
+
+               $a->data['user'] = $user;
+               $a->profile_uid  = $user['uid'];
+
+               Profile::load($a, $nickname);
+
+               $is_owner = $a->profile['profile_uid'] == local_user();
+
+               // tabs
+               $o = Profile::getTabs($a, 'contacts', $is_owner, $nickname);
+
+               if (!count($a->profile) || $a->profile['hide-friends']) {
+                       notice(L10n::t('Permission denied.') . EOL);
+                       return $o;
+               }
+
+               $condition = [
+                       'uid'     => $a->profile['uid'],
+                       'blocked' => false,
+                       'pending' => false,
+                       'hidden'  => false,
+                       'archive' => false,
+                       'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::FEED]
+               ];
+
+               switch ($type) {
+                       case 'followers': $condition['rel'] = [1, 3]; break;
+                       case 'following': $condition['rel'] = [2, 3]; break;
+                       case 'mutuals': $condition['rel'] = 3; break;
+               }
+
+               $total = DBA::count('contact', $condition);
+
+               $pager = new Pager($a->query_string);
+
+               $params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
+
+               $contacts_stmt = DBA::select('contact', [], $condition, $params);
+
+               if (!DBA::isResult($contacts_stmt)) {
+                       info(L10n::t('No contacts.') . EOL);
+                       return $o;
+               }
+
+               $contacts = [];
+
+               while ($contact = DBA::fetch($contacts_stmt)) {
+                       if ($contact['self']) {
+                               continue;
+                       }
+
+                       $contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
+
+                       $contacts[] = [
+                               'id'           => $contact['id'],
+                               'img_hover'    => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
+                               'photo_menu'   => Contact::photoMenu($contact),
+                               'thumb'        => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
+                               'name'         => substr($contact_details['name'], 0, 20),
+                               'username'     => $contact_details['name'],
+                               'details'      => $contact_details['location'],
+                               'tags'         => $contact_details['keywords'],
+                               'about'        => $contact_details['about'],
+                               'account_type' => Contact::getAccountType($contact_details),
+                               'url'          => Contact::magicLink($contact['url']),
+                               'sparkle'      => '',
+                               'itemurl'      => $contact_details['addr'] ? : $contact['url'],
+                               'network'      => ContactSelector::networkToName($contact['network'], $contact['url']),
+                       ];
+               }
+
+               DBA::close($contacts_stmt);
+
+               switch ($type) {
+                       case 'followers':    $title = L10n::tt('Follower (%s)', 'Followers (%s)', $total); break;
+                       case 'following':    $title = L10n::tt('Following (%s)', 'Following (%s)', $total); break;
+                       case 'mutuals':      $title = L10n::tt('Mutual friend (%s)', 'Mutual friends (%s)', $total); break;
+
+                       case 'all': default: $title = L10n::tt('Contact (%s)', 'Contacts (%s)', $total); break;
+               }
+
+               $tpl = Renderer::getMarkupTemplate('profile/contacts.tpl');
+               $o .= Renderer::replaceMacros($tpl, [
+                       '$title'    => $title,
+                       '$nickname' => $nickname,
+                       '$type'     => $type,
+
+                       '$all_label' => L10n::t('All contacts'),
+                       '$followers_label' => L10n::t('Followers'),
+                       '$following_label' => L10n::t('Following'),
+                       '$mutuals_label' => L10n::t('Mutual friends'),
+
+                       '$contacts' => $contacts,
+                       '$paginate' => $pager->renderFull($total),
+               ]);
+
+               return $o;
+       }
+}
index 045c48a4c1ced304920444f465f559bd6973d65a..fd84f494e2d597f172269b64e5790d1a5e4c420a 100644 (file)
@@ -530,7 +530,7 @@ class Processor
                        DBA::update('contact', ['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]);
                        $contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
                } else {
-                       $contact = false;
+                       $contact = [];
                }
 
                $item = ['author-id' => Contact::getIdForURL($activity['actor']),
@@ -541,7 +541,11 @@ class Processor
                // Ensure that the contact has got the right network type
                self::switchContact($item['author-id']);
 
-               Contact::addRelationship($owner, $contact, $item, '', false, $note);
+               $result = Contact::addRelationship($owner, $contact, $item, false, $note);
+               if ($result === true) {
+                       ActivityPub\Transmitter::sendContactAccept($item['author-link'], $item['author-id'], $owner['uid']);
+               }
+
                $cid = Contact::getIdForURL($activity['actor'], $uid);
                if (empty($cid)) {
                        return;
index 1a2c13533070440dbec2d5a7fd8e95102fe82cc7..06dbaee71f81f1c619a9e857dde8dd80c465cb01 100644 (file)
@@ -1567,13 +1567,16 @@ class Transmitter
                        'id' => System::baseUrl() . '/activity/' . System::createGUID(),
                        'type' => 'Accept',
                        'actor' => $owner['url'],
-                       'object' => ['id' => $id, 'type' => 'Follow',
+                       'object' => [
+                               'id' => (string)$id,
+                               'type' => 'Follow',
                                'actor' => $profile['url'],
-                               'object' => $owner['url']],
+                               'object' => $owner['url']
+                       ],
                        'instrument' => self::getService(),
                        'to' => [$profile['url']]];
 
-               Logger::log('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
+               Logger::debug('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id);
 
                $signed = LDSignature::sign($data, $owner);
                HTTPSignature::transmit($signed, $profile['inbox'], $uid);
@@ -1597,13 +1600,16 @@ class Transmitter
                        'id' => System::baseUrl() . '/activity/' . System::createGUID(),
                        'type' => 'Reject',
                        'actor' => $owner['url'],
-                       'object' => ['id' => $id, 'type' => 'Follow',
+                       'object' => [
+                               'id' => (string)$id,
+                               'type' => 'Follow',
                                'actor' => $profile['url'],
-                               'object' => $owner['url']],
+                               'object' => $owner['url']
+                       ],
                        'instrument' => self::getService(),
                        'to' => [$profile['url']]];
 
-               Logger::log('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
+               Logger::debug('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id);
 
                $signed = LDSignature::sign($data, $owner);
                HTTPSignature::transmit($signed, $profile['inbox'], $uid);
index f74b572949d3f140285cfdb5095ca9d9facbb38d..01159b1baeea7c8cfea232462be993b0c78acaa3 100644 (file)
@@ -2247,13 +2247,12 @@ class DFRN
                        // The functions below are partly used by ostatus.php as well - where we have this variable
                        $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"]));
                        $contact = $r[0];
-                       $nickname = $contact["nick"];
 
                        // Big question: Do we need these functions? They were part of the "consume_feed" function.
                        // This function once was responsible for DFRN and OStatus.
                        if (activity_match($item["verb"], ACTIVITY_FOLLOW)) {
                                Logger::log("New follower");
-                               Contact::addRelationship($importer, $contact, $item, $nickname);
+                               Contact::addRelationship($importer, $contact, $item);
                                return false;
                        }
                        if (activity_match($item["verb"], ACTIVITY_UNFOLLOW)) {
@@ -2263,7 +2262,7 @@ class DFRN
                        }
                        if (activity_match($item["verb"], ACTIVITY_REQ_FRIEND)) {
                                Logger::log("New friend request");
-                               Contact::addRelationship($importer, $contact, $item, $nickname, true);
+                               Contact::addRelationship($importer, $contact, $item, true);
                                return false;
                        }
                        if (activity_match($item["verb"], ACTIVITY_UNFRIEND)) {
index dec5c4c80b021a572da0d951304a7234dcbab6bc..81e5c19db8cc42d73eaf2c5e7adc2f2f74c2cb53 100644 (file)
@@ -417,13 +417,6 @@ class OStatus
                                $author = self::fetchAuthor($xpath, $entry, $importer, $contact, $stored);
                        }
 
-                       $value = XML::getFirstNodeValue($xpath, 'atom:author/poco:preferredUsername/text()', $entry);
-                       if ($value != "") {
-                               $nickname = $value;
-                       } else {
-                               $nickname = $author["author-name"];
-                       }
-
                        $item = array_merge($header, $author);
 
                        $item["uri"] = XML::getFirstNodeValue($xpath, 'atom:id/text()', $entry);
@@ -463,7 +456,7 @@ class OStatus
                        }
 
                        if ($item["verb"] == ACTIVITY_FOLLOW) {
-                               Contact::addRelationship($importer, $contact, $item, $nickname);
+                               Contact::addRelationship($importer, $contact, $item);
                                continue;
                        }
 
@@ -1515,7 +1508,7 @@ class OStatus
                                $author->appendChild($urls);
                        }
 
-                       XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl()."/viewcontacts/".$owner["nick"]]);
+                       XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl() . "/profile/" . $owner["nick"] . "/contacts/followers"]);
                        XML::addElement($doc, $author, "statusnet:profile_info", "", ["local_id" => $owner["uid"]]);
 
                        if ($profile["publish"]) {
index 69973f4febf7d95757189a7e36b8a3ffa4c04b10..926fa1437d46aa1cb4efac0c8d79fbdaf1527d2d 100644 (file)
@@ -68,9 +68,16 @@ class JsonLD
                }
                catch (Exception $e) {
                        $normalized = false;
-                       Logger::error('normalise error');
-                       // Sooner or later we should log some details as well - but currently this leads to memory issues
-                       // Logger::log('normalise error:' . substr(print_r($e, true), 0, 10000), Logger::DEBUG);
+                       $messages = [];
+                       $currentException = $e;
+                       do {
+                               $messages[] = $currentException->getMessage();
+                       } while($currentException = $currentException->getPrevious());
+
+                       Logger::warning('JsonLD normalize error');
+                       Logger::notice('JsonLD normalize error', ['messages' => $messages]);
+                       Logger::info('JsonLD normalize error', ['trace' => $e->getTraceAsString()]);
+                       Logger::debug('JsonLD normalize error', ['jsonobj' => $jsonobj]);
                }
 
                return $normalized;
diff --git a/view/templates/profile/contacts.tpl b/view/templates/profile/contacts.tpl
new file mode 100644 (file)
index 0000000..4e78a7a
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="generic-page-wrapper">
+       {{include file="section_title.tpl"}}
+
+       <ul role="menubar" class="tabs">
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts" class="tab button{{if !$type || $type == 'all'}} active{{/if}}">{{$all_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/followers" class="tab button{{if $type == 'followers'}} active{{/if}}">{{$followers_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/following" class="tab button{{if $type == 'following'}} active{{/if}}">{{$following_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/mutuals" class="tab button{{if $type == 'mutuals'}} active{{/if}}">{{$mutuals_label}}</a></li>
+       </ul>
+
+       <div id="viewcontact_wrapper-{{$id}}">
+{{foreach $contacts as $contact}}
+               {{include file="contact_template.tpl"}}
+{{/foreach}}
+       </div>
+       <div class="clear"></div>
+       <div id="view-contact-end"></div>
+
+       {{$paginate nofilter}}
+</div>
index f586d6e04d740fe705200e2364a8d25502bdc38e..7e66570ad57cddd532e82669e4c2d49197892818 100644 (file)
@@ -680,10 +680,6 @@ input#dfrn-url {
        clear: both;
 }
 
-
-#viewcontacts {
-       margin-top: 15px;
-}
 #profile-edit-default-desc {
        color: #FF0000;
        border: 1px solid #FF8888;
index 4a72628352891da6a7c77a7e4e8fc3bce158012f..2a5706c85968c5098919904e444232b468510d2e 100644 (file)
@@ -2317,7 +2317,7 @@ ul.dropdown-menu li:hover {
 .manage-content-wrapper, .notes-content-wrapper,
 .message-content-wrapper, .apps-content-wrapper,
 #adminpage, .delegate-content-wrapper, .uexport-content-wrapper,
-.viewcontacts-content-wrapper, .dfrn_request-content-wrapper,
+.dfrn_request-content-wrapper,
 .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper,
 .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper,
 .fsuggest-content-wrapper {
@@ -3547,7 +3547,7 @@ section .profile-match-wrapper {
                right: 10px;
        }
 
-       .generic-page-wrapper, .profile_photo-content-wrapper, .videos-content-wrapper, .suggest-content-wrapper, .common-content-wrapper, .help-content-wrapper, .allfriends-content-wrapper, .match-content-wrapper, .dirfind-content-wrapper, .directory-content-wrapper, .manage-content-wrapper, .notes-content-wrapper, .message-content-wrapper, .apps-content-wrapper, #adminpage, .delegate-content-wrapper, .uexport-content-wrapper, .viewcontacts-content-wrapper, .dfrn_request-content-wrapper, .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper, .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper, .fsuggest-content-wrapper {
+       .generic-page-wrapper, .profile_photo-content-wrapper, .videos-content-wrapper, .suggest-content-wrapper, .common-content-wrapper, .help-content-wrapper, .allfriends-content-wrapper, .match-content-wrapper, .dirfind-content-wrapper, .directory-content-wrapper, .manage-content-wrapper, .notes-content-wrapper, .message-content-wrapper, .apps-content-wrapper, #adminpage, .delegate-content-wrapper, .uexport-content-wrapper, .dfrn_request-content-wrapper, .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper, .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper, .fsuggest-content-wrapper {
                border-radius: 0;
                padding: 10px;
        }
diff --git a/view/theme/frio/templates/profile/contacts.tpl b/view/theme/frio/templates/profile/contacts.tpl
new file mode 100644 (file)
index 0000000..69d34d0
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="generic-page-wrapper">
+       {{include file="section_title.tpl"}}
+
+       <ul class="nav nav-tabs">
+               <li role="presentation"{{if !$type || $type == 'all'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts">{{$all_label}}</a></li>
+               <li role="presentation"{{if $type == 'followers'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/followers">{{$followers_label}}</a></li>
+               <li role="presentation"{{if $type == 'following'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/following">{{$following_label}}</a></li>
+               <li role="presentation"{{if $type == 'mutuals'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/mutuals">{{$mutuals_label}}</a></li>
+       </ul>
+
+       <ul id="viewcontact_wrapper{{if $id}}-{{$id}}{{/if}}" class="viewcontact_wrapper media-list">
+{{foreach $contacts as $contact}}
+               <li>{{include file="contact_template.tpl"}}</li>
+{{/foreach}}
+       </ul>
+       <div class="clear"></div>
+       <div id="view-contact-end"></div>
+
+       {{$paginate nofilter}}
+</div>
index 9bbf00c78030af7d751e570a2d0e933a3c6aec0f..fec5d2bda57889c1d13e8c30062237e8ff94b4c6 100644 (file)
@@ -2834,10 +2834,6 @@ margin-left: 0px;
        clear: both;
 }
 
-#viewcontacts {
-       margin-top: 15px;
-}
-
 .contact-entry-wrapper .contact-entry-photo-wrapper {
        float: left;
        margin-right: 10px;