]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Contact.php
Pagecache for frequently fetched pages
[friendica.git] / src / Model / Contact.php
index aea3faf3cdcb60aa7ced2e3437d62632fa97608d..6a57693991127be745e836acf043a08ecec04ed9 100644 (file)
@@ -128,10 +128,10 @@ class Contact
         * @param array $fields    Array of selected fields, empty for all
         * @param array $condition Array of fields for condition
         * @param array $params    Array of several parameters
-        * @return array
+        * @return array|bool
         * @throws \Exception
         */
-       public static function selectFirst(array $fields = [], array $condition = [], array $params = []): array
+       public static function selectFirst(array $fields = [], array $condition = [], array $params = [])
        {
                $contact = DBA::selectFirst('contact', $fields, $condition, $params);
 
@@ -160,6 +160,7 @@ class Contact
                        $fields['created'] = DateTimeFormat::utcNow();
                }
 
+               $fields = DI::dbaDefinition()->truncateFieldsForTable('contact', $fields);
                DBA::insert('contact', $fields, $duplicate_mode);
                $contact = DBA::selectFirst('contact', [], ['id' => DBA::lastInsertId()]);
                if (!DBA::isResult($contact)) {
@@ -191,6 +192,7 @@ class Contact
         */
        public static function update(array $fields, array $condition, $old_fields = [])
        {
+               $fields = DI::dbaDefinition()->truncateFieldsForTable('contact', $fields);
                $ret = DBA::update('contact', $fields, $condition, $old_fields);
 
                // Apply changes to the "user-contact" table on dedicated fields
@@ -327,14 +329,15 @@ class Contact
        /**
         * Tests if the given contact is a follower
         *
-        * @param int $cid Either public contact id or user's contact id
-        * @param int $uid User ID
+        * @param int  $cid    Either public contact id or user's contact id
+        * @param int  $uid    User ID
+        * @param bool $strict If "true" then contact mustn't be set to pending or readonly
         *
         * @return boolean is the contact id a follower?
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isFollower(int $cid, int $uid): bool
+       public static function isFollower(int $cid, int $uid, bool $strict = false): bool
        {
                if (Contact\User::isBlocked($cid, $uid)) {
                        return false;
@@ -346,20 +349,24 @@ class Contact
                }
 
                $condition = ['id' => $cdata['user'], 'rel' => [self::FOLLOWER, self::FRIEND]];
+               if ($strict) {
+                       $condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
+               }
                return DBA::exists('contact', $condition);
        }
 
        /**
         * Tests if the given contact url is a follower
         *
-        * @param string $url Contact URL
-        * @param int    $uid User ID
+        * @param string $url    Contact URL
+        * @param int    $uid    User ID
+        * @param bool   $strict If "true" then contact mustn't be set to pending or readonly
         *
         * @return boolean is the contact id a follower?
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isFollowerByURL(string $url, uid $uid): bool
+       public static function isFollowerByURL(string $url, int $uid, bool $strict = false): bool
        {
                $cid = self::getIdForURL($url, $uid);
 
@@ -367,20 +374,21 @@ class Contact
                        return false;
                }
 
-               return self::isFollower($cid, $uid);
+               return self::isFollower($cid, $uid, $strict);
        }
 
        /**
         * Tests if the given user shares with the given contact
         *
-        * @param int $cid Either public contact id or user's contact id
-        * @param int $uid User ID
+        * @param int  $cid    Either public contact id or user's contact id
+        * @param int  $uid    User ID
+        * @param bool $strict If "true" then contact mustn't be set to pending or readonly
         *
         * @return boolean is the contact sharing with given user?
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isSharing(int $cid, int $uid): bool
+       public static function isSharing(int $cid, int $uid, bool $strict = false): bool
        {
                if (Contact\User::isBlocked($cid, $uid)) {
                        return false;
@@ -392,20 +400,24 @@ class Contact
                }
 
                $condition = ['id' => $cdata['user'], 'rel' => [self::SHARING, self::FRIEND]];
+               if ($strict) {
+                       $condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
+               }
                return DBA::exists('contact', $condition);
        }
 
        /**
         * Tests if the given user follow the given contact url
         *
-        * @param string $url Contact URL
-        * @param int    $uid User ID
+        * @param string $url    Contact URL
+        * @param int    $uid    User ID
+        * @param bool   $strict If "true" then contact mustn't be set to pending or readonly
         *
         * @return boolean is the contact url being followed?
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isSharingByURL(string $url, int $uid): bool
+       public static function isSharingByURL(string $url, int $uid, bool $strict = false): bool
        {
                $cid = self::getIdForURL($url, $uid);
 
@@ -413,7 +425,7 @@ class Contact
                        return false;
                }
 
-               return self::isSharing($cid, $uid);
+               return self::isSharing($cid, $uid, $strict);
        }
 
        /**
@@ -474,7 +486,6 @@ class Contact
         * Check if the given contact ID is on the same server
         *
         * @param string $url The contact link
-        *
         * @return boolean Is it the same server?
         */
        public static function isLocalById(int $cid): bool
@@ -556,7 +567,6 @@ class Contact
         *
         * @param int $cid Either public contact id or user's contact id
         * @param int $uid User ID
-        *
         * @return array with public and user's contact id
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
@@ -597,12 +607,11 @@ class Contact
         * @param int $cid A contact ID
         * @param int $uid The User ID
         * @param array $fields The selected fields for the contact
-        *
         * @return array The contact details
         *
         * @throws \Exception
         */
-       public static function getContactForUser($cid, $uid, array $fields = [])
+       public static function getContactForUser(int $cid, int $uid, array $fields = []): array
        {
                $contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => $uid]);
 
@@ -620,7 +629,7 @@ class Contact
         * @return bool Operation success
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function createSelfFromUserId($uid)
+       public static function createSelfFromUserId(int $uid): bool
        {
                $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
                        ['uid' => $uid, 'account_expired' => false]);
@@ -677,12 +686,12 @@ class Contact
        /**
         * Updates the self-contact for the provided user id
         *
-        * @param int     $uid
-        * @param boolean $update_avatar Force the avatar update
-        * @return bool   "true" if updated
+        * @param int   $uid
+        * @param bool  $update_avatar Force the avatar update
+        * @return bool "true" if updated
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function updateSelfFromUserID($uid, $update_avatar = false)
+       public static function updateSelfFromUserID(int $uid, bool $update_avatar = false): bool
        {
                $fields = ['id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
                        'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
@@ -706,23 +715,33 @@ class Contact
                }
 
                $file_suffix = 'jpg';
+               $url = DI::baseUrl() . '/profile/' . $user['nickname'];
+
+               $fields = [
+                       'name'         => $profile['name'],
+                       'nick'         => $user['nickname'],
+                       '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'],
+                       'matrix'       => $profile['matrix'],
+                       'network'      => Protocol::DFRN,
+                       'url'          => $url,
+                       // it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
+                       'nurl'         => Strings::normaliseLink($url),
+                       'uri-id'       => ItemURI::getIdByURI($url),
+                       'addr'         => $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3),
+                       'request'      => DI::baseUrl() . '/dfrn_request/' . $user['nickname'],
+                       'notify'       => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
+                       'poll'         => DI::baseUrl() . '/dfrn_poll/'. $user['nickname'],
+                       'confirm'      => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
+                       'poco'         => DI::baseUrl() . '/poco/' . $user['nickname'],
+               ];
 
-               $fields = ['name' => $profile['name'], 'nick' => $user['nickname'],
-                       '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'], 'matrix' => $profile['matrix'], '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'];
-               $fields['nurl'] = Strings::normaliseLink($fields['url']);
-               $fields['uri-id'] = ItemURI::getIdByURI($fields['url']);
-               $fields['addr'] = $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
-               $fields['request'] = DI::baseUrl() . '/dfrn_request/' . $user['nickname'];
-               $fields['notify'] = DI::baseUrl() . '/dfrn_notify/' . $user['nickname'];
-               $fields['poll'] = DI::baseUrl() . '/dfrn_poll/'. $user['nickname'];
-               $fields['confirm'] = DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'];
-               $fields['poco'] = DI::baseUrl() . '/poco/' . $user['nickname'];
 
                $avatar = Photo::selectFirst(['resource-id', 'type'], ['uid' => $uid, 'profile' => true]);
                if (DBA::isResult($avatar)) {
@@ -795,9 +814,10 @@ class Contact
         * Marks a contact for removal
         *
         * @param int $id contact id
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function remove($id)
+       public static function remove(int $id)
        {
                // We want just to make sure that we don't delete our "self" contact
                $contact = DBA::selectFirst('contact', ['uri-id', 'photo', 'thumb', 'micro', 'uid'], ['id' => $id, 'self' => false]);
@@ -822,6 +842,7 @@ class Contact
         * Unfollow the remote contact
         *
         * @param array $contact Target user-specific contact (uid != 0) array
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
@@ -851,6 +872,7 @@ class Contact
         * The local relationship is updated immediately, the eventual remote server is messaged in the background.
         *
         * @param array $contact User-specific contact array (uid != 0) to revoke the follow from
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
@@ -878,6 +900,7 @@ class Contact
         * Completely severs a relationship with a contact
         *
         * @param array $contact User-specific contact (uid != 0) array
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
@@ -925,7 +948,7 @@ class Contact
         * up or some other transient event and that there's a possibility we could recover from it.
         *
         * @param array $contact contact to mark for archival
-        * @return null
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         */
        public static function markForArchival(array $contact)
@@ -978,7 +1001,7 @@ class Contact
         * @see   Contact::markForArchival()
         *
         * @param array $contact contact to be unmarked for archival
-        * @return null
+        * @return void
         * @throws \Exception
         */
        public static function unmarkForArchival(array $contact)
@@ -1025,12 +1048,11 @@ class Contact
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function photoMenu(array $contact, $uid = 0)
+       public static function photoMenu(array $contact, int $uid = 0): array
        {
                $pm_url = '';
                $status_link = '';
                $photos_link = '';
-               $poke_link = '';
 
                if ($uid == 0) {
                        $uid = local_user();
@@ -1073,10 +1095,6 @@ class Contact
                        $pm_url = DI::baseUrl() . '/message/new/' . $contact['id'];
                }
 
-               if (($contact['network'] == Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
-                       $poke_link = 'contact/' . $contact['id'] . '/poke';
-               }
-
                $contact_url = DI::baseUrl() . '/contact/' . $contact['id'];
 
                $posts_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
@@ -1111,7 +1129,6 @@ class Contact
                                'network' => [DI::l10n()->t('Network Posts') , $posts_link       , false],
                                'edit'    => [DI::l10n()->t('View Contact')  , $contact_url      , false],
                                'pm'      => [DI::l10n()->t('Send PM')       , $pm_url           , false],
-                               'poke'    => [DI::l10n()->t('Poke')          , $poke_link        , false],
                                'follow'  => [DI::l10n()->t('Connect/Follow'), $follow_link      , true],
                                'unfollow'=> [DI::l10n()->t('UnFollow')      , $unfollow_link    , true],
                        ];
@@ -1168,11 +1185,11 @@ class Contact
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function getIdForURL($url, $uid = 0, $update = null, $default = [])
+       public static function getIdForURL(string $url = null, int $uid = 0, $update = null, array $default = []): int
        {
                $contact_id = 0;
 
-               if ($url == '') {
+               if (empty($url)) {
                        Logger::notice('Empty url, quitting', ['url' => $url, 'user' => $uid, 'default' => $default]);
                        return 0;
                }
@@ -1180,7 +1197,7 @@ class Contact
                $contact = self::getByURL($url, false, ['id', 'network', 'uri-id'], $uid);
 
                if (!empty($contact)) {
-                       $contact_id = $contact["id"];
+                       $contact_id = $contact['id'];
 
                        if (empty($update) && (!empty($contact['uri-id']) || is_bool($update))) {
                                Logger::debug('Contact found', ['url' => $url, 'uid' => $uid, 'update' => $update, 'cid' => $contact_id]);
@@ -1197,10 +1214,10 @@ class Contact
                $data = [];
 
                if (empty($default['network']) || $update) {
-                       $data = Probe::uri($url, "", $uid);
+                       $data = Probe::uri($url, '', $uid);
 
                        // Take the default values when probing failed
-                       if (!empty($default) && !in_array($data["network"], array_merge(Protocol::NATIVE_SUPPORT, [Protocol::PUMPIO]))) {
+                       if (!empty($default) && !in_array($data['network'], array_merge(Protocol::NATIVE_SUPPORT, [Protocol::PUMPIO]))) {
                                $data = array_merge($data, $default);
                        }
                } elseif (!empty($default['network'])) {
@@ -1282,7 +1299,7 @@ class Contact
                        }
 
                        if (!$contact_id) {
-                               Logger::info('Contact was not inserted', ['url' => $url, 'uid' => $uid]);
+                               Logger::warning('Contact was not inserted', ['url' => $url, 'uid' => $uid]);
                                return 0;
                        }
                } else {
@@ -1312,7 +1329,7 @@ class Contact
         * @return boolean Is the contact archived?
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function isArchived(int $cid)
+       public static function isArchived(int $cid): bool
        {
                if ($cid == 0) {
                        return false;
@@ -1352,11 +1369,10 @@ class Contact
         * Checks if the contact is blocked
         *
         * @param int $cid contact id
-        *
         * @return boolean Is the contact blocked?
         * @throws HTTPException\InternalServerErrorException
         */
-       public static function isBlocked($cid)
+       public static function isBlocked(int $cid): bool
        {
                if ($cid == 0) {
                        return false;
@@ -1378,11 +1394,10 @@ class Contact
         * Checks if the contact is hidden
         *
         * @param int $cid contact id
-        *
         * @return boolean Is the contact hidden?
         * @throws \Exception
         */
-       public static function isHidden($cid)
+       public static function isHidden(int $cid): bool
        {
                if ($cid == 0) {
                        return false;
@@ -1406,7 +1421,7 @@ class Contact
         * @return string posts in HTML
         * @throws \Exception
         */
-       public static function getPostsFromUrl($contact_url, $thread_mode = false, $update = 0, $parent = 0, bool $only_media = false)
+       public static function getPostsFromUrl(string $contact_url, bool $thread_mode = false, int $update = 0, int $parent = 0, bool $only_media = false): string
        {
                return self::getPostsFromId(self::getIdForURL($contact_url), $thread_mode, $update, $parent, $only_media);
        }
@@ -1422,7 +1437,7 @@ class Contact
         * @return string posts in HTML
         * @throws \Exception
         */
-       public static function getPostsFromId($cid, $thread_mode = false, $update = 0, $parent = 0, bool $only_media = false)
+       public static function getPostsFromId(int $cid, bool $thread_mode = false, int $update = 0, int $parent = 0, bool $only_media = false): string
        {
                $contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
                if (!DBA::isResult($contact)) {
@@ -1527,7 +1542,7 @@ class Contact
         * @param int $type type of contact or account
         * @return string
         */
-       public static function getAccountType(int $type)
+       public static function getAccountType(int $type): string
        {
                switch ($type) {
                        case self::TYPE_ORGANISATION:
@@ -1553,11 +1568,11 @@ class Contact
        /**
         * Blocks a contact
         *
-        * @param int $cid
-        * @return bool
-        * @throws \Exception
+        * @param int $cid Contact id to block
+        * @param string $reason Block reason
+        * @return bool Whether it was successful
         */
-       public static function block($cid, $reason = null)
+       public static function block(int $cid, string $reason = null): bool
        {
                $return = self::update(['blocked' => true, 'block_reason' => $reason], ['id' => $cid]);
 
@@ -1567,11 +1582,10 @@ class Contact
        /**
         * Unblocks a contact
         *
-        * @param int $cid
-        * @return bool
-        * @throws \Exception
+        * @param int $cid Contact id to unblock
+        * @return bool Whether it was successfull
         */
-       public static function unblock($cid)
+       public static function unblock(int $cid): bool
        {
                $return = self::update(['blocked' => false, 'block_reason' => null], ['id' => $cid]);
 
@@ -1581,7 +1595,7 @@ class Contact
        /**
         * Ensure that cached avatar exist
         *
-        * @param integer $cid
+        * @param integer $cid Contact id
         */
        public static function checkAvatarCache(int $cid)
        {
@@ -1621,7 +1635,7 @@ 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 $size, $no_update = false)
+       private static function getAvatarPath(array $contact, string $size, bool $no_update = false): string
        {
                $contact = self::checkAvatarCacheByArray($contact, $no_update);
 
@@ -1655,7 +1669,7 @@ class Contact
         * @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, bool $no_update = false)
+       public static function getPhoto(array $contact, bool $no_update = false): string
        {
                return self::getAvatarPath($contact, Proxy::SIZE_SMALL, $no_update);
        }
@@ -1667,7 +1681,7 @@ class Contact
         * @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, bool $no_update = false)
+       public static function getThumb(array $contact, bool $no_update = false): string
        {
                return self::getAvatarPath($contact, Proxy::SIZE_THUMB, $no_update);
        }
@@ -1679,7 +1693,7 @@ class Contact
         * @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, bool $no_update = false)
+       public static function getMicro(array $contact, bool $no_update = false): string
        {
                return self::getAvatarPath($contact, Proxy::SIZE_MICRO, $no_update);
        }
@@ -1691,7 +1705,7 @@ class Contact
         * @param bool  $no_update Don't perfom an update if no cached avatar was found
         * @return array contact array with avatar cache fields
         */
-       private static function checkAvatarCacheByArray(array $contact, bool $no_update = false)
+       private static function checkAvatarCacheByArray(array $contact, bool $no_update = false): array
        {
                $update = false;
                $contact_fields = [];
@@ -1797,7 +1811,7 @@ class Contact
         * @param string $size    Size of the avatar picture
         * @return string avatar URL
         */
-       public static function getDefaultAvatar(array $contact, string $size)
+       public static function getDefaultAvatar(array $contact, string $size): string
        {
                switch ($size) {
                        case Proxy::SIZE_MICRO:
@@ -1959,7 +1973,7 @@ class Contact
         * @param string  $updated Contact update date
         * @return string avatar link
         */
-       public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''):string
+       public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
        {
                // We have to fetch the "updated" variable when it wasn't provided
                // The parameter can be provided to improve performance
@@ -2000,7 +2014,7 @@ class Contact
         * @param string  $size One of the Proxy::SIZE_* constants
         * @return string avatar link
         */
-       public static function getAvatarUrlForUrl(string $url, int $uid, string $size = ''):string
+       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];
@@ -2016,7 +2030,7 @@ class Contact
         * @param string  $updated Contact update date
         * @return string header link
         */
-       public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''):string
+       public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
        {
                // We have to fetch the "updated" variable when it wasn't provided
                // The parameter can be provided to improve performance
@@ -2313,6 +2327,8 @@ class Contact
        }
 
        /**
+        * Updates contact record by provided id and optional network
+        *
         * @param integer $id      contact id
         * @param string  $network Optional network we are probing for
         * @return boolean
@@ -2336,13 +2352,56 @@ class Contact
        }
 
        /**
+        * Checks if the given contact has got local data
+        *
+        * @param int   $id
+        * @param array $contact
+        *
+        * @return boolean
+        */
+       private static function hasLocalData(int $id, array $contact): bool
+       {
+               if (!empty($contact['uri-id']) && DBA::exists('contact', ["`uri-id` = ? AND `uid` != ?", $contact['uri-id'], 0])) {
+                       // User contacts with the same uri-id exist
+                       return true;
+               } elseif (DBA::exists('contact', ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($contact['url']), 0])) {
+                       // User contacts with the same nurl exists (compatibility mode for systems with missing uri-id values)
+                       return true;
+               }
+               if (DBA::exists('post-tag', ['cid' => $id])) {
+                       // Is tagged in a post
+                       return true;
+               }
+               if (DBA::exists('user-contact', ['cid' => $id])) {
+                       // Has got user-contact data
+                       return true;
+               }
+               if (Post::exists(['author-id' => $id])) {
+                       // Posts with this author exist
+                       return true;
+               }
+               if (Post::exists(['owner-id' => $id])) {
+                       // Posts with this owner exist
+                       return true;
+               }
+               if (Post::exists(['causer-id' => $id])) {
+                       // Posts with this causer exist
+                       return true;
+               }
+               // We don't have got this contact locally
+               return false;
+       }
+
+       /**
+        * Updates contact record by provided id and probed data
+        *
         * @param integer $id      contact id
         * @param array   $ret     Probed data
         * @return boolean
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function updateFromProbeArray(int $id, array $ret)
+       private static function updateFromProbeArray(int $id, array $ret): bool
        {
                /*
                  Warning: Never ever fetch the public key via Probe::uri and write it into the contacts.
@@ -2354,7 +2413,8 @@ class Contact
 
                $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', 'xmpp', 'matrix'];
+                       'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item', 'xmpp', 'matrix',
+                       'created', 'last-update'];
                $contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
                if (!DBA::isResult($contact)) {
                        return false;
@@ -2387,14 +2447,34 @@ class Contact
                $pubkey = $contact['pubkey'];
                unset($contact['pubkey']);
 
+               $created = $contact['created'];
+               unset($contact['created']);
+
+               $last_update = $contact['last-update'];
+               unset($contact['last-update']);
+
                $contact['photo'] = $contact['avatar'];
                unset($contact['avatar']);
 
                $updated = DateTimeFormat::utcNow();
 
+               $has_local_data = self::hasLocalData($id, $contact);
+
+               if (!in_array($ret['network'], array_merge(Protocol::FEDERATED, [Protocol::ZOT, Protocol::PHANTOM]))) {
+                       // Periodical checks are only done on federated contacts
+                       $failed_next_update  = null;
+                       $success_next_update = null;
+               } elseif ($has_local_data) {
+                       $failed_next_update  = GServer::getNextUpdateDate(false, $created, $last_update, !in_array($contact['network'], Protocol::FEDERATED));
+                       $success_next_update = GServer::getNextUpdateDate(true, $created, $last_update, !in_array($contact['network'], Protocol::FEDERATED));
+               } else {
+                       $failed_next_update  = DateTimeFormat::utc('now +6 month');
+                       $success_next_update = DateTimeFormat::utc('now +1 month');
+               }
+
                if (Strings::normaliseLink($contact['url']) != Strings::normaliseLink($ret['url'])) {
                        Logger::notice('New URL differs from old URL', ['id' => $id, 'uid' => $uid, 'old' => $contact['url'], 'new' => $ret['url']]);
-                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'last-update' => $updated, 'failure_update' => $updated]);
+                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]);
                        return false;
                }
 
@@ -2402,14 +2482,14 @@ class Contact
                // We check after the probing to be able to correct falsely detected contact types.
                if (($contact['contact-type'] == self::TYPE_RELAY) &&
                        (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))) {
-                       self::updateContact($id, $uid, $contact['url'], $contact['url'], ['failed' => false, 'last-update' => $updated, 'success_update' => $updated]);
+                       self::updateContact($id, $uid, $contact['url'], $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]);
                        Logger::info('Not updating relais', ['id' => $id, 'url' => $contact['url']]);
                        return true;
                }
 
                // If Probe::uri fails the network code will be different ("feed" or "unkn")
                if (($ret['network'] == Protocol::PHANTOM) || (($ret['network'] == Protocol::FEED) && ($ret['network'] != $contact['network']))) {
-                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'last-update' => $updated, 'failure_update' => $updated]);
+                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]);
                        return false;
                }
 
@@ -2437,7 +2517,7 @@ class Contact
 
                $new_pubkey = $ret['pubkey'] ?? '';
 
-               if ($uid == 0) {
+               if ($uid == 0 && DI::config()->get('system', 'fetch_featured_posts')) {
                        if ($ret['network'] == Protocol::ACTIVITYPUB) {
                                $apcontact = APContact::getByURL($ret['url'], false);
                                if (!empty($apcontact['featured'])) {
@@ -2481,7 +2561,7 @@ class Contact
                $uriid = ItemURI::insert(['uri' => $ret['url'], 'guid' => $guid]);
 
                if (!$update) {
-                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => false, 'last-update' => $updated, 'success_update' => $updated]);
+                       self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]);
 
                        if (Contact\Relation::isDiscoverable($ret['url'])) {
                                Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
@@ -2498,10 +2578,12 @@ class Contact
                        return true;
                }
 
-               $ret['uri-id']  = $uriid;
-               $ret['nurl']    = Strings::normaliseLink($ret['url']);
-               $ret['updated'] = $updated;
-               $ret['failed']  = false;
+               $ret['uri-id']      = $uriid;
+               $ret['nurl']        = Strings::normaliseLink($ret['url']);
+               $ret['updated']     = $updated;
+               $ret['failed']      = false;
+               $ret['next-update'] = $success_next_update;
+               $ret['local-data']  = $has_local_data;
 
                // Only fill the pubkey if it had been empty before. We have to prevent identity theft.
                if (empty($pubkey) && !empty($new_pubkey)) {
@@ -2553,12 +2635,14 @@ class Contact
        }
 
        /**
+        * Updates contact record by provided URL
+        *
         * @param integer $url contact url
         * @return integer Contact id
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function updateFromProbeByURL($url)
+       public static function updateFromProbeByURL(string $url): int
        {
                $id = self::getIdForURL($url);
 
@@ -2579,7 +2663,7 @@ class Contact
         * @param string $network Network of that contact
         * @return string with protocol
         */
-       public static function getProtocol($url, $network)
+       public static function getProtocol(string $url, string $network): string
        {
                if ($network != Protocol::DFRN) {
                        return $network;
@@ -2946,6 +3030,7 @@ class Contact
         * Update the local relationship when a local user loses a follower
         *
         * @param array $contact User-specific contact (uid != 0) array
+        * @return void
         * @throws HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
@@ -3236,7 +3321,7 @@ class Contact
                                continue;
                        }
                        $contact = self::getByURL($url, false, ['id', 'updated']);
-                       if (empty($contact['id'])) {
+                       if (empty($contact['id']) && Network::isValidHttpUrl($url)) {
                                Worker::add(PRIORITY_LOW, 'AddContact', 0, $url);
                                ++$added;
                        } elseif ($contact['updated'] < DateTimeFormat::utc('now -7 days')) {