]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Contact.php
Merge pull request #7919 from annando/mail-update
[friendica.git] / src / Model / Contact.php
index 6f9ee4cdb6d9f4aa2bf508d83af596d2581f7311..a30cd39baf093c37fdb0853e0d7195b2aca035ac 100644 (file)
@@ -12,18 +12,20 @@ use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
+use Friendica\Core\Session;
 use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\Network\Probe;
 use Friendica\Object\Image;
+use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\DFRN;
 use Friendica\Protocol\Diaspora;
 use Friendica\Protocol\OStatus;
-use Friendica\Protocol\PortableContact;
 use Friendica\Protocol\Salmon;
 use Friendica\Util\DateTimeFormat;
+use Friendica\Util\Images;
 use Friendica\Util\Network;
 use Friendica\Util\Strings;
 
@@ -136,6 +138,31 @@ class Contact extends BaseObject
                return $contact;
        }
 
+       /**
+        * Insert a row into the contact table
+        * Important: You can't use DBA::lastInsertId() after this call since it will be set to 0.
+        *
+        * @param array        $fields              field array
+        * @param bool         $on_duplicate_update Do an update on a duplicate entry
+        *
+        * @return boolean was the insert successful?
+        * @throws \Exception
+        */
+       public static function insert(array $fields, bool $on_duplicate_update = false)
+       {
+               $ret = DBA::insert('contact', $fields, $on_duplicate_update);
+               $contact = DBA::selectFirst('contact', ['nurl', 'uid'], ['id' => DBA::lastInsertId()]);
+               if (!DBA::isResult($contact)) {
+                       // Shouldn't happen
+                       return $ret;
+               }
+
+               // Search for duplicated contacts and get rid of them
+               self::removeDuplicates($contact['nurl'], $contact['uid']);
+
+               return $ret;
+       }
+
        /**
         * @param integer $id     Contact ID
         * @param array   $fields Array of selected fields, empty for all
@@ -245,14 +272,17 @@ class Contact extends BaseObject
         * @param string $url The contact link
         *
         * @return string basepath
+        * @return boolean $dont_update Don't update the contact
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function getBasepath($url)
+       public static function getBasepath($url, $dont_update = false)
        {
                $contact = DBA::selectFirst('contact', ['baseurl'], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]);
                if (!empty($contact['baseurl'])) {
                        return $contact['baseurl'];
+               } elseif ($dont_update) {
+                       return '';
                }
 
                self::updateFromProbeByURL($url, true);
@@ -265,6 +295,18 @@ class Contact extends BaseObject
                return '';
        }
 
+       /**
+        * Check if the given contact url is on the same server
+        *
+        * @param string $url The contact link
+        *
+        * @return boolean Is it the same server?
+        */
+       public static function isLocal($url)
+       {
+               return Strings::compareLink(self::getBasepath($url, true), System::baseUrl());
+       }
+
        /**
         * Returns the public contact id of the given user id
         *
@@ -643,21 +685,21 @@ class Contact extends BaseObject
        public static function updateSelfFromUserID($uid, $update_avatar = false)
        {
                $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'gender', 'avatar',
-                       'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl',
+                       'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
                        'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco'];
                $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
                if (!DBA::isResult($self)) {
                        return;
                }
 
-               $fields = ['nickname', 'page-flags', 'account-type'];
+               $fields = ['nickname', 'page-flags', 'account-type', 'hidewall'];
                $user = DBA::selectFirst('user', $fields, ['uid' => $uid]);
                if (!DBA::isResult($user)) {
                        return;
                }
 
                $fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
-                       'country-name', 'gender', 'pub_keywords', 'xmpp'];
+                       'country-name', 'gender', 'pub_keywords', 'xmpp', 'net-publish'];
                $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]);
                if (!DBA::isResult($profile)) {
                        return;
@@ -678,7 +720,7 @@ class Contact extends BaseObject
                        }
 
                        // Creating the path to the avatar, beginning with the file suffix
-                       $types = Image::supportedTypes();
+                       $types = Images::supportedTypes();
                        if (isset($types[$avatar['type']])) {
                                $file_suffix = $types[$avatar['type']];
                        }
@@ -702,6 +744,7 @@ class Contact extends BaseObject
                $fields['avatar'] = System::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix;
                $fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
                $fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
+               $fields['unsearchable'] = $user['hidewall'] || !$profile['net-publish'];
 
                // it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
                $fields['url'] = System::baseUrl() . '/profile/' . $user['nickname'];
@@ -786,7 +829,7 @@ class Contact extends BaseObject
                } elseif (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
                        // create an unfollow slap
                        $item = [];
-                       $item['verb'] = NAMESPACE_OSTATUS . "/unfollow";
+                       $item['verb'] = Activity::O_UNFOLLOW;
                        $item['follow'] = $contact["url"];
                        $item['body'] = '';
                        $item['title'] = '';
@@ -860,8 +903,8 @@ class Contact extends BaseObject
                                 * delete, though if the owner tries to unarchive them we'll start
                                 * the whole process over again.
                                 */
-                               DBA::update('contact', ['archive' => 1], ['id' => $contact['id']]);
-                               DBA::update('contact', ['archive' => 1], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
+                               DBA::update('contact', ['archive' => true], ['id' => $contact['id']]);
+                               DBA::update('contact', ['archive' => true], ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
                                GContact::updateFromPublicContactURL($contact['url']);
                        }
                }
@@ -878,6 +921,13 @@ class Contact extends BaseObject
         */
        public static function unmarkForArchival(array $contact)
        {
+               // Always unarchive the relay contact entry
+               if (!empty($contact['batch']) && !empty($contact['term-date']) && ($contact['term-date'] > DBA::NULL_DATETIME)) {
+                       $fields = ['term-date' => DBA::NULL_DATETIME, 'archive' => false];
+                       $condition = ['uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
+                       DBA::update('contact', $fields, $condition);
+               }
+
                $condition = ['`id` = ? AND (`term-date` > ? OR `archive`)', $contact['id'], DBA::NULL_DATETIME];
                $exists = DBA::exists('contact', $condition);
 
@@ -899,13 +949,8 @@ class Contact extends BaseObject
                // It's a miracle. Our dead contact has inexplicably come back to life.
                $fields = ['term-date' => DBA::NULL_DATETIME, 'archive' => false];
                DBA::update('contact', $fields, ['id' => $contact['id']]);
-               DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($contact['url'])]);
+               DBA::update('contact', $fields, ['nurl' => Strings::normaliseLink($contact['url']), 'self' => false]);
                GContact::updateFromPublicContactURL($contact['url']);
-
-               if (!empty($contact['batch'])) {
-                       $condition = ['batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
-                       DBA::update('contact', $fields, $condition);
-               }
        }
 
        /**
@@ -939,41 +984,43 @@ class Contact extends BaseObject
 
                $ssl_url = str_replace('http://', 'https://', $url);
 
+               $nurl = Strings::normaliseLink($url);
+
                // Fetch contact data from the contact table for the given user
                $s = DBA::p("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
-               FROM `contact` WHERE `nurl` = ? AND `uid` = ?", Strings::normaliseLink($url), $uid);
+                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`, `rel`, `pending`
+               FROM `contact` WHERE `nurl` = ? AND `uid` = ?", $nurl, $uid);
                $r = DBA::toArray($s);
 
                // Fetch contact data from the contact table for the given user, checking with the alias
                if (!DBA::isResult($r)) {
                        $s = DBA::p("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                               `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
-                       FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ?", Strings::normaliseLink($url), $url, $ssl_url, $uid);
+                               `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`, `rel`, `pending`
+                       FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = ?", $nurl, $url, $ssl_url, $uid);
                        $r = DBA::toArray($s);
                }
 
                // Fetch the data from the contact table with "uid=0" (which is filled automatically)
                if (!DBA::isResult($r)) {
                        $s = DBA::p("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
-                       FROM `contact` WHERE `nurl` = ? AND `uid` = 0", Strings::normaliseLink($url));
+                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`, `rel`, `pending`
+                       FROM `contact` WHERE `nurl` = ? AND `uid` = 0", $nurl);
                        $r = DBA::toArray($s);
                }
 
                // Fetch the data from the contact table with "uid=0" (which is filled automatically) - checked with the alias
                if (!DBA::isResult($r)) {
                        $s = DBA::p("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
-                       FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = 0", Strings::normaliseLink($url), $url, $ssl_url);
+                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`, `rel`, `pending`
+                       FROM `contact` WHERE `alias` IN (?, ?, ?) AND `uid` = 0", $nurl, $url, $ssl_url);
                        $r = DBA::toArray($s);
                }
 
                // Fetch the data from the gcontact table
                if (!DBA::isResult($r)) {
                        $s = DBA::p("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
-                       `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, 0 AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
-                       FROM `gcontact` WHERE `nurl` = ?", Strings::normaliseLink($url));
+                       `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, 0 AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`, 2 AS `rel`, 0 AS `pending`
+                       FROM `gcontact` WHERE `nurl` = ?", $nurl);
                        $r = DBA::toArray($s);
                }
 
@@ -1034,14 +1081,14 @@ class Contact extends BaseObject
                        $profile["micro"] = $profile["thumb"];
                }
 
-               if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0)
+               if ((empty($profile["addr"]) || empty($profile["name"])) && !empty($profile["gid"])
                        && in_array($profile["network"], Protocol::FEDERATED)
                ) {
                        Worker::add(PRIORITY_LOW, "UpdateGContact", $url);
                }
 
                // Show contact details of Diaspora contacts only if connected
-               if ((defaults($profile, "cid", 0) == 0) && (defaults($profile, "network", "") == Protocol::DIASPORA)) {
+               if (empty($profile["cid"]) && ($profile["network"] ?? "") == Protocol::DIASPORA) {
                        $profile["location"] = "";
                        $profile["about"] = "";
                        $profile["gender"] = "";
@@ -1077,7 +1124,7 @@ class Contact extends BaseObject
 
                // Fetch contact data from the contact table for the given user
                $r = q("SELECT `id`, `id` AS `cid`, 0 AS `gid`, 0 AS `zid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`
+                       `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, `self`, `rel`, `pending`
                        FROM `contact` WHERE `addr` = '%s' AND `uid` = %d AND NOT `deleted`",
                        DBA::escape($addr),
                        intval($uid)
@@ -1085,7 +1132,7 @@ class Contact extends BaseObject
                // Fetch the data from the contact table with "uid=0" (which is filled automatically)
                if (!DBA::isResult($r)) {
                        $r = q("SELECT `id`, 0 AS `cid`, `id` AS `zid`, 0 AS `gid`, `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, `xmpp`,
-                               `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`
+                               `keywords`, `gender`, `photo`, `thumb`, `micro`, `forum`, `prv`, (`forum` | `prv`) AS `community`, `contact-type`, `bd` AS `birthday`, 0 AS `self`, `rel`, `pending`
                                FROM `contact` WHERE `addr` = '%s' AND `uid` = 0 AND NOT `deleted`",
                                DBA::escape($addr)
                        );
@@ -1094,7 +1141,7 @@ class Contact extends BaseObject
                // Fetch the data from the gcontact table
                if (!DBA::isResult($r)) {
                        $r = q("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
-                               `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
+                               `keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`, 2 AS `rel`, 0 AS `pending`
                                FROM `gcontact` WHERE `addr` = '%s'",
                                DBA::escape($addr)
                        );
@@ -1148,9 +1195,9 @@ class Contact extends BaseObject
                }
 
                $sparkle = false;
-               if (($contact['network'] === Protocol::DFRN) && !$contact['self']) {
+               if (($contact['network'] === Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
                        $sparkle = true;
-                       $profile_link = System::baseUrl() . '/redir/' . $contact['id'] . '?url=' . $contact['url'];
+                       $profile_link = System::baseUrl() . '/redir/' . $contact['id'];
                } else {
                        $profile_link = $contact['url'];
                }
@@ -1165,12 +1212,12 @@ class Contact extends BaseObject
                        $profile_link = $profile_link . '?tab=profile';
                }
 
-               if (self::canReceivePrivateMessages($contact)) {
+               if (self::canReceivePrivateMessages($contact) && empty($contact['pending'])) {
                        $pm_url = System::baseUrl() . '/message/new/' . $contact['id'];
                }
 
-               if (($contact['network'] == Protocol::DFRN) && !$contact['self']) {
-                       $poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id'];
+               if (($contact['network'] == Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
+                       $poke_link = System::baseUrl() . '/poke/?c=' . $contact['id'];
                }
 
                $contact_url = System::baseUrl() . '/contact/' . $contact['id'];
@@ -1181,29 +1228,48 @@ class Contact extends BaseObject
                        $contact_drop_link = System::baseUrl() . '/contact/' . $contact['id'] . '/drop?confirm=1';
                }
 
+               $follow_link = '';
+               $unfollow_link = '';
+               if (in_array($contact['network'], Protocol::NATIVE_SUPPORT)) {
+                       if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
+                               $unfollow_link = 'unfollow?url=' . urlencode($contact['url']);
+                       } elseif(!$contact['pending']) {
+                               $follow_link = 'follow?url=' . urlencode($contact['url']);
+                       }
+               }
+
                /**
                 * Menu array:
                 * "name" => [ "Label", "link", (bool)Should the link opened in a new tab? ]
                 */
                if (empty($contact['uid'])) {
-                       $connlnk = 'follow/?url=' . $contact['url'];
                        $menu = [
-                               'profile' => [L10n::t('View Profile'),   $profile_link, true],
-                               'network' => [L10n::t('Network Posts'),  $posts_link,   false],
-                               'edit'    => [L10n::t('View Contact'),   $contact_url,  false],
-                               'follow'  => [L10n::t('Connect/Follow'), $connlnk,      true],
+                               'profile' => [L10n::t('View Profile')  , $profile_link , true],
+                               'network' => [L10n::t('Network Posts') , $posts_link   , false],
+                               'edit'    => [L10n::t('View Contact')  , $contact_url  , false],
+                               'follow'  => [L10n::t('Connect/Follow'), $follow_link  , true],
+                               'unfollow'=> [L10n::t('UnFollow')      , $unfollow_link, true],
                        ];
                } else {
                        $menu = [
-                               'status'  => [L10n::t('View Status'),   $status_link,       true],
-                               'profile' => [L10n::t('View Profile'),  $profile_link,      true],
-                               'photos'  => [L10n::t('View Photos'),   $photos_link,       true],
-                               'network' => [L10n::t('Network Posts'), $posts_link,        false],
-                               'edit'    => [L10n::t('View Contact'),  $contact_url,       false],
-                               'drop'    => [L10n::t('Drop Contact'),  $contact_drop_link, false],
-                               'pm'      => [L10n::t('Send PM'),       $pm_url,            false],
-                               'poke'    => [L10n::t('Poke'),          $poke_link,         false],
+                               'status'  => [L10n::t('View Status')   , $status_link      , true],
+                               'profile' => [L10n::t('View Profile')  , $profile_link     , true],
+                               'photos'  => [L10n::t('View Photos')   , $photos_link      , true],
+                               'network' => [L10n::t('Network Posts') , $posts_link       , false],
+                               'edit'    => [L10n::t('View Contact')  , $contact_url      , false],
+                               'drop'    => [L10n::t('Drop Contact')  , $contact_drop_link, false],
+                               'pm'      => [L10n::t('Send PM')       , $pm_url           , false],
+                               'poke'    => [L10n::t('Poke')          , $poke_link        , false],
+                               'follow'  => [L10n::t('Connect/Follow'), $follow_link      , true],
+                               'unfollow'=> [L10n::t('UnFollow')      , $unfollow_link    , true],
                        ];
+
+                       if (!empty($contact['pending'])) {
+                               $intro = DBA::selectFirst('intro', ['id'], ['contact-id' => $contact['id']]);
+                               if (DBA::isResult($intro)) {
+                                       $menu['follow'] = [L10n::t('Approve'), 'notifications/intros/' . $intro['id'], true];
+                               }
+                       }
                }
 
                $args = ['contact' => $contact, 'menu' => &$menu];
@@ -1392,12 +1458,27 @@ class Contact extends BaseObject
                if (DBA::isResult($contact)) {
                        $contact_id = $contact["id"];
 
-                       // Update the contact every 7 days
-                       $update_contact = ($contact['updated'] < DateTimeFormat::utc('now -7 days'));
+                       // Update the contact every 7 days (Don't update mail or feed contacts)
+                       if (in_array($contact['network'], Protocol::FEDERATED)) {
+                               $update_contact = ($contact['updated'] < DateTimeFormat::utc('now -7 days'));
 
-                       // We force the update if the avatar is empty
-                       if (empty($contact['avatar'])) {
-                               $update_contact = true;
+                               // We force the update if the avatar is empty
+                               if (empty($contact['avatar'])) {
+                                       $update_contact = true;
+                               }
+                       } elseif (empty($default) && in_array($contact['network'], [Protocol::MAIL, Protocol::PHANTOM]) && ($uid == 0)) {
+                               // Update public mail accounts via their user's accounts
+                               $fields = ['network', 'addr', 'name', 'nick', 'avatar', 'photo', 'thumb', 'micro'];
+                               $mailcontact = DBA::selectFirst('contact', $fields, ["`addr` = ? AND `network` = ? AND `uid` != 0", $url, Protocol::MAIL]);
+                               if (!DBA::isResult($mailcontact)) {
+                                       $mailcontact = DBA::selectFirst('contact', $fields, ["`nurl` = ? AND `network` = ? AND `uid` != 0", $url, Protocol::MAIL]);
+                               }
+
+                               if (DBA::isResult($mailcontact)) {
+                                       DBA::update('contact', $mailcontact, ['id' => $contact_id]);
+                               }
+
+                               $update_contact = false;
                        }
 
                        // Update the contact in the background if needed but it is called by the frontend
@@ -1428,10 +1509,9 @@ class Contact extends BaseObject
 
                if (empty($data)) {
                        $data = Probe::uri($url, "", $uid);
-
                        // Ensure that there is a gserver entry
                        if (!empty($data['baseurl']) && ($data['network'] != Protocol::PHANTOM)) {
-                               PortableContact::checkServer($data['baseurl']);
+                               GServer::check($data['baseurl']);
                        }
                }
 
@@ -1454,25 +1534,25 @@ class Contact extends BaseObject
                                'created'   => DateTimeFormat::utcNow(),
                                'url'       => $data['url'],
                                'nurl'      => Strings::normaliseLink($data['url']),
-                               'addr'      => defaults($data, 'addr', ''),
-                               'alias'     => defaults($data, 'alias', ''),
-                               'notify'    => defaults($data, 'notify', ''),
-                               'poll'      => defaults($data, 'poll', ''),
-                               'name'      => defaults($data, 'name', ''),
-                               'nick'      => defaults($data, 'nick', ''),
-                               'photo'     => defaults($data, 'photo', ''),
-                               'keywords'  => defaults($data, 'keywords', ''),
-                               'location'  => defaults($data, 'location', ''),
-                               'about'     => defaults($data, 'about', ''),
+                               'addr'      => $data['addr'] ?? '',
+                               'alias'     => $data['alias'] ?? '',
+                               'notify'    => $data['notify'] ?? '',
+                               'poll'      => $data['poll'] ?? '',
+                               'name'      => $data['name'] ?? '',
+                               'nick'      => $data['nick'] ?? '',
+                               'photo'     => $data['photo'] ?? '',
+                               'keywords'  => $data['keywords'] ?? '',
+                               'location'  => $data['location'] ?? '',
+                               'about'     => $data['about'] ?? '',
                                'network'   => $data['network'],
-                               'pubkey'    => defaults($data, 'pubkey', ''),
+                               'pubkey'    => $data['pubkey'] ?? '',
                                'rel'       => self::SHARING,
-                               'priority'  => defaults($data, 'priority', 0),
-                               'batch'     => defaults($data, 'batch', ''),
-                               'request'   => defaults($data, 'request', ''),
-                               'confirm'   => defaults($data, 'confirm', ''),
-                               'poco'      => defaults($data, 'poco', ''),
-                               'baseurl'   => defaults($data, 'baseurl', ''),
+                               'priority'  => $data['priority'] ?? 0,
+                               'batch'     => $data['batch'] ?? '',
+                               'request'   => $data['request'] ?? '',
+                               'confirm'   => $data['confirm'] ?? '',
+                               'poco'      => $data['poco'] ?? '',
+                               'baseurl'   => $data['baseurl'] ?? '',
                                'name-date' => DateTimeFormat::utcNow(),
                                'uri-date'  => DateTimeFormat::utcNow(),
                                'avatar-date' => DateTimeFormat::utcNow(),
@@ -1488,7 +1568,7 @@ class Contact extends BaseObject
                        if (!DBA::isResult($contact)) {
                                Logger::info('Create new contact', $fields);
 
-                               DBA::insert('contact', $fields);
+                               self::insert($fields);
 
                                // We intentionally aren't using lastInsertId here. There is a chance for duplicates.
                                $contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
@@ -1539,7 +1619,7 @@ class Contact extends BaseObject
                        $fields = ['addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'baseurl'];
 
                        foreach ($fields as $field) {
-                               $updated[$field] = defaults($data, $field, $contact[$field]);
+                               $updated[$field] = ($data[$field] ?? '') ?: $contact[$field];
                        }
 
                        if (($updated['addr'] != $contact['addr']) || (!empty($data['alias']) && ($data['alias'] != $contact['alias']))) {
@@ -1556,6 +1636,50 @@ class Contact extends BaseObject
                return $contact_id;
        }
 
+       /**
+        * @brief Checks if the contact is archived
+        *
+        * @param int $cid contact id
+        *
+        * @return boolean Is the contact archived?
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public static function isArchived(int $cid)
+       {
+               if ($cid == 0) {
+                       return false;
+               }
+
+               $contact = DBA::selectFirst('contact', ['archive', 'url', 'batch'], ['id' => $cid]);
+               if (!DBA::isResult($contact)) {
+                       return false;
+               }
+
+               if ($contact['archive']) {
+                       return true;
+               }
+
+               // Check status of ActivityPub endpoints
+               $apcontact = APContact::getByURL($contact['url'], false);
+               if (!empty($apcontact)) {
+                       if (!empty($apcontact['inbox']) && DBA::exists('inbox-status', ['archive' => true, 'url' => $apcontact['inbox']])) {
+                               return true;
+                       }
+
+                       if (!empty($apcontact['sharedinbox']) && DBA::exists('inbox-status', ['archive' => true, 'url' => $apcontact['sharedinbox']])) {
+                               return true;
+                       }
+               }
+
+               // Check status of Diaspora endpoints
+               if (!empty($contact['batch'])) {
+                       $condition = ['archive' => true, 'uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
+                       return DBA::exists('contact', $condition);
+                }
+
+               return false;
+       }
+
        /**
         * @brief Checks if the contact is blocked
         *
@@ -1630,7 +1754,7 @@ class Contact extends BaseObject
                        $sql = "`item`.`uid` = ?";
                }
 
-               $contact_field = ($contact["contact-type"] == self::TYPE_COMMUNITY ? 'owner-id' : 'author-id');
+               $contact_field = ((($contact["contact-type"] == self::TYPE_COMMUNITY) || ($contact['network'] == Protocol::MAIL)) ? 'owner-id' : 'author-id');
 
                if ($thread_mode) {
                        $condition = ["`$contact_field` = ? AND `gravity` = ? AND " . $sql,
@@ -1795,16 +1919,22 @@ class Contact extends BaseObject
         /**
         * @brief Helper function for "updateFromProbe". Updates personal and public contact
         *
-        * @param array $contact The personal contact entry
-        * @param array $fields  The fields that are updated
+        * @param integer $id      contact id
+        * @param integer $uid     user id
+        * @param string  $url     The profile URL of the contact
+        * @param array   $fields  The fields that are updated
+        *
         * @throws \Exception
         */
        private static function updateContact($id, $uid, $url, array $fields)
        {
-               DBA::update('contact', $fields, ['id' => $id]);
+               if (!DBA::update('contact', $fields, ['id' => $id])) {
+                       Logger::info('Couldn\'t update contact.', ['id' => $id, 'fields' => $fields]);
+                       return;
+               }
 
                // Search for duplicated contacts and get rid of them
-               if (self::handleDuplicates(Strings::normaliseLink($url), $uid, $id) || ($uid != 0)) {
+               if (self::removeDuplicates(Strings::normaliseLink($url), $uid) || ($uid != 0)) {
                        return;
                }
 
@@ -1814,6 +1944,11 @@ class Contact extends BaseObject
                // Archive or unarchive the contact. We only need to do this for the public contact.
                // The archive/unarchive function will update the personal contacts by themselves.
                $contact = DBA::selectFirst('contact', [], ['id' => $id]);
+               if (!DBA::isResult($contact)) {
+                       Logger::info('Couldn\'t select contact for archival.', ['id' => $id]);
+                       return;
+               }
+
                if (!empty($fields['success_update'])) {
                        self::unmarkForArchival($contact);
                } elseif (!empty($fields['failure_update'])) {
@@ -1841,15 +1976,14 @@ class Contact extends BaseObject
        }
 
         /**
-        * @brief Helper function for "updateFromProbe". Remove duplicated contacts
+        * @brief Remove duplicated contacts
         *
         * @param string  $nurl  Normalised contact url
         * @param integer $uid   User id
-        * @param integer $id    Contact id of a duplicate
         * @return boolean
         * @throws \Exception
         */
-       private static function handleDuplicates($nurl, $uid, $id)
+       public static function removeDuplicates(string $nurl, int $uid)
        {
                $condition = ['nurl' => $nurl, 'uid' => $uid, 'deleted' => false, 'network' => Protocol::FEDERATED];
                $count = DBA::count('contact', $condition);
@@ -1864,7 +1998,7 @@ class Contact extends BaseObject
                }
 
                $first = $first_contact['id'];
-               Logger::info('Found duplicates', ['count' => $count, 'id' => $id, 'first' => $first, 'uid' => $uid, 'nurl' => $nurl]);
+               Logger::info('Found duplicates', ['count' => $count, 'first' => $first, 'uid' => $uid, 'nurl' => $nurl]);
                if (($uid != 0 && ($first_contact['network'] == Protocol::DFRN))) {
                        // Don't handle non public DFRN duplicates by now (legacy DFRN is very special because of the key handling)
                        Logger::info('Not handling non public DFRN duplicate', ['uid' => $uid, 'nurl' => $nurl]);
@@ -1933,9 +2067,8 @@ class Contact extends BaseObject
                        return true;
                }
 
-               // If Probe::uri fails the network code will be different (mostly "feed" or "unkn")
-               if (!in_array($ret['network'], Protocol::NATIVE_SUPPORT) ||
-                       (in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]) && ($ret['network'] != $contact['network']))) {
+               // If Probe::uri fails the network code will be different ("feed" or "unkn")
+               if (in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]) && ($ret['network'] != $contact['network'])) {
                        if ($force && ($uid == 0)) {
                                self::updateContact($id, $uid, $ret['url'], ['last-update' => $updated, 'failure_update' => $updated]);
                        }
@@ -1975,7 +2108,7 @@ class Contact extends BaseObject
                        }
                }
 
-               if ($ret['network'] != Protocol::FEED) {
+               if (!empty($ret['photo']) && ($ret['network'] != Protocol::FEED)) {
                        self::updateAvatar($ret['photo'], $uid, $id, $update || $force);
                }
 
@@ -2195,7 +2328,13 @@ class Contact extends BaseObject
 
                $hidden = (($protocol === Protocol::MAIL) ? 1 : 0);
 
-               $pending = in_array($protocol, [Protocol::ACTIVITYPUB]);
+               $pending = false;
+               if ($protocol == Protocol::ACTIVITYPUB) {
+                       $apcontact = APContact::getByURL($url, false);
+                       if (isset($apcontact['manually-approve'])) {
+                               $pending = (bool)$apcontact['manually-approve'];
+                       }                       
+               }
 
                if (in_array($protocol, [Protocol::MAIL, Protocol::DIASPORA, Protocol::ACTIVITYPUB])) {
                        $writeable = 1;
@@ -2211,7 +2350,7 @@ class Contact extends BaseObject
                        $new_relation = (in_array($protocol, [Protocol::MAIL]) ? self::FRIEND : self::SHARING);
 
                        // create contact record
-                       DBA::insert('contact', [
+                       self::insert([
                                'uid'     => $uid,
                                'created' => DateTimeFormat::utcNow(),
                                'url'     => $ret['url'],
@@ -2263,7 +2402,7 @@ class Contact extends BaseObject
                        if (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
                                // create a follow slap
                                $item = [];
-                               $item['verb'] = ACTIVITY_FOLLOW;
+                               $item['verb'] = Activity::FOLLOW;
                                $item['follow'] = $contact["url"];
                                $item['body'] = '';
                                $item['title'] = '';
@@ -2366,9 +2505,9 @@ class Contact extends BaseObject
                        return false;
                }
 
-               $url = defaults($datarray, 'author-link', $pub_contact['url']);
+               $url = ($datarray['author-link'] ?? '') ?: $pub_contact['url'];
                $name = $pub_contact['name'];
-               $photo = defaults($pub_contact, 'avatar', $pub_contact["photo"]);
+               $photo = ($pub_contact['avatar'] ?? '') ?: $pub_contact["photo"];
                $nick = $pub_contact['nick'];
                $network = $pub_contact['network'];
 
@@ -2399,6 +2538,9 @@ class Contact extends BaseObject
                                                ['id' => $contact['id'], 'uid' => $importer['uid']]);
                        }
 
+                       // Ensure to always have the correct network type, independent from the connection request method
+                       self::updateFromProbe($contact['id'], '', true);
+
                        return true;
                } else {
                        // send email notification to owner?
@@ -2424,15 +2566,14 @@ class Contact extends BaseObject
                                'writable' => 1,
                        ]);
 
-                       $contact_record = [
-                               'id' => DBA::lastInsertId(),
-                               'network' => $network,
-                               'name' => $name,
-                               'url' => $url,
-                               'photo' => $photo
-                       ];
+                       $contact_id = DBA::lastInsertId();
+
+                       // Ensure to always have the correct network type, independent from the connection request method
+                       self::updateFromProbe($contact_id, '', true);
 
-                       Contact::updateAvatar($photo, $importer["uid"], $contact_record["id"], true);
+                       Contact::updateAvatar($photo, $importer["uid"], $contact_id, true);
+
+                       $contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]);
 
                        /// @TODO Encapsulate this into a function/method
                        $fields = ['uid', 'username', 'email', 'page-flags', 'notify-flags', 'language'];
@@ -2463,7 +2604,7 @@ class Contact extends BaseObject
                                                'source_name'  => ((strlen(stripslashes($contact_record['name']))) ? stripslashes($contact_record['name']) : L10n::t('[Name Withheld]')),
                                                'source_link'  => $contact_record['url'],
                                                'source_photo' => $contact_record['photo'],
-                                               'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
+                                               'verb'         => ($sharing ? Activity::FRIEND : Activity::FOLLOW),
                                                'otype'        => 'intro'
                                        ]);
                                }
@@ -2575,7 +2716,7 @@ class Contact extends BaseObject
         */
        public static function magicLink($contact_url, $url = '')
        {
-               if (!local_user() && !remote_user()) {
+               if (!Session::isAuthenticated()) {
                        return $url ?: $contact_url; // Equivalent to: ($url != '') ? $url : $contact_url;
                }
 
@@ -2587,7 +2728,7 @@ class Contact extends BaseObject
                // Prevents endless loop in case only a non-public contact exists for the contact URL
                unset($data['uid']);
 
-               return self::magicLinkByContact($data, $contact_url);
+               return self::magicLinkByContact($data, $url ?: $contact_url);
        }
 
        /**
@@ -2619,8 +2760,10 @@ class Contact extends BaseObject
         */
        public static function magicLinkByContact($contact, $url = '')
        {
-               if ((!local_user() && !remote_user()) || ($contact['network'] != Protocol::DFRN)) {
-                       return $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
+               $destination = $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
+
+               if (!Session::isAuthenticated() || ($contact['network'] != Protocol::DFRN)) {
+                       return $destination;
                }
 
                // Only redirections to the same host do make sense
@@ -2633,12 +2776,12 @@ class Contact extends BaseObject
                }
 
                if (empty($contact['id'])) {
-                       return $url ?: $contact['url'];
+                       return $destination;
                }
 
                $redirect = 'redir/' . $contact['id'];
 
-               if ($url != '') {
+               if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
                        $redirect .= '?url=' . $url;
                }