]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Contact.php
Fix: Images must not be removed on preview
[friendica.git] / src / Model / Contact.php
index c5b7017a391abe31e1e265908fbf55e543841697..8f9cb4d051c76832e52a4c9641fcf4a09f766ceb 100644 (file)
@@ -34,11 +34,15 @@ use Friendica\Core\Worker;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Network\HTTPClient\Client\HttpClientOptions;
 use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
+use Friendica\Object\Image;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Util\DateTimeFormat;
+use Friendica\Util\HTTPSignature;
 use Friendica\Util\Images;
 use Friendica\Util\Network;
 use Friendica\Util\Proxy;
@@ -96,17 +100,17 @@ class Contact
         * Relationship types
         * @{
         */
-       const NOTHING  = 0;
-       const FOLLOWER = 1;
-       const SHARING  = 2;
-       const FRIEND   = 3;
-       const SELF     = 4;
+       const NOTHING  = 0; // There is no relationship between the contact and the user
+       const FOLLOWER = 1; // The contact is following this user (the contact is the subscriber)
+       const SHARING  = 2; // The contact shares their content with this user (the user is the subscriber)
+       const FRIEND   = 3; // There is a mutual relationship between the contact and the user
+       const SELF     = 4; // This is the user theirself
        /**
         * @}
         */
 
         const MIRROR_DEACTIVATED = 0;
-        const MIRROR_FORWARDED = 1;
+        const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST
         const MIRROR_OWN_POST = 2;
         const MIRROR_NATIVE_RESHARE = 3;
 
@@ -136,6 +140,18 @@ class Contact
                return $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
+        * @throws \Exception
+        */
+       public static function selectAccountToArray(array $fields = [], array $condition = [], array $params = []): array
+       {
+               return DBA::selectToArray('account-user-view', $fields, $condition, $params);
+       }
+
        /**
         * @param array $fields    Array of selected fields, empty for all
         * @param array $condition Array of fields for condition
@@ -729,7 +745,6 @@ class Contact
                        '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'],
                        'name-date'   => DateTimeFormat::utcNow(),
                        'uri-date'    => DateTimeFormat::utcNow(),
                        'avatar-date' => DateTimeFormat::utcNow(),
@@ -811,7 +826,6 @@ class Contact
                        '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'],
                ];
 
 
@@ -902,7 +916,7 @@ class Contact
                self::clearFollowerFollowingEndpointCache($contact['uid']);
 
                // Archive the contact
-               self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
+               self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'rel' => self::NOTHING, 'deleted' => true], ['id' => $id]);
 
                if (!DBA::exists('contact', ['uri-id' => $contact['uri-id'], 'deleted' => false])) {
                        Avatar::deleteCache($contact);
@@ -1149,7 +1163,7 @@ class Contact
                $sparkle = false;
                if (($contact['network'] === Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
                        $sparkle = true;
-                       $profile_link = DI::baseUrl() . '/redir/' . $contact['id'];
+                       $profile_link = 'contact/redir/' . $contact['id'];
                } else {
                        $profile_link = $contact['url'];
                }
@@ -1160,25 +1174,25 @@ class Contact
 
                if ($sparkle) {
                        $status_link = $profile_link . '/status';
-                       $photos_link = str_replace('/profile/', '/photos/', $profile_link);
+                       $photos_link = $profile_link . '/photos';
                        $profile_link = $profile_link . '/profile';
                }
 
                if (self::canReceivePrivateMessages($contact) && empty($contact['pending'])) {
-                       $pm_url = DI::baseUrl() . '/message/new/' . $contact['id'];
+                       $pm_url = 'message/new/' . $contact['id'];
                }
 
-               $contact_url = DI::baseUrl() . '/contact/' . $contact['id'];
+               $contact_url = 'contact/' . $contact['id'];
 
-               $posts_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
+               $posts_link = 'contact/' . $contact['id'] . '/conversations';
 
                $follow_link = '';
                $unfollow_link = '';
                if (!$contact['self'] && Protocol::supportsFollow($contact['network'])) {
                        if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
-                               $unfollow_link = 'unfollow?url=' . urlencode($contact['url']) . '&auto=1';
+                               $unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1';
                        } elseif(!$contact['pending']) {
-                               $follow_link = 'follow?url=' . urlencode($contact['url']) . '&auto=1';
+                               $follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1';
                        }
                }
 
@@ -1192,7 +1206,7 @@ class Contact
                                'network' => [DI::l10n()->t('Network Posts') , $posts_link   , false],
                                'edit'    => [DI::l10n()->t('View Contact')  , $contact_url  , false],
                                'follow'  => [DI::l10n()->t('Connect/Follow'), $follow_link  , true],
-                               'unfollow'=> [DI::l10n()->t('UnFollow')      , $unfollow_link, true],
+                               'unfollow'=> [DI::l10n()->t('Unfollow')      , $unfollow_link, true],
                        ];
                } else {
                        $menu = [
@@ -1203,7 +1217,7 @@ class Contact
                                'edit'    => [DI::l10n()->t('View Contact')  , $contact_url      , false],
                                'pm'      => [DI::l10n()->t('Send PM')       , $pm_url           , false],
                                'follow'  => [DI::l10n()->t('Connect/Follow'), $follow_link      , true],
-                               'unfollow'=> [DI::l10n()->t('UnFollow')      , $unfollow_link    , true],
+                               'unfollow'=> [DI::l10n()->t('Unfollow')      , $unfollow_link    , true],
                        ];
 
                        if (!empty($contact['pending'])) {
@@ -1359,9 +1373,10 @@ class Contact
                                'writable'  => 1,
                                'blocked'   => 0,
                                'readonly'  => 0,
-                               'pending'   => 0];
+                               'pending'   => 0,
+                       ];
 
-                       $condition = ['nurl' => Strings::normaliseLink($data["url"]), 'uid' => $uid, 'deleted' => false];
+                       $condition = ['nurl' => Strings::normaliseLink($data['url']), 'uid' => $uid, 'deleted' => false];
 
                        // Before inserting we do check if the entry does exist now.
                        $contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
@@ -1384,7 +1399,17 @@ class Contact
                }
 
                if ($data['network'] == Protocol::DIASPORA) {
-                       FContact::updateFromProbeArray($data);
+                       try {
+                               DI::dsprContact()->updateFromProbeArray($data);
+                       } catch (\InvalidArgumentException $e) {
+                               Logger::error($e->getMessage(), ['url' => $url, 'data' => $data]);
+                       }
+               } elseif (!empty($data['networks'][Protocol::DIASPORA])) {
+                       try {
+                               DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
+                       } catch (\InvalidArgumentException $e) {
+                               Logger::error($e->getMessage(), ['url' => $url, 'data' => $data['networks'][Protocol::DIASPORA]]);
+                       }
                }
 
                self::updateFromProbeArray($contact_id, $data);
@@ -2046,9 +2071,10 @@ class Contact
         * @param integer $cid     contact id
         * @param string  $size    One of the Proxy::SIZE_* constants
         * @param string  $updated Contact update date
+        * @param bool    $static  If "true" a parameter is added to convert the avatar to a static one
         * @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 = '', bool $static = false): string
        {
                // We have to fetch the "updated" variable when it wasn't provided
                // The parameter can be provided to improve performance
@@ -2078,7 +2104,15 @@ class Contact
                                $url .= Proxy::PIXEL_LARGE . '/';
                                break;
                }
-               return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
+               $query_params = [];
+               if ($updated) {
+                       $query_params['ts'] = strtotime($updated);
+               }
+               if ($static) {
+                       $query_params['static'] = true;
+               }
+
+               return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
        }
 
        /**
@@ -2103,9 +2137,10 @@ class Contact
         * @param integer $cid     contact id
         * @param string  $size    One of the Proxy::SIZE_* constants
         * @param string  $updated Contact update date
+        * @param bool    $static  If "true" a parameter is added to convert the header to a static one
         * @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 = '', bool $static = false): string
        {
                // We have to fetch the "updated" variable when it wasn't provided
                // The parameter can be provided to improve performance
@@ -2136,7 +2171,15 @@ class Contact
                                break;
                }
 
-               return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
+               $query_params = [];
+               if ($updated) {
+                       $query_params['ts'] = strtotime($updated);
+               }
+               if ($static) {
+                       $query_params['static'] = true;
+               }
+
+               return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
        }
 
        /**
@@ -2154,7 +2197,7 @@ class Contact
         */
        public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false)
        {
-               $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
+               $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
                        ['id' => $cid, 'self' => false]);
                if (!DBA::isResult($contact)) {
                        return;
@@ -2164,8 +2207,19 @@ class Contact
 
                // Only update the cached photo links of public contacts when they already are cached
                if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) {
-                       if ($contact['avatar'] != $avatar) {
-                               self::update(['avatar' => $avatar], ['id' => $cid]);
+                       if (($contact['avatar'] != $avatar) || empty($contact['blurhash'])) {
+                               $update_fields = ['avatar' => $avatar];
+                               $fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
+
+                               $img_str = $fetchResult->getBody();
+                               if (!empty($img_str)) {
+                                       $image = new Image($img_str, Images::getMimeTypeByData($img_str));
+                                       if ($image->isValid()) {
+                                               $update_fields['blurhash'] = $image->getBlurHash();
+                                       }
+                               }
+
+                               self::update($update_fields, ['id' => $cid]);
                                Logger::info('Only update the avatar', ['id' => $cid, 'avatar' => $avatar, 'contact' => $contact]);
                        }
                        return;
@@ -2236,7 +2290,7 @@ class Contact
                                if ($update) {
                                        $photos = Photo::importProfilePhoto($avatar, $uid, $cid, true);
                                        if ($photos) {
-                                               $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'avatar-date' => DateTimeFormat::utcNow()];
+                                               $fields = ['avatar' => $avatar, 'photo' => $photos[0], 'thumb' => $photos[1], 'micro' => $photos[2], 'blurhash' => $photos[3], 'avatar-date' => DateTimeFormat::utcNow()];
                                                $update = !empty($fields);
                                                Logger::debug('Created new cached avatars', ['id' => $cid, 'uid' => $uid, 'owner-uid' => $local_uid]);
                                        } else {
@@ -2454,13 +2508,23 @@ class Contact
                        return false;
                }
 
-               $ret = Probe::uri($contact['url'], $network, $contact['uid']);
+               $data = Probe::uri($contact['url'], $network, $contact['uid']);
 
-               if ($ret['network'] == Protocol::DIASPORA) {
-                       FContact::updateFromProbeArray($ret);
+               if ($data['network'] == Protocol::DIASPORA) {
+                       try {
+                               DI::dsprContact()->updateFromProbeArray($data);
+                       } catch (\InvalidArgumentException $e) {
+                               Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
+                       }
+               } elseif (!empty($data['networks'][Protocol::DIASPORA])) {
+                       try {
+                               DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
+                       } catch (\InvalidArgumentException $e) {
+                               Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
+                       }
                }
 
-               return self::updateFromProbeArray($id, $ret);
+               return self::updateFromProbeArray($id, $data);
        }
 
        /**
@@ -2642,7 +2706,7 @@ class Contact
                }
 
                $update = false;
-               $guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url'], parse_url($ret['url'], PHP_URL_HOST));
+               $guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url']);
 
                // make sure to not overwrite existing values with blank entries except some technical fields
                $keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl'];
@@ -3155,11 +3219,14 @@ class Contact
                        return;
                }
 
+               Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
+
                self::clearFollowerFollowingEndpointCache($contact['uid']);
 
                $cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
-
-               DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
+               if (!empty($cdata['public'])) {
+                       DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
+               }
        }
 
        /**
@@ -3178,6 +3245,8 @@ class Contact
                } else {
                        self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
                }
+
+               Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
        }
 
        /**
@@ -3323,7 +3392,7 @@ class Contact
                        return $destination;
                }
 
-               $redirect = 'redir/' . $contact['id'];
+               $redirect = 'contact/redir/' . $contact['id'];
 
                if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
                        $redirect .= '?url=' . $url;
@@ -3372,11 +3441,13 @@ class Contact
         * @param string $search Name or nick
         * @param string $mode   Search mode (e.g. "community")
         * @param int    $uid    User ID
+        * @param int    $limit  Maximum amount of returned values
+        * @param int    $offset Limit offset
         *
         * @return array with search results
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function searchByName(string $search, string $mode = '', int $uid = 0): array
+       public static function searchByName(string $search, string $mode = '', int $uid = 0, int $limit = 0, int $offset = 0): array
        {
                if (empty($search)) {
                        return [];
@@ -3396,6 +3467,8 @@ class Contact
 
                if ($uid == 0) {
                        $condition['blocked'] = false;
+               } else {
+                       $condition['rel'] = [Contact::SHARING, Contact::FRIEND];
                }
 
                // check if we search only communities or every contact
@@ -3405,12 +3478,19 @@ class Contact
 
                $search .= '%';
 
+               $params = [];
+
+               if (!empty($limit) && !empty($offset)) {
+                       $params['limit'] = [$offset, $limit];
+               } elseif (!empty($limit)) {
+                       $params['limit'] = $limit;
+               }
+
                $condition = DBA::mergeConditions($condition,
                        ["(NOT `unsearchable` OR `nurl` IN (SELECT `nurl` FROM `owner-view` WHERE `publish` OR `net-publish`))
                        AND (`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]);
 
-               $contacts = self::selectToArray([], $condition);
-               return $contacts;
+               return self::selectToArray([], $condition, $params);
        }
 
        /**
@@ -3465,4 +3545,17 @@ class Contact
 
                return [];
        }
+
+       /**
+        * Checks, if contacts with the given condition exists
+        *
+        * @param array $condition
+        *
+        * @return bool
+        * @throws \Exception
+        */
+       public static function exists(array $condition): bool
+       {
+               return DBA::exists('contact', $condition);
+       }
 }