]> git.mxchange.org Git - friendica.git/commitdiff
[Database version 1499] Create new paradigm classes for Diaspora Contact
authorHypolite Petovan <hypolite@mrpetovan.com>
Wed, 15 Sep 2021 04:36:01 +0000 (00:36 -0400)
committerHypolite Petovan <hypolite@mrpetovan.com>
Mon, 5 Dec 2022 05:00:50 +0000 (00:00 -0500)
- Switch table fcontact for dcontact in views

14 files changed:
database.sql
src/Console/Relocate.php
src/DI.php
src/Database/Database.php
src/Model/Contact.php
src/Network/Probe.php
src/Protocol/DFRN.php
src/Protocol/Diaspora.php
src/Protocol/Diaspora/Entity/DiasporaContact.php [new file with mode: 0644]
src/Protocol/Diaspora/Factory/DiasporaContact.php [new file with mode: 0644]
src/Protocol/Diaspora/Repository/DiasporaContact.php [new file with mode: 0644]
src/Worker/Delivery.php
src/Worker/ExpirePosts.php
static/dbview.config.php

index 35831813469b5397b64fb9a626f30cca2184a7da..ae2f8096ba2e95803ea8fce4036caaa2f39b425d 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
 -- Friendica 2022.12-dev (Giant Rhubarb)
--- DB_UPDATE_VERSION 1498
+-- DB_UPDATE_VERSION 1499
 -- ------------------------------------------
 
 
@@ -2836,11 +2836,11 @@ CREATE VIEW `account-view` AS SELECT
        `contact`.`blocked` AS `blocked`,
        `contact`.`notify` AS `dfrn-notify`,
        `contact`.`poll` AS `dfrn-poll`,
-       `fcontact`.`guid` AS `diaspora-guid`,
-       `fcontact`.`batch` AS `diaspora-batch`,
-       `fcontact`.`notify` AS `diaspora-notify`,
-       `fcontact`.`poll` AS `diaspora-poll`,
-       `fcontact`.`alias` AS `diaspora-alias`,
+       `item-uri`.`guid` AS `diaspora-guid`,
+       `diaspora-contact`.`batch` AS `diaspora-batch`,
+       `diaspora-contact`.`notify` AS `diaspora-notify`,
+       `diaspora-contact`.`poll` AS `diaspora-poll`,
+       `diaspora-contact`.`alias` AS `diaspora-alias`,
        `apcontact`.`uuid` AS `ap-uuid`,
        `apcontact`.`type` AS `ap-type`,
        `apcontact`.`following` AS `ap-following`,
@@ -2858,7 +2858,7 @@ CREATE VIEW `account-view` AS SELECT
        FROM `contact`
                        LEFT JOIN `item-uri` ON `item-uri`.`id` = `contact`.`uri-id`
                        LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `contact`.`uri-id`
-                       LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = contact.`uri-id`
+                       LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = contact.`uri-id`
                        LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`
                        WHERE `contact`.`uid` = 0;
 
@@ -2937,14 +2937,14 @@ CREATE VIEW `account-user-view` AS SELECT
        `ucontact`.`reason` AS `reason`,
        `contact`.`notify` AS `dfrn-notify`,
        `contact`.`poll` AS `dfrn-poll`,
-       `fcontact`.`guid` AS `diaspora-guid`,
-       `fcontact`.`batch` AS `diaspora-batch`,
-       `fcontact`.`notify` AS `diaspora-notify`,
-       `fcontact`.`poll` AS `diaspora-poll`,
-       `fcontact`.`alias` AS `diaspora-alias`,
-       `fcontact`.`interacting_count` AS `diaspora-interacting_count`,
-       `fcontact`.`interacted_count` AS `diaspora-interacted_count`,
-       `fcontact`.`post_count` AS `diaspora-post_count`,
+       `item-uri`.`guid` AS `diaspora-guid`,
+       `diaspora-contact`.`batch` AS `diaspora-batch`,
+       `diaspora-contact`.`notify` AS `diaspora-notify`,
+       `diaspora-contact`.`poll` AS `diaspora-poll`,
+       `diaspora-contact`.`alias` AS `diaspora-alias`,
+       `diaspora-contact`.`interacting_count` AS `diaspora-interacting_count`,
+       `diaspora-contact`.`interacted_count` AS `diaspora-interacted_count`,
+       `diaspora-contact`.`post_count` AS `diaspora-post_count`,
        `apcontact`.`uuid` AS `ap-uuid`,
        `apcontact`.`type` AS `ap-type`,
        `apcontact`.`following` AS `ap-following`,
@@ -2963,7 +2963,7 @@ CREATE VIEW `account-user-view` AS SELECT
                        INNER JOIN `contact` ON `contact`.`uri-id` = `ucontact`.`uri-id` AND `contact`.`uid` = 0
                        LEFT JOIN `item-uri` ON `item-uri`.`id` = `ucontact`.`uri-id`
                        LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `ucontact`.`uri-id`
-                       LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = `ucontact`.`uri-id` AND `fcontact`.`network` = 'dspr'
+                       LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = `ucontact`.`uri-id`
                        LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`;
 
 --
index 729bee392f4ea5bd33ec651989ac6a78868fb887..a90802d81e22f46429c9d53931e66d7fda67d528 100644 (file)
@@ -131,9 +131,9 @@ HELP;
                        $this->out('Updating event table fields');
                        $this->database->replaceInTableFields('event', ['uri'], $old_url, $new_url);
 
-                       $this->out('Updating fcontact table fields');
-                       $this->database->replaceInTableFields('fcontact', ['url', 'photo', 'request', 'batch', 'poll', 'confirm', 'alias'], $old_url, $new_url);
-                       $this->database->replaceInTableFields('fcontact', ['addr'], $old_host, $new_host);
+                       $this->out('Updating diaspora-contact table fields');
+                       $this->database->replaceInTableFields('diaspora-contact', ['alias', 'photo', 'photo-medium', 'photo-small', 'batch', 'notify', 'poll', 'subscribe'], $old_url, $new_url);
+                       $this->database->replaceInTableFields('diaspora-contact', ['addr'], $old_host, $new_host);
 
                        $this->out('Updating fsuggest table fields');
                        $this->database->replaceInTableFields('fsuggest', ['url', 'request', 'photo'], $old_url, $new_url);
index 3a8a9b6d2474eabea20815bed10269ba58cb5fd0..59f48fcb5f1205d3a9d4087a18aa15535c24ca54 100644 (file)
@@ -599,6 +599,11 @@ abstract class DI
                return self::$dice->create(Protocol\Activity::class);
        }
 
+       public static function dsprContact(): Protocol\Diaspora\Repository\DiasporaContact
+       {
+               return self::$dice->create(Protocol\Diaspora\Repository\DiasporaContact::class);
+       }
+
        //
        // "Security" namespace instances
        //
index 036e6ec2cbf6d8ad52d2cce7a4d058f42af60684..b5d4963923801ceddc50009a327f419753dc9847 100644 (file)
@@ -833,7 +833,7 @@ class Database
        /**
         * Check if data exists
         *
-        * @param string $table     Table name in format schema.table (while scheme is optiona)
+        * @param string $table     Table name in format [schema.]table
         * @param array  $condition Array of fields for condition
         *
         * @return boolean Are there rows for that condition?
@@ -1017,7 +1017,7 @@ class Database
        /**
         * Insert a row into a table. Field value objects will be cast as string.
         *
-        * @param string $table          Table name in format schema.table (while scheme is optiona)
+        * @param string $table          Table name in format [schema.]table
         * @param array  $param          parameter array
         * @param int    $duplicate_mode What to do on a duplicated entry
         *
@@ -1068,7 +1068,7 @@ class Database
         * Inserts a row with the provided data in the provided table.
         * If the data corresponds to an existing row through a UNIQUE or PRIMARY index constraints, it updates the row instead.
         *
-        * @param string $table Table name in format schema.table (while scheme is optiona)
+        * @param string $table Table name in format [schema.]table
         * @param array  $param parameter array
         * @return boolean was the insert successful?
         * @throws \Exception
@@ -1116,7 +1116,7 @@ class Database
         *
         * This function can be extended in the future to accept a table array as well.
         *
-        * @param string $table Table name in format schema.table (while scheme is optiona)
+        * @param string $table Table name in format [schema.]table
         * @return boolean was the lock successful?
         * @throws \Exception
         */
@@ -1314,7 +1314,7 @@ class Database
         * Only set $old_fields to a boolean value when you are sure that you will update a single row.
         * When you set $old_fields to "true" then $fields must contain all relevant fields!
         *
-        * @param string        $table      Table name in format schema.table (while scheme is optiona)
+        * @param string        $table      Table name in format [schema.]table
         * @param array         $fields     contains the fields that are updated
         * @param array         $condition  condition array with the key values
         * @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate, false = don't update identical fields)
@@ -1380,7 +1380,7 @@ class Database
        /**
         * Retrieve a single record from a table and returns it in an associative array
         *
-        * @param string $table     Table name in format schema.table (while scheme is optiona)
+        * @param string $table     Table name in format [schema.]table
         * @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
@@ -1406,7 +1406,7 @@ class Database
        /**
         * Select rows from a table and fills an array with the data
         *
-        * @param string $table     Table name in format schema.table (while scheme is optiona)
+        * @param string $table     Table name in format [schema.]table
         * @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
@@ -1479,7 +1479,7 @@ class Database
         *
         * $data = DBA::select($table, $fields, $condition, $params);
         *
-        * @param string $table     Table name in format schema.table (while scheme is optiona)
+        * @param string $table     Table name in format [schema.]table
         * @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
@@ -1519,7 +1519,7 @@ class Database
        /**
         * Counts the rows from a table satisfying the provided condition
         *
-        * @param string $table     Table name in format schema.table (while scheme is optiona)
+        * @param string $table     Table name in format [schema.]table
         * @param array  $condition Array of fields for condition
         * @param array  $params    Array of several parameters
         *
index e8a718f0547ca1759da2f5f9f426660eb3851f7d..e80f0752ea6b4dda10f995041287aaa152438b49 100644 (file)
@@ -1395,7 +1395,7 @@ class Contact
                }
 
                if ($data['network'] == Protocol::DIASPORA) {
-                       FContact::updateFromProbeArray($data);
+                       DI::dsprContact()->updateFromProbeArray($data);
                }
 
                self::updateFromProbeArray($contact_id, $data);
@@ -2486,7 +2486,7 @@ class Contact
                $ret = Probe::uri($contact['url'], $network, $contact['uid']);
 
                if ($ret['network'] == Protocol::DIASPORA) {
-                       FContact::updateFromProbeArray($ret);
+                       DI::dsprContact()->updateFromProbeArray($ret);
                }
 
                return self::updateFromProbeArray($id, $ret);
index 5c188f2522bf95c77f914f73ce96393125dab5b5..d79cd4055f8dcf493519cb4bcb8882d5ff927f0d 100644 (file)
@@ -110,7 +110,8 @@ class Probe
         */
        private static function rearrangeData(array $data): array
        {
-               $fields = ['name', 'nick', 'guid', 'url', 'addr', 'alias', 'photo', 'header',
+               $fields = ['name', 'given_name', 'family_name', 'nick', 'guid', 'url', 'addr', 'alias',
+                       'photo', 'photo_medium', 'photo_small', 'header',
                                'account-type', 'community', 'keywords', 'location', 'about', 'xmpp', 'matrix',
                                'hide', 'batch', 'notify', 'poll', 'request', 'confirm', 'subscribe', 'poco',
                                'following', 'followers', 'inbox', 'outbox', 'sharedinbox',
@@ -124,7 +125,7 @@ class Probe
                                if (in_array($field, $numeric_fields)) {
                                        $newdata[$field] = (int)$data[$field];
                                } else {
-                                       $newdata[$field] = $data[$field];
+                                       $newdata[$field] = trim($data[$field]);
                                }
                        } elseif (!in_array($field, $numeric_fields)) {
                                $newdata[$field] = '';
@@ -1290,9 +1291,19 @@ class Probe
                                $data['name'] = $search->item(0)->nodeValue;
                        }
 
+                       $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' given_name ')]", $vcard); // */
+                       if ($search->length > 0) {
+                               $data["given_name"] = $search->item(0)->nodeValue;
+                       }
+
+                       $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' family_name ')]", $vcard); // */
+                       if ($search->length > 0) {
+                               $data["family_name"] = $search->item(0)->nodeValue;
+                       }
+
                        $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' searchable ')]", $vcard); // */
                        if ($search->length > 0) {
-                               $data['searchable'] = $search->item(0)->nodeValue;
+                               $data['hide'] = (strtolower($search->item(0)->nodeValue) != 'true');
                        }
 
                        $search = $xpath->query("//*[contains(concat(' ', @class, ' '), ' key ')]", $vcard); // */
@@ -1309,7 +1320,7 @@ class Probe
                        }
                }
 
-               $avatar = [];
+               $avatars = [];
                if (!empty($vcard)) {
                        $photos = $xpath->query("//*[contains(concat(' ', @class, ' '), ' photo ') or contains(concat(' ', @class, ' '), ' avatar ')]", $vcard); // */
                        foreach ($photos as $photo) {
@@ -1319,20 +1330,27 @@ class Probe
                                }
 
                                if (isset($attr['src']) && isset($attr['width'])) {
-                                       $avatar[$attr['width']] = $attr['src'];
+                                       $avatars[$attr['width']] = self::fixAvatar($attr['src'], $data['baseurl']);
                                }
 
                                // We don't have a width. So we just take everything that we got.
                                // This is a Hubzilla workaround which doesn't send a width.
-                               if ((sizeof($avatar) == 0) && !empty($attr['src'])) {
-                                       $avatar[] = $attr['src'];
+                               if (!$avatars && !empty($attr['src'])) {
+                                       $avatars[] = self::fixAvatar($attr['src'], $data['baseurl']);
                                }
                        }
                }
 
-               if (sizeof($avatar)) {
-                       ksort($avatar);
-                       $data['photo'] = self::fixAvatar(array_pop($avatar), $data['baseurl']);
+               if ($avatars) {
+                       ksort($avatars);
+                       $data['photo'] = array_pop($avatars);
+                       if ($avatars) {
+                               $data['photo_medium'] = array_pop($avatars);
+                       }
+
+                       if ($avatars) {
+                               $data['photo_small'] = array_pop($avatars);
+                       }
                }
 
                if ($dfrn) {
@@ -1356,7 +1374,6 @@ class Probe
                        }
                }
 
-
                return $data;
        }
 
index a86cf2094dc2343fc353426da5bbd52810dfb3df..621cc2e3cfac0ed52952fd310c60d0acae0db74f 100644 (file)
@@ -45,6 +45,7 @@ use Friendica\Model\Post;
 use Friendica\Model\Profile;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
+use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
@@ -981,12 +982,12 @@ class DFRN
                                }
                        }
 
-                       $fcontact = FContact::getByURL($contact['addr']);
-                       if (empty($fcontact)) {
+                       try {
+                               $pubkey = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']))->pubKey;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
                                Logger::notice('Unable to find contact details for ' . $contact['id'] . ' - ' . $contact['addr']);
                                return -22;
                        }
-                       $pubkey = $fcontact['pubkey'] ?? '';
                } else {
                        $pubkey = '';
                }
index 7fd899f8dd8cdc059dd24d6ea95f1fea2028caf3..bb955dca1ecb728a3fd51d9f4e612fb3003a7d33 100644 (file)
@@ -33,7 +33,6 @@ use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
-use Friendica\Model\FContact;
 use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\ItemURI;
@@ -42,7 +41,7 @@ use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
-use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
@@ -455,9 +454,8 @@ class Diaspora
                }
 
                // Once we have the author URI, go to the web and try to find their public key
-               // (first this will look it up locally if it is in the fcontact cache)
+               // (first this will look it up locally if it is in the diaspora-contact cache)
                // This will also convert diaspora public key from pkcs#1 to pkcs#8
-
                Logger::notice('Fetching key for ' . $author);
                $key = self::key($author);
                if (!$key) {
@@ -795,13 +793,11 @@ class Diaspora
        private static function key(WebFingerUri $uri): string
        {
                Logger::notice('Fetching diaspora key', ['handle' => $uri->getAddr(), 'callstack' => System::callstack(20)]);
-
-               $fcontact = FContact::getByURL($uri);
-               if (!empty($fcontact['pubkey'])) {
-                       return $fcontact['pubkey'];
+               try {
+                       return DI::dsprContact()->getByAddr($uri)->pubKey;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       return '';
                }
-
-               return '';
        }
 
        /**
@@ -822,15 +818,17 @@ class Diaspora
        /**
         * Checks if the given contact url does support ActivityPub
         *
-        * @param string  $url    profile url
-        * @param boolean $update true = always update, false = never update, null = update when not found or outdated
+        * @param string       $url    profile url or WebFinger address
+        * @param boolean|null $update true = always update, false = never update, null = update when not found or outdated
         * @return boolean
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function isSupportedByContactUrl(string $url, $update = null)
+       public static function isSupportedByContactUrl(string $url, ?bool $update = null): bool
        {
-               return !empty(FContact::getByURL($url, $update));
+               $contact = Contact::getByURL($url, $update);
+
+               return DI::dsprContact()->existsByUriId($contact['uri-id'] ?? 0);
        }
 
        /**
@@ -977,7 +975,7 @@ class Diaspora
                                // 0 => '[url=/people/0123456789abcdef]Foo Bar[/url]'
                                // 1 => '0123456789abcdef'
                                // 2 => 'Foo Bar'
-                               $handle = FContact::getUrlByGuid($match[1]);
+                               $handle = DI::dsprContact()->getUrlByGuid($match[1]);
 
                                if ($handle) {
                                        $return = '@[url=' . $handle . ']' . $match[2] . '[/url]';
@@ -1188,18 +1186,21 @@ class Diaspora
                $item = Post::selectFirst($fields, $condition);
 
                if (!DBA::isResult($item)) {
-                       $person = FContact::getByURL($author);
-                       $result = self::storeByGuid($guid, $person['url'], false);
+                       try {
+                               $result = self::storeByGuid($guid, DI::dsprContact()->getByAddr($author)->url, false);
 
-                       // We don't have an url for items that arrived at the public dispatcher
-                       if (!$result && !empty($contact['url'])) {
-                               $result = self::storeByGuid($guid, $contact['url'], false);
-                       }
+                               // We don't have an url for items that arrived at the public dispatcher
+                               if (!$result && !empty($contact['url'])) {
+                                       $result = self::storeByGuid($guid, $contact['url'], false);
+                               }
 
-                       if ($result) {
-                               Logger::info('Fetched missing item ' . $guid . ' - result: ' . $result);
+                               if ($result) {
+                                       Logger::info('Fetched missing item ' . $guid . ' - result: ' . $result);
 
-                               $item = Post::selectFirst($fields, $condition);
+                                       $item = Post::selectFirst($fields, $condition);
+                               }
+                       } catch (HTTPException\NotFoundException $e) {
+                               Logger::notice('Unable to retrieve author details', ['author' => $author->getAddr()]);
                        }
                }
 
@@ -1422,16 +1423,14 @@ class Diaspora
        private static function getUriFromGuid(string $guid, WebFingerUri $person_uri = null): string
        {
                $item = Post::selectFirst(['uri'], ['guid' => $guid]);
-               if (DBA::isResult($item)) {
+               if ($item) {
                        return $item['uri'];
                } elseif ($person_uri) {
-                       $person = FContact::getByURL($person_uri);
-
-                       $parts = parse_url($person['url']);
-                       unset($parts['path']);
-                       $host_url = (string)Uri::fromParts($parts);
-
-                       return $host_url . '/objects/' . $guid;
+                       try {
+                               return DI::dsprContact()->selectOneByAddr($person_uri)->baseurl . '/objects/' . $guid;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                               return '';
+                       }
                }
 
                return '';
@@ -1462,12 +1461,12 @@ class Diaspora
                                continue;
                        }
 
-                       $person = FContact::getByURL($match[3]);
-                       if (empty($person)) {
-                               continue;
-                       }
+                       try {
+                               $contact = DI::dsprContact()->getByUrl(new Uri($match[3]));
+                               Tag::storeByHash($uriid, $match[1], $contact->name ?: $contact->nick, $contact->url);
+                       } catch (\Throwable $e) {
 
-                       Tag::storeByHash($uriid, $match[1], $person['name'] ?: $person['nick'], $person['url']);
+                       }
                }
        }
 
@@ -1523,14 +1522,15 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
                // Fetch the contact id - if we know this contact
-               $author_contact = self::authorContactByUrl($contact, $person['url'], $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                $datarray = [];
 
@@ -1538,11 +1538,11 @@ class Diaspora
                $datarray['contact-id'] = $author_contact['cid'];
                $datarray['network']  = $author_contact['network'];
 
-               $datarray['author-link'] = $person['url'];
-               $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['author-link'] = $author_url;
+               $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['owner-link'] = $contact['url'];
-               $datarray['owner-id'] = Contact::getIdForURL($contact['url'], 0);
+               $datarray['owner-id'] = Contact::getIdForURL($contact['url']);
 
                // Will be overwritten for sharing accounts in Item::insert
                $datarray = self::setDirection($datarray, $direction);
@@ -1569,7 +1569,7 @@ class Diaspora
                $datarray['plink'] = self::plink($author, $guid, $toplevel_parent_item['guid']);
                $body = Markdown::toBBCode($text);
 
-               $datarray['body'] = self::replacePeopleGuid($body, $person['url']);
+               $datarray['body'] = self::replacePeopleGuid($body, $author_url);
 
                self::storeMentions($datarray['uri-id'], $text);
                Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray['body']);
@@ -1633,6 +1633,12 @@ class Diaspora
                        return false;
                }
 
+               try {
+                       $msg_author_uri = WebFingerUri::fromString($msg_author_handle);
+               } catch (\InvalidArgumentException $e) {
+                       return false;
+               }
+
                $msg_guid = XML::unescape($mesg->guid);
                $msg_conversation_guid = XML::unescape($mesg->conversation_guid);
                $msg_text = XML::unescape($mesg->text);
@@ -1643,20 +1649,18 @@ class Diaspora
                        return false;
                }
 
-               $body = Markdown::toBBCode($msg_text);
-
-               $person = FContact::getByURL($msg_author_handle);
+               $msg_author = DI::dsprContact()->getByAddr($msg_author_uri);
 
                return Mail::insert([
                        'uid'        => $importer['uid'],
                        'guid'       => $msg_guid,
                        'convid'     => $conversation['id'],
-                       'from-name'  => $person['name'],
-                       'from-photo' => $person['photo'],
-                       'from-url'   => $person['url'],
+                       'from-name'  => $msg_author->name,
+                       'from-photo' => (string)$msg_author->photo,
+                       'from-url'   => (string)$msg_author->url,
                        'contact-id' => $contact['id'],
                        'title'      => $subject,
-                       'body'       => $body,
+                       'body'       => Markdown::toBBCode($msg_text),
                        'uri'        => $msg_author_handle . ':' . $msg_guid,
                        'parent-uri' => $author_handle . ':' . $guid,
                        'created'    => $msg_created_at
@@ -1770,14 +1774,15 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
                // Fetch the contact id - if we know this contact
-               $author_contact = self::authorContactByUrl($contact, $person['url'], $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
                // We would accept this anyhow.
@@ -1797,8 +1802,8 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['owner-link'] = $datarray['author-link'] = $person['url'];
-               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['owner-link'] = $datarray['author-link'] = $author_url;
+               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['guid'] = $guid;
                $datarray['uri'] = self::getUriFromGuid($guid, $author);
@@ -1860,13 +1865,13 @@ class Diaspora
         */
        private static function receiveMessage(array $importer, SimpleXMLElement $data): bool
        {
-               $author = WebFingerUri::fromString(XML::unescape($data->author));
+               $author_uri = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $conversation_guid = XML::unescape($data->conversation_guid);
                $text = XML::unescape($data->text);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
 
-               $contact = self::allowedContactByHandle($importer, $author, true);
+               $contact = self::allowedContactByHandle($importer, $author_uri, true);
                if (!$contact) {
                        return false;
                }
@@ -1882,29 +1887,30 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!$person) {
-                       Logger::notice('Unable to find author details');
+               try {
+                       $author = DI::dsprContact()->getByAddr($author_uri);
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find author details', ['author' => $author_uri->getAddr()]);
                        return false;
                }
 
                $body = Markdown::toBBCode($text);
 
-               $body = self::replacePeopleGuid($body, $person['url']);
+               $body = self::replacePeopleGuid($body, $author->url);
 
                return Mail::insert([
                        'uid'        => $importer['uid'],
                        'guid'       => $guid,
                        'convid'     => $conversation['id'],
-                       'from-name'  => $person['name'],
-                       'from-photo' => $person['photo'],
-                       'from-url'   => $person['url'],
+                       'from-name'  => $author->name,
+                       'from-photo' => (string)$author->photo,
+                       'from-url'   => (string)$author->url,
                        'contact-id' => $contact['id'],
                        'title'      => $conversation['subject'],
                        'body'       => $body,
                        'reply'      => 1,
-                       'uri'        => $author . ':' . $guid,
-                       'parent-uri' => $author . ':' . $conversation['guid'],
+                       'uri'        => $author_uri . ':' . $guid,
+                       'parent-uri' => $author_uri . ':' . $conversation['guid'],
                        'created'    => $created_at
                ]);
        }
@@ -1953,13 +1959,14 @@ class Diaspora
                        return false;
                }
 
-               $person = FContact::getByURL($author);
-               if (!is_array($person)) {
-                       Logger::notice('Person not found: ' . $author);
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('unable to find author details', ['author' => $author->getAddr()]);
                        return false;
                }
 
-               $author_contact = self::authorContactByUrl($contact, $person['url'], $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // Store participation
                $datarray = [];
@@ -1972,8 +1979,8 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['owner-link'] = $datarray['author-link'] = $person['url'];
-               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($person['url'], 0);
+               $datarray['owner-link'] = $datarray['author-link'] = $author_url;
+               $datarray['owner-id'] = $datarray['author-id'] = Contact::getIdForURL($author_url);
 
                $datarray['guid'] = $guid;
                $datarray['uri'] = self::getUriFromGuid($guid, $author);
@@ -2233,22 +2240,24 @@ class Diaspora
                        Logger::info("Author " . $author . " wants to listen to us.");
                }
 
-               $ret = FContact::getByURL($author);
-
-               if (!$ret || ($ret['network'] != Protocol::DIASPORA)) {
-                       Logger::notice("Cannot resolve diaspora handle " . $author . " for ".$recipient);
+               try {
+                       $author_url = (string)DI::dsprContact()->getByAddr($author)->url;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Cannot resolve diaspora handle for recipient', ['author' => $author->getAddr(), 'recipient' => $recipient]);
                        return false;
                }
 
-               $cid = Contact::getIdForURL($ret['url'], $importer['uid']);
+               $cid = Contact::getIdForURL($author_url, $importer['uid']);
                if (!empty($cid)) {
                        $contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
                } else {
                        $contact = [];
                }
 
-               $item = ['author-id' => Contact::getIdForURL($ret['url']),
-                       'author-link' => $ret['url']];
+               $item = [
+                       'author-id'   => Contact::getIdForURL($author_url),
+                       'author-link' => $author_url
+               ];
 
                $result = Contact::addRelationship($importer, $contact, $item, false);
                if ($result === true) {
@@ -2346,7 +2355,12 @@ class Diaspora
                $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
-               $root_author = XML::unescape($data->root_author);
+               try {
+                       $root_author = WebFingerUri::fromString(XML::unescape($data->root_author));
+               } catch (\InvalidArgumentException $e) {
+                       return false;
+               }
+
                $root_guid = XML::unescape($data->root_guid);
                /// @todo handle unprocessed property "provider_display_name"
                $public = XML::unescape($data->public);
@@ -2365,8 +2379,9 @@ class Diaspora
                        return true;
                }
 
-               $original_person = FContact::getByURL($root_author);
-               if (!$original_person) {
+               try {
+                       $original_person = DI::dsprContact()->getByAddr($root_author);
+               } catch (HTTPException\NotFoundException $e) {
                        return false;
                }
 
@@ -2394,7 +2409,7 @@ class Diaspora
 
                $datarray = self::setDirection($datarray, $direction);
 
-               $datarray['quote-uri-id'] = self::getQuoteUriId($root_guid, $importer['uid'], $original_person['url']);
+               $datarray['quote-uri-id'] = self::getQuoteUriId($root_guid, $importer['uid'], $original_person->url);
                if (empty($datarray['quote-uri-id'])) {
                        return false;
                }
@@ -2462,19 +2477,18 @@ class Diaspora
         */
        private static function itemRetraction(array $importer, array $contact, SimpleXMLElement $data): bool
        {
-               $author_handle = XML::unescape($data->author);
+               $author_uri  = WebFingerUri::fromString(XML::unescape($data->author));
                $target_guid = XML::unescape($data->target_guid);
                $target_type = XML::unescape($data->target_type);
 
-               $person = FContact::getByURL($author_handle);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author detail for ' . $author_handle);
+               try {
+                       $author = DI::dsprContact()->getByAddr($author_uri);
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       Logger::notice('Unable to find details for author', ['author' => $author_uri->getAddr()]);
                        return false;
                }
 
-               if (empty($contact['url'])) {
-                       $contact['url'] = $person['url'];
-               }
+               $contact_url = $contact['url'] ?? '' ?: (string)$author->url;
 
                // Fetch items that are about to be deleted
                $fields = ['uid', 'id', 'parent', 'author-link', 'uri-id'];
@@ -2502,8 +2516,8 @@ class Diaspora
                        $parent = Post::selectFirst(['author-link'], ['id' => $item['parent']]);
 
                        // Only delete it if the parent author really fits
-                       if (!Strings::compareLink($parent['author-link'], $contact['url']) && !Strings::compareLink($item['author-link'], $contact['url'])) {
-                               Logger::info("Thread author " . $parent['author-link'] . " and item author " . $item['author-link'] . " don't fit to expected contact " . $contact['url']);
+                       if (!Strings::compareLink($parent['author-link'], $contact_url) && !Strings::compareLink($item['author-link'], $contact_url)) {
+                               Logger::info("Thread author " . $parent['author-link'] . " and item author " . $item['author-link'] . " don't fit to expected contact " . $contact_url);
                                continue;
                        }
 
@@ -2968,13 +2982,13 @@ class Diaspora
 
                $logid = Strings::getRandomHex(4);
 
-               // We always try to use the data from the fcontact table.
+               // We always try to use the data from the diaspora-contact table.
                // This is important for transmitting data to Friendica servers.
-               if (!empty($contact['addr'])) {
-                       $fcontact = FContact::getByURL($contact['addr']);
-                       if (!empty($fcontact)) {
-                               $dest_url = ($public_batch ? $fcontact['batch'] : $fcontact['notify']);
-                       }
+               try {
+                       $target = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']));
+                       $dest_url = $public_batch ? $target->batch : $target->notify;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+
                }
 
                if (empty($dest_url)) {
@@ -3043,18 +3057,19 @@ class Diaspora
                }
 
                // When sending content to Friendica contacts using the Diaspora protocol
-               // we have to fetch the public key from the fcontact.
+               // we have to fetch the public key from the diaspora-contact.
                // This is due to the fact that legacy DFRN had unique keys for every contact.
                $pubkey = $contact['pubkey'];
                if (!empty($contact['addr'])) {
-                       $fcontact = FContact::getByURL($contact['addr']);
-                       if (!empty($fcontact)) {
-                               $pubkey = $fcontact['pubkey'];
+                       try {
+                               $pubkey = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']))->pubKey;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+
                        }
                } else {
                        // The "addr" field should always be filled.
                        // If this isn't the case, it will raise a notice some lines later.
-                       // And in the log we will see where it came from and we can handle it there.
+                       // And in the log we will see where it came from, and we can handle it there.
                        Logger::notice('Empty addr', ['contact' => $contact ?? [], 'callstack' => System::callstack(20)]);
                }
 
@@ -4038,6 +4053,8 @@ class Diaspora
         *
         * @param integer $parent_id
         * @return boolean
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
        private static function parentSupportDiaspora(int $parent_id): bool
        {
@@ -4047,7 +4064,7 @@ class Diaspora
                        return false;
                }
 
-               if (empty(FContact::getByURL($parent_post['author-link'], false))) {
+               if (!self::isSupportedByContactUrl($parent_post['author-link'], false)) {
                        Logger::info('Parent author is no Diaspora contact.', ['parent-id' => $parent_id]);
                        return false;
                }
diff --git a/src/Protocol/Diaspora/Entity/DiasporaContact.php b/src/Protocol/Diaspora/Entity/DiasporaContact.php
new file mode 100644 (file)
index 0000000..7fbd283
--- /dev/null
@@ -0,0 +1,140 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Protocol\Diaspora\Entity;
+
+use Psr\Http\Message\UriInterface;
+
+/**
+ * @property-read $uriId
+ * @property-read $url
+ * @property-read $guid
+ * @property-read $addr
+ * @property-read $alias
+ * @property-read $nick
+ * @property-read $name
+ * @property-read $givenName
+ * @property-read $familyName
+ * @property-read $photo
+ * @property-read $photoMedium
+ * @property-read $photoSmall
+ * @property-read $batch
+ * @property-read $notify
+ * @property-read $poll
+ * @property-read $subscribe
+ * @property-read $searchable
+ * @property-read $pubKey
+ * @property-read $baseurl
+ * @property-read $gsid
+ * @property-read $created
+ * @property-read $updated
+ * @property-read $interacting_count
+ * @property-read $interacted_count
+ * @property-read $post_count
+ */
+class DiasporaContact extends \Friendica\BaseEntity
+{
+       /** @var int */
+       protected $uriId;
+       /** @var UriInterface */
+       protected $url;
+       /** @var string */
+       protected $guid;
+       /** @var string */
+       protected $addr;
+       /** @var UriInterface */
+       protected $alias;
+       /** @var string */
+       protected $nick;
+       /** @var string */
+       protected $name;
+       /** @var string */
+       protected $givenName;
+       /** @var string */
+       protected $familyName;
+       /** @var UriInterface */
+       protected $photo;
+       /** @var UriInterface */
+       protected $photoMedium;
+       /** @var UriInterface */
+       protected $photoSmall;
+       /** @var UriInterface */
+       protected $batch;
+       /** @var UriInterface */
+       protected $notify;
+       /** @var UriInterface */
+       protected $poll;
+       /** @var UriInterface */
+       protected $subscribe;
+       /** @var bool */
+       protected $searchable;
+       /** @var string */
+       protected $pubKey;
+       /** @var UriInterface */
+       protected $baseurl;
+       /** @var int */
+       protected $gsid;
+       /** @var \DateTime */
+       protected $created;
+       /** @var \DateTime */
+       protected $updated;
+       /** @var int */
+       protected $interacting_count;
+       /** @var int */
+       protected $interacted_count;
+       /** @var int */
+       protected $post_count;
+
+       public function __construct(
+               UriInterface $url, \DateTime $created, string $guid = null, string $addr = null, UriInterface $alias = null,
+               string $nick = null, string $name = null, string $givenName = null, string $familyName = null,
+               UriInterface $photo = null, UriInterface $photoMedium = null, UriInterface $photoSmall = null,
+               UriInterface $batch = null, UriInterface $notify = null, UriInterface $poll = null, UriInterface $subscribe = null,
+               bool $searchable = null, string $pubKey = null, UriInterface $baseurl = null, int $gsid = null,
+               \DateTime $updated = null, int $interacting_count = 0, int $interacted_count = 0, int $post_count = 0, int $uriId = null
+       ) {
+               $this->uriId             = $uriId;
+               $this->url               = $url;
+               $this->guid              = $guid;
+               $this->addr              = $addr;
+               $this->alias             = $alias;
+               $this->nick              = $nick;
+               $this->name              = $name;
+               $this->givenName         = $givenName;
+               $this->familyName        = $familyName;
+               $this->photo             = $photo;
+               $this->photoMedium       = $photoMedium;
+               $this->photoSmall        = $photoSmall;
+               $this->batch             = $batch;
+               $this->notify            = $notify;
+               $this->poll              = $poll;
+               $this->subscribe         = $subscribe;
+               $this->searchable        = $searchable;
+               $this->pubKey            = $pubKey;
+               $this->baseurl           = $baseurl;
+               $this->gsid              = $gsid;
+               $this->created           = $created;
+               $this->updated           = $updated;
+               $this->interacting_count = $interacting_count;
+               $this->interacted_count  = $interacted_count;
+               $this->post_count        = $post_count;
+       }
+}
diff --git a/src/Protocol/Diaspora/Factory/DiasporaContact.php b/src/Protocol/Diaspora/Factory/DiasporaContact.php
new file mode 100644 (file)
index 0000000..d5c91d2
--- /dev/null
@@ -0,0 +1,102 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Protocol\Diaspora\Factory;
+
+use Friendica\Capabilities\ICanCreateFromTableRow;
+use Friendica\Database\DBA;
+use GuzzleHttp\Psr7\Uri;
+
+class DiasporaContact extends \Friendica\BaseFactory implements ICanCreateFromTableRow
+{
+       public function createFromTableRow(array $row): \Friendica\Protocol\Diaspora\Entity\DiasporaContact
+       {
+               return new \Friendica\Protocol\Diaspora\Entity\DiasporaContact(
+                       new Uri($row['url']),
+                       new \DateTime($row['created'], new \DateTimeZone('UTC')),
+                       $row['guid'],
+                       $row['addr'],
+                       $row['alias'] ? new Uri($row['alias']) : null,
+                       $row['nick'],
+                       $row['name'],
+                       $row['given-name'],
+                       $row['family-name'],
+                       $row['photo'] ? new Uri($row['photo']) : null,
+                       $row['photo-medium'] ? new Uri($row['photo-medium']) : null,
+                       $row['photo-small'] ? new Uri($row['photo-small']) : null,
+                       $row['batch'] ? new Uri($row['batch']) : null,
+                       $row['notify'] ? new Uri($row['notify']) : null,
+                       $row['poll'] ? new Uri($row['poll']) : null,
+                       $row['subscribe'] ? new Uri($row['subscribe']) : null,
+                       $row['searchable'],
+                       $row['pubkey'],
+                       $row['baseurl'] ? new Uri($row['baseurl']) : null,
+                       $row['gsid'],
+                       $row['updated'] !== DBA::NULL_DATETIME ? new \DateTime($row['updated'], new \DateTimeZone('UTC')) : null,
+                       $row['interacting_count'],
+                       $row['interacted_count'],
+                       $row['post_count'],
+                       $row['uri-id'],
+               );
+       }
+
+       /**
+        * @param array     $data              Data returned by \Friendica\Network\Probe::uri()
+        * @param int       $uriId             The URI ID of the Diaspora contact URL + GUID
+        * @param \DateTime $created
+        * @param int       $interacting_count
+        * @param int       $interacted_count
+        * @param int       $post_count
+        * @return \Friendica\Protocol\Diaspora\Entity\DiasporaContact
+        */
+       public function createfromProbeData(array $data, int $uriId, \DateTime $created, int $interacting_count = 0, int $interacted_count = 0, int $post_count = 0): \Friendica\Protocol\Diaspora\Entity\DiasporaContact
+       {
+               $alias = $data['alias'] != $data['url'] ? $data['alias'] : null;
+
+               return new \Friendica\Protocol\Diaspora\Entity\DiasporaContact(
+                       new Uri($data['url']),
+                       $created,
+                       $data['guid'],
+                       $data['addr'],
+                       $alias ? new Uri($alias) : null,
+                       $data['nick'],
+                       $data['name'],
+                       $data['given-name'] ?? '',
+                       $data['family-name'] ?? '',
+                       $data['photo'] ? new Uri($data['photo']) : null,
+                       !empty($data['photo_medium']) ? new Uri($data['photo_medium']) : null,
+                       !empty($data['photo_small']) ? new Uri($data['photo_small']) : null,
+                       $data['batch'] ? new Uri($data['batch']) : null,
+                       $data['notify'] ? new Uri($data['notify']) : null,
+                       $data['poll'] ? new Uri($data['poll']) : null,
+                       $data['subscribe'] ? new Uri($data['subscribe']) : null,
+                       !$data['hide'],
+                       $data['pubkey'],
+                       $data['baseurl'] ? new Uri($data['baseurl']) : null,
+                       $data['gsid'],
+                       null,
+                       $interacting_count,
+                       $interacted_count,
+                       $post_count,
+                       $uriId,
+               );
+       }
+}
diff --git a/src/Protocol/Diaspora/Repository/DiasporaContact.php b/src/Protocol/Diaspora/Repository/DiasporaContact.php
new file mode 100644 (file)
index 0000000..ac8d200
--- /dev/null
@@ -0,0 +1,283 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Protocol\Diaspora\Repository;
+
+use Friendica\BaseRepository;
+use Friendica\Core\System;
+use Friendica\Database\Database;
+use Friendica\Database\Definition\DbaDefinition;
+use Friendica\Model\APContact;
+use Friendica\Model\Contact;
+use Friendica\Model\Item;
+use Friendica\Model\ItemURI;
+use Friendica\Network\HTTPException;
+use Friendica\Protocol\Diaspora\Entity;
+use Friendica\Protocol\Diaspora\Factory;
+use Friendica\Protocol\WebFingerUri;
+use Friendica\Util\DateTimeFormat;
+use Psr\Http\Message\UriInterface;
+use Psr\Log\LoggerInterface;
+
+class DiasporaContact extends BaseRepository
+{
+       const ALWAYS_UPDATE                 = true;
+       const NEVER_UPDATE                  = false;
+       const UPDATE_IF_MISSING_OR_OUTDATED = null;
+
+       protected static $table_name = 'diaspora-contact-view';
+
+       /** @var Factory\DiasporaContact */
+       protected $factory;
+       /** @var DbaDefinition */
+       private $definition;
+
+       public function __construct(DbaDefinition $definition, Database $database, LoggerInterface $logger, Factory\DiasporaContact $factory)
+       {
+               parent::__construct($database, $logger, $factory);
+
+               $this->definition = $definition;
+       }
+
+       /**
+        * @param array $condition
+        * @param array $params
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function selectOne(array $condition, array $params = []): Entity\DiasporaContact
+       {
+               return parent::_selectOne($condition, $params);
+       }
+
+       /**
+        * @param int $uriId
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function selectOneByUriId(int $uriId): Entity\DiasporaContact
+       {
+               return $this->selectOne(['uri-id' => $uriId]);
+       }
+
+       /**
+        * @param UriInterface $uri
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function selectOneByUri(UriInterface $uri): Entity\DiasporaContact
+       {
+               try {
+                       return $this->selectOne(['url' => (string) $uri]);
+               } catch (HTTPException\NotFoundException $e) {
+               }
+
+               try {
+                       return $this->selectOne(['addr' => (string) $uri]);
+               } catch (HTTPException\NotFoundException $e) {
+               }
+
+               return $this->selectOne(['alias' => (string) $uri]);
+       }
+
+       /**
+        * @param WebFingerUri $uri
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function selectOneByAddr(WebFingerUri $uri): Entity\DiasporaContact
+       {
+               return $this->selectOne(['addr' => $uri->getAddr()]);
+       }
+
+       /**
+        * @param int $uriId
+        * @return bool
+        * @throws \Exception
+        */
+       public function existsByUriId(int $uriId): bool
+       {
+               return $this->db->exists(self::$table_name, ['uri-id' => $uriId]);
+       }
+
+       public function save(Entity\DiasporaContact $DiasporaContact): Entity\DiasporaContact
+       {
+               $uriId = $DiasporaContact->uriId ?? ItemURI::insert(['uri' => $DiasporaContact->url, 'guid' => $DiasporaContact->guid]);
+
+               $fields = [
+                       'uri-id'            => $uriId,
+                       'addr'              => $DiasporaContact->addr,
+                       'alias'             => (string)$DiasporaContact->alias,
+                       'nick'              => $DiasporaContact->nick,
+                       'name'              => $DiasporaContact->name,
+                       'given-name'        => $DiasporaContact->givenName,
+                       'family-name'       => $DiasporaContact->familyName,
+                       'photo'             => (string)$DiasporaContact->photo,
+                       'photo-medium'      => (string)$DiasporaContact->photoMedium,
+                       'photo-small'       => (string)$DiasporaContact->photoSmall,
+                       'batch'             => (string)$DiasporaContact->batch,
+                       'notify'            => (string)$DiasporaContact->notify,
+                       'poll'              => (string)$DiasporaContact->poll,
+                       'subscribe'         => (string)$DiasporaContact->subscribe,
+                       'searchable'        => $DiasporaContact->searchable,
+                       'pubkey'            => $DiasporaContact->pubKey,
+                       'gsid'              => $DiasporaContact->gsid,
+                       'created'           => $DiasporaContact->created->format(DateTimeFormat::MYSQL),
+                       'updated'           => DateTimeFormat::utcNow(),
+                       'interacting_count' => $DiasporaContact->interacting_count,
+                       'interacted_count'  => $DiasporaContact->interacted_count,
+                       'post_count'        => $DiasporaContact->post_count,
+               ];
+
+               // Limit the length on incoming fields
+               $fields = $this->definition->truncateFieldsForTable('diaspora-contact', $fields);
+
+               $this->db->insert('diaspora-contact', $fields, Database::INSERT_UPDATE);
+
+               return $this->selectOneByUriId($uriId);
+       }
+
+       /**
+        * Fetch a Diaspora profile from a given WebFinger address and updates it depending on the mode
+        *
+        * @param WebFingerUri $uri    Profile address
+        * @param boolean      $update true = always update, false = never update, null = update when not found or outdated
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function getByAddr(WebFingerUri $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact
+       {
+               if ($update !== self::ALWAYS_UPDATE) {
+                       try {
+                               $dcontact = $this->selectOneByAddr($uri);
+                               if ($update === self::NEVER_UPDATE) {
+                                       return $dcontact;
+                               }
+                       } catch (HTTPException\NotFoundException $e) {
+                               if ($update === self::NEVER_UPDATE) {
+                                       throw $e;
+                               }
+
+                               // This is necessary for Contact::getByURL in case the base contact record doesn't need probing,
+                               // but we still need the result of a probe to create the missing diaspora-contact record.
+                               $update = self::ALWAYS_UPDATE;
+                       }
+               }
+
+               $contact = Contact::getByURL($uri, $update, ['uri-id']);
+               if (empty($contact['uri-id'])) {
+                       throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found');
+               }
+
+               return self::selectOneByUriId($contact['uri-id']);
+       }
+
+       /**
+        * Fetch a Diaspora profile from a given profile URL and updates it depending on the mode
+        *
+        * @param UriInterface $uri    Profile URL
+        * @param boolean      $update true = always update, false = never update, null = update when not found or outdated
+        * @return Entity\DiasporaContact
+        * @throws HTTPException\NotFoundException
+        */
+       public function getByUrl(UriInterface $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact
+       {
+               if ($update !== self::ALWAYS_UPDATE) {
+                       try {
+                               $dcontact = $this->selectOneByUriId(ItemURI::getIdByURI($uri));
+                               if ($update === self::NEVER_UPDATE) {
+                                       return $dcontact;
+                               }
+                       } catch (HTTPException\NotFoundException $e) {
+                               if ($update === self::NEVER_UPDATE) {
+                                       throw $e;
+                               }
+
+                               // This is necessary for Contact::getByURL in case the base contact record doesn't need probing,
+                               // but we still need the result of a probe to create the missing diaspora-contact record.
+                               $update = self::ALWAYS_UPDATE;
+                       }
+               }
+
+               $contact = Contact::getByURL($uri, $update, ['uri-id']);
+               if (empty($contact['uri-id'])) {
+                       throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found');
+               }
+
+               return self::selectOneByUriId($contact['uri-id']);
+       }
+
+       /**
+        * Update or create a diaspora-contact entry via a probe array
+        *
+        * @param array $data Probe array
+        * @return Entity\DiasporaContact
+        * @throws \Exception
+        */
+       public function updateFromProbeArray(array $data): Entity\DiasporaContact
+       {
+               $uriId = ItemURI::insert(['uri' => $data['url'], 'guid' => $data['guid']]);
+
+               $contact   = Contact::getByUriId($uriId, ['id', 'created']);
+               $apcontact = APContact::getByURL($data['url'], false);
+               if (!empty($apcontact)) {
+                       $interacting_count = $apcontact['followers_count'];
+                       $interacted_count  = $apcontact['following_count'];
+                       $post_count        = $apcontact['statuses_count'];
+               } elseif (!empty($contact['id'])) {
+                       $last_interaction = DateTimeFormat::utc('now - 180 days');
+
+                       $interacting_count = $this->db->count('contact-relation', ["`relation-cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
+                       $interacted_count  = $this->db->count('contact-relation', ["`cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
+                       $post_count        = $this->db->count('post', ['author-id' => $contact['id'], 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]]);
+               }
+
+               $DiasporaContact = $this->factory->createfromProbeData(
+                       $data,
+                       $uriId,
+                       new \DateTime($contact['created'] ?? 'now', new \DateTimeZone('UTC')),
+                       $interacting_count ?? 0,
+                       $interacted_count ?? 0,
+                       $post_count ?? 0
+               );
+
+               $DiasporaContact = $this->save($DiasporaContact);
+
+               $this->logger->info('Updated diaspora-contact', ['url' => (string) $DiasporaContact->url, 'callstack' => System::callstack(20)]);
+
+               return $DiasporaContact;
+       }
+
+       /**
+        * get a url (scheme://domain.tld/u/user) from a given contact guid
+        *
+        * @param mixed $guid Hexadecimal string guid
+        *
+        * @return string the contact url or null
+        * @throws \Exception
+        */
+       public function getUrlByGuid(string $guid): ?string
+       {
+               $diasporaContact = $this->db->selectFirst(self::$table_name, ['url'], ['guid' => $guid]);
+
+               return $diasporaContact['url'] ?? null;
+       }
+}
index 706adb401e935124d048abca310fe9d7d2605899..cec894480ca90cee1f4c8b3f08bd5eb2a2c37db6 100644 (file)
@@ -29,7 +29,6 @@ use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Contact;
-use Friendica\Model\FContact;
 use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\Post;
@@ -94,7 +93,7 @@ class Delivery
                                if ($item['verb'] == Activity::ANNOUNCE) {
                                        continue;
                                }
-       
+
                                if ($item['id'] == $parent_id) {
                                        $parent = $item;
                                }
@@ -278,7 +277,7 @@ class Delivery
        private static function deliverDFRN(string $cmd, array $contact, array $owner, array $items, array $target_item, bool $public_message, bool $top_level, bool $followup, int $server_protocol = null)
        {
                // Transmit Diaspora reshares via Diaspora if the Friendica contact support Diaspora
-               if (Diaspora::getReshareDetails($target_item ?? []) && !empty(FContact::getByURL($contact['addr'], false))) {
+               if (Diaspora::getReshareDetails($target_item ?? []) && Diaspora::isSupportedByContactUrl($contact['addr'], false)) {
                        Logger::info('Reshare will be transmitted via Diaspora', ['url' => $contact['url'], 'guid' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
                        self::deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
                        return;
index 6f75d1855910fba0e7f3d68e6bc600310d5506a4..a2ef33f330e74a4397e1e16f027878074545a233 100644 (file)
@@ -189,7 +189,7 @@ class ExpirePosts
                        AND NOT EXISTS(SELECT `uri-id` FROM `user-contact` WHERE `uri-id` = `item-uri`.`id`)
                        AND NOT EXISTS(SELECT `uri-id` FROM `contact` WHERE `uri-id` = `item-uri`.`id`)
                        AND NOT EXISTS(SELECT `uri-id` FROM `apcontact` WHERE `uri-id` = `item-uri`.`id`)
-                       AND NOT EXISTS(SELECT `uri-id` FROM `fcontact` WHERE `uri-id` = `item-uri`.`id`)
+                       AND NOT EXISTS(SELECT `uri-id` FROM `diaspora-contact` WHERE `uri-id` = `item-uri`.`id`)
                        AND NOT EXISTS(SELECT `uri-id` FROM `inbox-status` WHERE `uri-id` = `item-uri`.`id`)
                        AND NOT EXISTS(SELECT `uri-id` FROM `post-delivery` WHERE `uri-id` = `item-uri`.`id`)
                        AND NOT EXISTS(SELECT `uri-id` FROM `post-delivery` WHERE `inbox-id` = `item-uri`.`id`)
index ab18e80e592be43d5b4bb187fcf6507d5f3e2d54..c82e7dc77f68f5a85f3f4a491c438e01c3455e4f 100644 (file)
                        "blocked" => ["contact", "blocked"],
                        "dfrn-notify" => ["contact", "notify"],
                        "dfrn-poll" => ["contact", "poll"],
-                       "diaspora-guid" => ["fcontact", "guid"],
-                       "diaspora-batch" => ["fcontact", "batch"],
-                       "diaspora-notify" => ["fcontact", "notify"],
-                       "diaspora-poll" => ["fcontact", "poll"],
-                       "diaspora-alias" => ["fcontact", "alias"],
+                       "diaspora-guid" => ["item-uri", "guid"],
+                       "diaspora-batch" => ["diaspora-contact", "batch"],
+                       "diaspora-notify" => ["diaspora-contact", "notify"],
+                       "diaspora-poll" => ["diaspora-contact", "poll"],
+                       "diaspora-alias" => ["diaspora-contact", "alias"],
                        "ap-uuid" => ["apcontact", "uuid"],
                        "ap-type" => ["apcontact", "type"],
                        "ap-following" => ["apcontact", "following"],
                "query" => "FROM `contact`
                        LEFT JOIN `item-uri` ON `item-uri`.`id` = `contact`.`uri-id`
                        LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `contact`.`uri-id`
-                       LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = contact.`uri-id`
+                       LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = contact.`uri-id`
                        LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`
                        WHERE `contact`.`uid` = 0"
        ],
                        "reason" => ["ucontact", "reason"],
                        "dfrn-notify" => ["contact", "notify"],
                        "dfrn-poll" => ["contact", "poll"],
-                       "diaspora-guid" => ["fcontact", "guid"],
-                       "diaspora-batch" => ["fcontact", "batch"],
-                       "diaspora-notify" => ["fcontact", "notify"],
-                       "diaspora-poll" => ["fcontact", "poll"],
-                       "diaspora-alias" => ["fcontact", "alias"],
-                       "diaspora-interacting_count" => ["fcontact", "interacting_count"],
-                       "diaspora-interacted_count" => ["fcontact", "interacted_count"],
-                       "diaspora-post_count" => ["fcontact", "post_count"],
+                       "diaspora-guid" => ["item-uri", "guid"],
+                       "diaspora-batch" => ["diaspora-contact", "batch"],
+                       "diaspora-notify" => ["diaspora-contact", "notify"],
+                       "diaspora-poll" => ["diaspora-contact", "poll"],
+                       "diaspora-alias" => ["diaspora-contact", "alias"],
+                       "diaspora-interacting_count" => ["diaspora-contact", "interacting_count"],
+                       "diaspora-interacted_count" => ["diaspora-contact", "interacted_count"],
+                       "diaspora-post_count" => ["diaspora-contact", "post_count"],
                        "ap-uuid" => ["apcontact", "uuid"],
                        "ap-type" => ["apcontact", "type"],
                        "ap-following" => ["apcontact", "following"],
                        INNER JOIN `contact` ON `contact`.`uri-id` = `ucontact`.`uri-id` AND `contact`.`uid` = 0
                        LEFT JOIN `item-uri` ON `item-uri`.`id` = `ucontact`.`uri-id`
                        LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `ucontact`.`uri-id`
-                       LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = `ucontact`.`uri-id` AND `fcontact`.`network` = 'dspr'
+                       LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = `ucontact`.`uri-id`
                        LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`"
        ],
        "pending-view" => [