]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Contact.php
Merge pull request #10478 from annando/notice
[friendica.git] / src / Model / Contact.php
index 8b28575edb9fc6439a75bc6dae5b28817e1b68ac..0184861c3c2d8403750bb075d5c69a03781cd43c 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2021, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -185,6 +185,8 @@ class Contact
                        $fields['gsid'] = GServer::getID($fields['baseurl'], true);
                }
 
+               $fields['uri-id'] = ItemURI::getIdByURI($fields['url']);
+
                if (empty($fields['created'])) {
                        $fields['created'] = DateTimeFormat::utcNow();
                }
@@ -264,14 +266,14 @@ class Contact
                        $condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, $uid];
                        $contact = DBA::selectFirst('contact', $fields, $condition, $options);
                }
-               
+
                if (!DBA::isResult($contact)) {
                        return [];
                }
 
                // Update the contact in the background if needed
                $updated = max($contact['success_update'], $contact['created'], $contact['updated'], $contact['last-update'], $contact['failure_update']);
-               if (($updated < DateTimeFormat::utc('now -7 days')) && in_array($contact['network'], Protocol::FEDERATED)) {
+               if (($updated < DateTimeFormat::utc('now -7 days')) && in_array($contact['network'], Protocol::FEDERATED) && !self::isLocalById($contact['id'])) {
                        Worker::add(PRIORITY_LOW, "UpdateContact", $contact['id']);
                }
 
@@ -307,7 +309,7 @@ class Contact
                }
 
                $contact = self::getByURL($url, $update, $fields);
-               if (!empty($contact['id'])) {           
+               if (!empty($contact['id'])) {
                        $contact['cid'] = 0;
                        $contact['zid'] = $contact['id'];
                }
@@ -566,17 +568,13 @@ class Contact
         */
        public static function createSelfFromUserId($uid)
        {
-               // Only create the entry if it doesn't exist yet
-               if (DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
-                       return true;
-               }
-
-               $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'], ['uid' => $uid]);
+               $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
+                       ['uid' => $uid, 'account_expired' => false]);
                if (!DBA::isResult($user)) {
                        return false;
                }
 
-               $return = DBA::insert('contact', [
+               $contact = [
                        'uid'         => $user['uid'],
                        'created'     => DateTimeFormat::utcNow(),
                        'self'        => 1,
@@ -601,7 +599,23 @@ class Contact
                        'uri-date'    => DateTimeFormat::utcNow(),
                        'avatar-date' => DateTimeFormat::utcNow(),
                        'closeness'   => 0
-               ]);
+               ];
+
+               $return = true;
+
+               // Only create the entry if it doesn't exist yet
+               if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) {
+                       $return = DBA::insert('contact', $contact);
+               }
+
+               // Create the public contact
+               if (!DBA::exists('contact', ['nurl' => $contact['nurl'], 'uid' => 0])) {
+                       $contact['self']   = false;
+                       $contact['uid']    = 0;
+                       $contact['prvkey'] = null;
+
+                       DBA::insert('contact', $contact, Database::INSERT_IGNORE);
+               }
 
                return $return;
        }
@@ -611,29 +625,30 @@ class Contact
         *
         * @param int     $uid
         * @param boolean $update_avatar Force the avatar update
+        * @return bool   "true" if updated
         * @throws HTTPException\InternalServerErrorException
         */
        public static function updateSelfFromUserID($uid, $update_avatar = false)
        {
                $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey',
                        'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
-                       'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco'];
+                       'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
                $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
                if (!DBA::isResult($self)) {
-                       return;
+                       return false;
                }
 
                $fields = ['nickname', 'page-flags', 'account-type', 'prvkey', 'pubkey'];
-               $user = DBA::selectFirst('user', $fields, ['uid' => $uid]);
+               $user = DBA::selectFirst('user', $fields, ['uid' => $uid, 'account_expired' => false]);
                if (!DBA::isResult($user)) {
-                       return;
+                       return false;
                }
 
                $fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
                        'country-name', 'pub_keywords', 'xmpp', 'net-publish'];
                $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
                if (!DBA::isResult($profile)) {
-                       return;
+                       return false;
                }
 
                $file_suffix = 'jpg';
@@ -642,7 +657,7 @@ class Contact
                        'avatar-date' => $self['avatar-date'], 'location' => Profile::formatLocation($profile),
                        'about' => $profile['about'], 'keywords' => $profile['pub_keywords'],
                        'contact-type' => $user['account-type'], 'prvkey' => $user['prvkey'],
-                       'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp']];
+                       'pubkey' => $user['pubkey'], 'xmpp' => $profile['xmpp'], 'network' => Protocol::DFRN];
 
                // it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
                $fields['url'] = DI::baseUrl() . '/profile/' . $user['nickname'];
@@ -703,6 +718,8 @@ class Contact
                        DBA::update('contact', $fields, ['id' => $self['id']]);
 
                        // Update the public contact as well
+                       $fields['prvkey'] = null;
+                       $fields['self']   = false;
                        DBA::update('contact', $fields, ['uid' => 0, 'nurl' => $self['nurl']]);
 
                        // Update the profile
@@ -710,6 +727,8 @@ class Contact
                                'thumb' => DI::baseUrl() . '/photo/avatar/' . $uid .'.' . $file_suffix];
                        DBA::update('profile', $fields, ['uid' => $uid]);
                }
+
+               return $update;
        }
 
        /**
@@ -1053,12 +1072,12 @@ class Contact
                        return 0;
                }
 
-               $contact = self::getByURL($url, false, ['id', 'network'], $uid);
+               $contact = self::getByURL($url, false, ['id', 'network', 'uri-id'], $uid);
 
                if (!empty($contact)) {
                        $contact_id = $contact["id"];
 
-                       if (empty($update)) {
+                       if (empty($update) && (!empty($contact['uri-id']) || is_bool($update))) {
                                Logger::debug('Contact found', ['url' => $url, 'uid' => $uid, 'update' => $update, 'cid' => $contact_id]);
                                return $contact_id;
                        }
@@ -1086,7 +1105,7 @@ class Contact
                if (($uid == 0) && (empty($data['network']) || ($data['network'] == Protocol::PHANTOM))) {
                        // Fetch data for the public contact via the first found personal contact
                        /// @todo Check if this case can happen at all (possibly with mail accounts?)
-                       $fields = ['name', 'nick', 'url', 'addr', 'alias', 'avatar', 'contact-type',
+                       $fields = ['name', 'nick', 'url', 'addr', 'alias', 'avatar', 'header', 'contact-type',
                                'keywords', 'location', 'about', 'unsearchable', 'batch', 'notify', 'poll',
                                'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid'];
 
@@ -1273,7 +1292,7 @@ class Contact
         *
         * @param string $contact_url Contact URL
         * @param bool   $thread_mode
-        * @param int    $update      Update mode 
+        * @param int    $update      Update mode
         * @param int    $parent      Item parent ID for the update mode
         * @return string posts in HTML
         * @throws \Exception
@@ -1288,7 +1307,7 @@ class Contact
         *
         * @param int  $cid         Contact ID
         * @param bool $thread_mode
-        * @param int  $update      Update mode 
+        * @param int  $update      Update mode
         * @param int  $parent     Item parent ID for the update mode
         * @return string posts in HTML
         * @throws \Exception
@@ -1346,7 +1365,7 @@ class Contact
                        $o = '';
                }
 
-               if ($thread_mode) {             
+               if ($thread_mode) {
                        $items = Post::toArray(Post::selectForUser(local_user(), ['uri-id', 'gravity', 'parent-uri-id', 'thr-parent-id', 'author-id'], $condition, $params));
 
                        $o .= conversation($a, $items, 'contacts', $update, false, 'commented', local_user());
@@ -1480,67 +1499,46 @@ class Contact
         * @param bool  $no_update Don't perfom an update if no cached avatar was found
         * @return string photo path
         */
-       private static function getAvatarPath(array $contact, string $field, string $size, string $avatar, $no_update = false)
+       private static function getAvatarPath(array $contact, string $size, $no_update = false)
        {
-               if (!empty($contact)) {
-                       $contact = self::checkAvatarCacheByArray($contact, $no_update);
-                       if (!empty($contact[$field])) {
-                               $avatar = $contact[$field];
-                       }
-               }
-
-               if ($no_update && empty($avatar) && !empty($contact['avatar'])) {
-                       $avatar = $contact['avatar'];
-               }
-
-               if (empty($avatar)) {
-                       $avatar = self::getDefaultAvatar([], $size);
-               }
-
-               if (Proxy::isLocalImage($avatar)) {
-                       return $avatar;
-               } else {
-                       return Proxy::proxifyUrl($avatar, false, $size);
-               }
+               $contact = self::checkAvatarCacheByArray($contact, $no_update);
+               return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
        }
 
        /**
         * Return the photo path for a given contact array
         *
         * @param array  $contact   Contact array
-        * @param string $avatar    Avatar path that is displayed when no photo had been found
         * @param bool   $no_update Don't perfom an update if no cached avatar was found
         * @return string photo path
         */
-       public static function getPhoto(array $contact, string $avatar = '', bool $no_update = false)
+       public static function getPhoto(array $contact, bool $no_update = false)
        {
-               return self::getAvatarPath($contact, 'photo', Proxy::SIZE_SMALL, $avatar, $no_update);
+               return self::getAvatarPath($contact, Proxy::SIZE_SMALL, $no_update);
        }
 
        /**
         * Return the photo path (thumb size) for a given contact array
         *
         * @param array  $contact   Contact array
-        * @param string $avatar    Avatar path that is displayed when no photo had been found
         * @param bool   $no_update Don't perfom an update if no cached avatar was found
         * @return string photo path
         */
-       public static function getThumb(array $contact, string $avatar = '', bool $no_update = false)
+       public static function getThumb(array $contact, bool $no_update = false)
        {
-               return self::getAvatarPath($contact, 'thumb', Proxy::SIZE_THUMB, $avatar, $no_update);
+               return self::getAvatarPath($contact, Proxy::SIZE_THUMB, $no_update);
        }
 
        /**
         * Return the photo path (micro size) for a given contact array
         *
         * @param array  $contact   Contact array
-        * @param string $avatar    Avatar path that is displayed when no photo had been found
         * @param bool   $no_update Don't perfom an update if no cached avatar was found
         * @return string photo path
         */
-       public static function getMicro(array $contact, string $avatar = '', bool $no_update = false)
+       public static function getMicro(array $contact, bool $no_update = false)
        {
-               return self::getAvatarPath($contact, 'micro', Proxy::SIZE_MICRO, $avatar, $no_update);
+               return self::getAvatarPath($contact, Proxy::SIZE_MICRO, $no_update);
        }
 
        /**
@@ -1597,7 +1595,7 @@ class Contact
         *
         * @param array $contact  contact array
         * @param string $size    Size of the avatar picture
-        * @return void
+        * @return string avatar URL
         */
        public static function getDefaultAvatar(array $contact, string $size)
        {
@@ -1606,12 +1604,12 @@ class Contact
                                $avatar['size'] = 48;
                                $default = self::DEFAULT_AVATAR_MICRO;
                                break;
-       
+
                        case Proxy::SIZE_THUMB:
                                $avatar['size'] = 80;
                                $default = self::DEFAULT_AVATAR_THUMB;
                                break;
-       
+
                        case Proxy::SIZE_SMALL:
                        default:
                                $avatar['size'] = 300;
@@ -1645,6 +1643,99 @@ class Contact
                return DI::baseUrl() . $default;
        }
 
+       /**
+        * Get avatar link for given contact id
+        *
+        * @param integer $cid     contact id
+        * @param string  $size    One of the ProxyUtils::SIZE_* constants
+        * @param string  $updated Contact update date
+        * @return string avatar link
+        */
+       public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = ''):string
+       {
+               // We have to fetch the "updated" variable when it wasn't provided
+               // The parameter can be provided to improve performance
+               if (empty($updated)) {
+                       $contact = self::getById($cid, ['updated']);
+                       $updated = $contact['updated'] ?? '';
+               }
+
+               $url = DI::baseUrl() . '/photo/contact/';
+               switch ($size) {
+                       case Proxy::SIZE_MICRO:
+                               $url .= Proxy::PIXEL_MICRO . '/';
+                               break;
+                       case Proxy::SIZE_THUMB:
+                               $url .= Proxy::PIXEL_THUMB . '/';
+                               break;
+                       case Proxy::SIZE_SMALL:
+                               $url .= Proxy::PIXEL_SMALL . '/';
+                               break;
+                       case Proxy::SIZE_MEDIUM:
+                               $url .= Proxy::PIXEL_MEDIUM . '/';
+                               break;
+                       case Proxy::SIZE_LARGE:
+                               $url .= Proxy::PIXEL_LARGE . '/';
+                               break;
+               }
+               return $url . $cid . ($updated ? '?ts=' . strtotime($updated) : '');
+       }
+
+       /**
+        * Get avatar link for given contact URL
+        *
+        * @param string  $url  contact url
+        * @param integer $uid  user id
+        * @param string  $size One of the ProxyUtils::SIZE_* constants
+        * @return string avatar link
+        */
+       public static function getAvatarUrlForUrl(string $url, int $uid, string $size = ''):string
+       {
+               $condition = ["`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)",
+                       Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0];
+               $contact = self::selectFirst(['id', 'updated'], $condition);
+               return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? '');
+       }
+
+       /**
+        * Get header link for given contact id
+        *
+        * @param integer $cid     contact id
+        * @param string  $size    One of the ProxyUtils::SIZE_* constants
+        * @param string  $updated Contact update date
+        * @return string header link
+        */
+       public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = ''):string
+       {
+               // We have to fetch the "updated" variable when it wasn't provided
+               // The parameter can be provided to improve performance
+               if (empty($updated)) {
+                       $contact = self::getById($cid, ['updated']);
+                       $updated = $contact['updated'] ?? '';
+               }
+
+               $url = DI::baseUrl() . '/photo/header/';
+               switch ($size) {
+                       case Proxy::SIZE_MICRO:
+                               $url .= Proxy::PIXEL_MICRO . '/';
+                               break;
+                       case Proxy::SIZE_THUMB:
+                               $url .= Proxy::PIXEL_THUMB . '/';
+                               break;
+                       case Proxy::SIZE_SMALL:
+                               $url .= Proxy::PIXEL_SMALL . '/';
+                               break;
+                       case Proxy::SIZE_MEDIUM:
+                               $url .= Proxy::PIXEL_MEDIUM . '/';
+                               break;
+                       case Proxy::SIZE_LARGE:
+                               $url .= Proxy::PIXEL_LARGE . '/';
+                               break;
+               }
+
+               return $url . $cid . ($updated ? '?ts=' . strtotime($updated) : '');
+       }
+
        /**
         * Updates the avatar links in a contact only if needed
         *
@@ -1719,7 +1810,7 @@ class Contact
                                        $contact['thumb'] ?? '',
                                        $contact['micro'] ?? '',
                                ];
-               
+
                                foreach ($data as $image_uri) {
                                        $image_rid = Photo::ridFromURI($image_uri);
                                        if ($image_rid && !Photo::exists(['resource-id' => $image_rid, 'uid' => $uid])) {
@@ -1797,7 +1888,7 @@ class Contact
        {
                if (Strings::normaliseLink($new_url) != Strings::normaliseLink($old_url)) {
                        Logger::notice('New URL differs from old URL', ['old' => $old_url, 'new' => $new_url]);
-                       // @todo It is to decide what to do when the URL is changed
+                       return;
                }
 
                if (!DBA::update('contact', $fields, ['id' => $id])) {
@@ -1932,14 +2023,23 @@ class Contact
                // These fields aren't updated by this routine:
                // 'xmpp', 'sensitive'
 
-               $fields = ['uid', 'avatar', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe', 'manually-approve',
-                       'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
+               $fields = ['uid', 'uri-id', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe',
+                       'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
                        'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item'];
                $contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
                if (!DBA::isResult($contact)) {
                        return false;
                }
 
+               if (self::isLocal($ret['url'])) {
+                       if ($contact['uid'] == 0) {
+                               Logger::info('Local contacts are not updated here.');
+                       } else {
+                               self::updateFromPublicContact($id, $contact);
+                       }
+                       return true;
+               }
+
                if (!empty($ret['account-type']) && $ret['account-type'] == User::ACCOUNT_TYPE_DELETED) {
                        Logger::info('Deleted account', ['id' => $id, 'url' => $ret['url'], 'ret' => $ret]);
                        self::remove($id);
@@ -1952,6 +2052,9 @@ class Contact
                $uid = $contact['uid'];
                unset($contact['uid']);
 
+               $uriid = $contact['uri-id'];
+               unset($contact['uri-id']);
+
                $pubkey = $contact['pubkey'];
                unset($contact['pubkey']);
 
@@ -1975,6 +2078,14 @@ class Contact
                        return false;
                }
 
+               if (Strings::normaliseLink($ret['url']) != Strings::normaliseLink($contact['url'])) {
+                       $cid = self::getIdForURL($ret['url']);
+                       if (!empty($cid) && ($cid != $id)) {
+                               Logger::notice('URL of contact changed.', ['id' => $id, 'new_id' => $cid, 'old' => $contact['url'], 'new' => $ret['url']]);
+                               return self::updateFromProbeArray($cid, $ret);
+                       }
+               }
+
                if (isset($ret['hide']) && is_bool($ret['hide'])) {
                        $ret['unsearchable'] = $ret['hide'];
                }
@@ -1997,6 +2108,7 @@ class Contact
                }
 
                $update = false;
+               $guid = $ret['guid'] ?? '';
 
                // make sure to not overwrite existing values with blank entries except some technical fields
                $keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl'];
@@ -2016,6 +2128,10 @@ class Contact
                        unset($ret['last-item']);
                }
 
+               if (empty($uriid)) {
+                       $update = true;
+               }
+
                if (!empty($ret['photo']) && ($ret['network'] != Protocol::FEED)) {
                        self::updateAvatar($id, $ret['photo'], $update);
                }
@@ -2026,7 +2142,7 @@ class Contact
                        if (Contact\Relation::isDiscoverable($ret['url'])) {
                                Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
                        }
-       
+
                        // Update the public contact
                        if ($uid != 0) {
                                $contact = self::getByURL($ret['url'], false, ['id']);
@@ -2038,9 +2154,15 @@ class Contact
                        return true;
                }
 
-               $ret['nurl'] = Strings::normaliseLink($ret['url']);
+               if (empty($guid)) {
+                       $ret['uri-id'] = ItemURI::getIdByURI($ret['url']);
+               } else {
+                       $ret['uri-id'] = ItemURI::insert(['uri' => $ret['url'], 'guid' => $guid]);
+               }
+
+               $ret['nurl']    = Strings::normaliseLink($ret['url']);
                $ret['updated'] = $updated;
-               $ret['failed'] = false;
+               $ret['failed']  = false;
 
                // Only fill the pubkey if it had been empty before. We have to prevent identity theft.
                if (empty($pubkey) && !empty($new_pubkey)) {
@@ -2071,6 +2193,26 @@ class Contact
                return true;
        }
 
+       private static function updateFromPublicContact(int $id, array $contact)
+       {
+               $public = self::getByURL($contact['url'], false);
+
+               $fields = [];
+
+               foreach ($contact as $field => $value) {
+                       if ($field == 'uid') {
+                               continue;
+                       }
+                       if ($public[$field] != $value) {
+                               $fields[$field] = $public[$field];
+                       }
+               }
+               if (!empty($fields)) {
+                       DBA::update('contact', $fields, ['id' => $id, 'self' => false]);
+                       Logger::info('Updating local contact', ['id' => $id]);
+               }
+       }
+
        /**
         * @param integer $url contact url
         * @return integer Contact id
@@ -2179,8 +2321,10 @@ class Contact
                }
 
                if (!empty($arr['contact']['name'])) {
+                       $probed = false;
                        $ret = $arr['contact'];
                } else {
+                       $probed = true;                 
                        $ret = Probe::uri($url, $network, $user['uid']);
                }
 
@@ -2324,6 +2468,10 @@ class Contact
                // pull feed and consume it, which should subscribe to the hub.
                if ($contact['network'] == Protocol::OSTATUS) {
                        Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
+               }
+
+               if ($probed) {
+                       self::updateFromProbeArray($contact_id, $ret);
                } else {
                        Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
                }
@@ -2408,6 +2556,50 @@ class Contact
                return $contact;
        }
 
+       /**
+        * Follow a contact
+        *
+        * @param int $cid Public contact id
+        * @param int $uid  User ID
+        *
+        * @return bool "true" if following had been successful
+        */
+       public static function follow(int $cid, int $uid)
+       {
+               $user = User::getById($uid);
+               if (empty($user)) {
+                       return false;
+               }
+
+               $contact = self::getById($cid, ['url']);
+
+               $result = self::createFromProbe($user, $contact['url'], false);
+
+               return $result['cid'];
+       }
+
+       /**
+        * Unfollow a contact
+        *
+        * @param int $cid Public contact id
+        * @param int $uid  User ID
+        *
+        * @return bool "true" if unfollowing had been successful
+        */
+       public static function unfollow(int $cid, int $uid)
+       {
+               $cdata = self::getPublicAndUserContacID($cid, $uid);
+               if (empty($cdata['user'])) {
+                       return false;
+               }
+
+               $contact = self::getById($cdata['user']);
+
+               self::removeSharer([], $contact);
+
+               return true;
+       }
+
        /**
         * @param array  $importer Owner (local user) data
         * @param array  $contact  Existing owner-specific contact data we want to expand the relationship with. Optional.
@@ -2473,6 +2665,8 @@ class Contact
                        // Ensure to always have the correct network type, independent from the connection request method
                        self::updateFromProbe($contact['id']);
 
+                       Post\UserNotification::insertNotication($contact['id'], Verb::getID(Activity::FOLLOW), $importer['uid']);
+
                        return true;
                } else {
                        // send email notification to owner?
@@ -2504,6 +2698,8 @@ class Contact
 
                        self::updateAvatar($contact_id, $photo, true);
 
+                       Post\UserNotification::insertNotication($contact_id, Verb::getID(Activity::FOLLOW), $importer['uid']);
+
                        $contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]);
 
                        /// @TODO Encapsulate this into a function/method
@@ -2553,7 +2749,7 @@ class Contact
                return null;
        }
 
-       public static function removeFollower($importer, $contact, array $datarray = [], $item = "")
+       public static function removeFollower($importer, $contact)
        {
                if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::SHARING)) {
                        DBA::update('contact', ['rel' => self::SHARING], ['id' => $contact['id']]);
@@ -2562,7 +2758,7 @@ class Contact
                }
        }
 
-       public static function removeSharer($importer, $contact, array $datarray = [], $item = "")
+       public static function removeSharer($importer, $contact)
        {
                if (($contact['rel'] == self::FRIEND) || ($contact['rel'] == self::FOLLOWER)) {
                        DBA::update('contact', ['rel' => self::FOLLOWER], ['id' => $contact['id']]);
@@ -2762,11 +2958,12 @@ class Contact
         *
         * @param string $search Name or nick
         * @param string $mode   Search mode (e.g. "community")
+        * @param int    $uid    User ID
         *
         * @return array with search results
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function searchByName($search, $mode = '')
+       public static function searchByName(string $search, string $mode = '', int $uid = 0)
        {
                if (empty($search)) {
                        return [];
@@ -2795,11 +2992,12 @@ class Contact
                $search .= '%';
 
                $results = DBA::p("SELECT * FROM `contact`
-                       WHERE NOT `unsearchable` AND `network` IN (?, ?, ?, ?) AND
+                       WHERE (NOT `unsearchable` OR `nurl` IN (SELECT `nurl` FROM `owner-view` where `publish` OR `net-publish`))
+                               AND `network` IN (?, ?, ?, ?) AND
                                NOT `failed` AND `uid` = ? AND
                                (`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?) $extra_sql
                                ORDER BY `nurl` DESC LIMIT 1000",
-                       Protocol::DFRN, Protocol::ACTIVITYPUB, $ostatus, $diaspora, 0, $search, $search, $search
+                       Protocol::DFRN, Protocol::ACTIVITYPUB, $ostatus, $diaspora, $uid, $search, $search, $search
                );
 
                $contacts = DBA::toArray($results);
@@ -2820,6 +3018,9 @@ class Contact
                $count = 0;
 
                foreach ($urls as $url) {
+                       if (empty($url) || !is_string($url)) {
+                               continue;
+                       }
                        $contact = self::getByURL($url, false, ['id', 'updated']);
                        if (empty($contact['id'])) {
                                Worker::add(PRIORITY_LOW, 'AddContact', 0, $url);