]> git.mxchange.org Git - friendica.git/commitdiff
Merge branch 'develop' into new_image_presentation
authorMarek Bachmann <marek.bachmann@comtec.eecs.uni-kassel.de>
Mon, 5 Dec 2022 15:54:12 +0000 (16:54 +0100)
committerMarek Bachmann <marek.bachmann@comtec.eecs.uni-kassel.de>
Mon, 5 Dec 2022 15:54:12 +0000 (16:54 +0100)
33 files changed:
database.sql
doc/FAQ.md
doc/database.md
doc/database/db_diaspora-contact.md [new file with mode: 0644]
doc/database/db_fcontact.md [deleted file]
src/Console/Relocate.php
src/DI.php
src/Database/DBStructure.php
src/Database/Database.php
src/Database/PostUpdate.php
src/Factory/Api/Mastodon/Status.php
src/Model/Contact.php
src/Model/FContact.php [deleted file]
src/Module/Api/Mastodon/Accounts/Statuses.php
src/Module/NoScrape.php
src/Module/Photo.php
src/Module/Profile/Status.php
src/Network/Probe.php
src/Object/Api/Mastodon/Account.php
src/Object/Image.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/Protocol/WebFingerUri.php [new file with mode: 0644]
src/Worker/Delivery.php
src/Worker/ExpirePosts.php
src/Worker/UpdateFContact.php [deleted file]
static/dbstructure.config.php
static/dbview.config.php
tests/src/Protocol/WebFingerUriTest.php [new file with mode: 0644]
update.php

index 7b3156f6f7c8d4cab53fa3898ca4759836b7eb5b..3169bac03ac75bce1cbcb94878d4eea1e487eff0 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
 -- Friendica 2022.12-dev (Giant Rhubarb)
--- DB_UPDATE_VERSION 1497
+-- DB_UPDATE_VERSION 1500
 -- ------------------------------------------
 
 
@@ -578,6 +578,40 @@ CREATE TABLE IF NOT EXISTS `delayed-post` (
        FOREIGN KEY (`wid`) REFERENCES `workerqueue` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts that are about to be distributed at a later time';
 
+--
+-- TABLE diaspora-contact
+--
+CREATE TABLE IF NOT EXISTS `diaspora-contact` (
+       `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the contact URL',
+       `addr` varchar(255) COMMENT '',
+       `alias` varchar(255) COMMENT '',
+       `nick` varchar(255) COMMENT '',
+       `name` varchar(255) COMMENT '',
+       `given-name` varchar(255) COMMENT '',
+       `family-name` varchar(255) COMMENT '',
+       `photo` varchar(255) COMMENT '',
+       `photo-medium` varchar(255) COMMENT '',
+       `photo-small` varchar(255) COMMENT '',
+       `batch` varchar(255) COMMENT '',
+       `notify` varchar(255) COMMENT '',
+       `poll` varchar(255) COMMENT '',
+       `subscribe` varchar(255) COMMENT '',
+       `searchable` boolean COMMENT '',
+       `pubkey` text COMMENT '',
+       `gsid` int unsigned COMMENT 'Global Server ID',
+       `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
+       `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
+       `interacting_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts this contact interactes with',
+       `interacted_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts that interacted with this contact',
+       `post_count` int unsigned DEFAULT 0 COMMENT 'Number of posts and comments',
+        PRIMARY KEY(`uri-id`),
+        UNIQUE INDEX `addr` (`addr`),
+        INDEX `alias` (`alias`),
+        INDEX `gsid` (`gsid`),
+       FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
+       FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
+) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Diaspora compatible contacts - used in the Diaspora implementation';
+
 --
 -- TABLE diaspora-interaction
 --
@@ -633,39 +667,6 @@ CREATE TABLE IF NOT EXISTS `event` (
        FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Events';
 
---
--- TABLE fcontact
---
-CREATE TABLE IF NOT EXISTS `fcontact` (
-       `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
-       `guid` varbinary(255) NOT NULL DEFAULT '' COMMENT 'unique id',
-       `url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the fcontact url',
-       `name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
-       `photo` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `request` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `nick` varchar(255) NOT NULL DEFAULT '' COMMENT '',
-       `addr` varchar(255) NOT NULL DEFAULT '' COMMENT '',
-       `batch` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `notify` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `poll` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `confirm` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
-       `network` char(4) NOT NULL DEFAULT '' COMMENT '',
-       `alias` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
-       `pubkey` text COMMENT '',
-       `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
-       `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
-       `interacting_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts this contact interactes with',
-       `interacted_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts that interacted with this contact',
-       `post_count` int unsigned DEFAULT 0 COMMENT 'Number of posts and comments',
-        PRIMARY KEY(`id`),
-        INDEX `addr` (`addr`(32)),
-        UNIQUE INDEX `url` (`url`(190)),
-        UNIQUE INDEX `uri-id` (`uri-id`),
-       FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
-) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Diaspora compatible contacts - used in the Diaspora implementation';
-
 --
 -- TABLE fetch-entry
 --
@@ -2802,11 +2803,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`,
@@ -2824,7 +2825,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;
 
@@ -2903,14 +2904,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`,
@@ -2929,7 +2930,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`;
 
 --
@@ -3006,3 +3007,37 @@ CREATE VIEW `profile_field-view` AS SELECT
        `profile_field`.`edited` AS `edited`
        FROM `profile_field`
                        INNER JOIN `permissionset` ON `permissionset`.`id` = `profile_field`.`psid`;
+
+--
+-- VIEW diaspora-contact-view
+--
+DROP VIEW IF EXISTS `diaspora-contact-view`;
+CREATE VIEW `diaspora-contact-view` AS SELECT 
+       `diaspora-contact`.`uri-id` AS `uri-id`,
+       `item-uri`.`uri` AS `url`,
+       `item-uri`.`guid` AS `guid`,
+       `diaspora-contact`.`addr` AS `addr`,
+       `diaspora-contact`.`alias` AS `alias`,
+       `diaspora-contact`.`nick` AS `nick`,
+       `diaspora-contact`.`name` AS `name`,
+       `diaspora-contact`.`given-name` AS `given-name`,
+       `diaspora-contact`.`family-name` AS `family-name`,
+       `diaspora-contact`.`photo` AS `photo`,
+       `diaspora-contact`.`photo-medium` AS `photo-medium`,
+       `diaspora-contact`.`photo-small` AS `photo-small`,
+       `diaspora-contact`.`batch` AS `batch`,
+       `diaspora-contact`.`notify` AS `notify`,
+       `diaspora-contact`.`poll` AS `poll`,
+       `diaspora-contact`.`subscribe` AS `subscribe`,
+       `diaspora-contact`.`searchable` AS `searchable`,
+       `diaspora-contact`.`pubkey` AS `pubkey`,
+       `gserver`.`url` AS `baseurl`,
+       `diaspora-contact`.`gsid` AS `gsid`,
+       `diaspora-contact`.`created` AS `created`,
+       `diaspora-contact`.`updated` AS `updated`,
+       `diaspora-contact`.`interacting_count` AS `interacting_count`,
+       `diaspora-contact`.`interacted_count` AS `interacted_count`,
+       `diaspora-contact`.`post_count` AS `post_count`
+       FROM `diaspora-contact`
+                       INNER JOIN `item-uri` ON `item-uri`.`id` = `diaspora-contact`.`uri-id`
+                       LEFT JOIN `gserver` ON `gserver`.`id` = `diaspora-contact`.`gsid`;
index 0f9e1150221d43fd9cf85b854e94a6f69ee8fb3e..63e04a7c3fbc5ca85468a0b43f689c3a8ac0568e 100644 (file)
@@ -179,26 +179,22 @@ The available features are client specific and may differ.
 
 * [AndStatus](http://andstatus.org) ([F-Droid](https://f-droid.org/repository/browse/?fdid=org.andstatus.app), [Google Play](https://play.google.com/store/apps/details?id=org.andstatus.app))
 * [B4X for Pleroma & Mastodon](https://github.com/AnywhereSoftware/B4X-Pleroma)
-* [Fedi](https://play.google.com/store/apps/details?id=com.fediverse.app)
+* [Fedi](https://github.com/Big-Fig/Fediverse.app) ([Google Play](https://play.google.com/store/apps/details?id=com.fediverse.app)
 * [Fedilab](https://fedilab.app) ([F-Droid](https://f-droid.org/app/fr.gouv.etalab.mastodon), [Google Play](https://play.google.com/store/apps/details?id=app.fedilab.android))
 * [Friendiqa](https://git.friendi.ca/lubuwest/Friendiqa) ([F-Droid](https://git.friendi.ca/lubuwest/Friendiqa#install), [Google Play](https://play.google.com/store/apps/details?id=org.qtproject.friendiqa))
 * [Husky](https://git.sr.ht/~captainepoch/husky) ([F-Droid](https://f-droid.org/repository/browse/?fdid=su.xash.husky), [Google Play](https://play.google.com/store/apps/details?id=su.xash.husky))
-* [Mastodon for Android](https://github.com/mastodon/mastodon-android) (F-Droid: Pending, [Google-Play](https://play.google.com/store/apps/details?id=org.joinmastodon.android))
-* [Subway Tooter](https://github.com/tateisu/SubwayTooter)
-* [Tooot](https://tooot.app/)
+* [Mastodon](https://github.com/mastodon/mastodon-android) ([F-Droid](https://f-droid.org/en/packages/org.joinmastodon.android/), [Google Play](https://play.google.com/store/apps/details?id=org.joinmastodon.android))
+* [Subway Tooter](https://github.com/tateisu/SubwayTooter) ([F-Droid](https://android.izzysoft.de/repo/apk/jp.juggler.subwaytooter))
+* [Tooot](https://tooot.app/) ([Google Play](https://play.google.com/store/apps/details?id=com.xmflsct.app.tooot))
 * [Tusky](https://tusky.app) ([F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky), [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky))
 * [Twidere](https://github.com/TwidereProject/Twidere-Android) ([F-Droid](https://f-droid.org/repository/browse/?fdid=org.mariotaku.twidere), [Google Play](https://play.google.com/store/apps/details?id=com.twidere.twiderex))
 * [TwidereX](https://github.com/TwidereProject/TwidereX-Android) ([F-Droid](https://f-droid.org/en/packages/com.twidere.twiderex/), [Google Play](https://play.google.com/store/apps/details?id=com.twidere.twiderex))
 * [Yuito](https://github.com/accelforce/Yuito) ([Google Play](https://play.google.com/store/apps/details?id=net.accelf.yuito))
 
-#### SailfishOS
-
-* [Friendly](https://openrepos.net/content/fabrixxm/friendly), last update: 2018
-
 #### iOS
 
 * [B4X for Pleroma & Mastodon](https://github.com/AnywhereSoftware/B4X-Pleroma) ([AppStore](https://apps.apple.com/app/b4x-pleroma/id1538396871))
-* [Fedi](https://fediapp.com) ([AppStore](https://apps.apple.com/de/app/fedi-for-pleroma-and-mastodon/id1478806281))
+* [Fedi](https://github.com/Big-Fig/Fediverse.app) ([AppStore](https://apps.apple.com/de/app/fedi-for-pleroma-and-mastodon/id1478806281))
 * [Mastodon for iPhone and iPad](https://joinmastodon.org/apps) ([AppStore](https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974))
 * [Stella*](https://www.stella-app.net/) ([AppStore](https://apps.apple.com/us/app/stella-for-mastodon-twitter/id921372048))
 * [Tooot](https://github.com/tooot-app) ([AppStore](https://apps.apple.com/app/id1549772269), Data collection (not linked to identity)
@@ -207,18 +203,19 @@ The available features are client specific and may differ.
 #### Linux
 
 * [Choqok](https://choqok.kde.org)
-* [Whalebird](https://whalebird.social)
-* [TheDesk](https://ja.mstdn.wiki/TheDesk)
+* [Whalebird](https://whalebird.social/en/desktop/contents) ([GitHub](https://github.com/h3poteto/whalebird-desktop))
+* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
 * [Toot](https://toot.readthedocs.io/en/latest/)
 * [Tootle](https://github.com/bleakgrey/tootle)
 
 #### macOS
 
-* [Mastonaut](https://mastonaut.app/) ([AppStore](https://apps.apple.com/us/app/mastonaut/id1450757574)), closed source
+* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
 * [Whalebird](https://whalebird.social/en/desktop/contents) ([AppStore](https://apps.apple.com/de/app/whalebird/id1378283354), [GitHub](https://github.com/h3poteto/whalebird-desktop))
 
 #### Windows
 
+* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
 * [Whalebird](https://whalebird.social/en/desktop/contents) ([Website Download](https://whalebird.social/en/desktop/contents/downloads#windows), [GitHub](https://github.com/h3poteto/whalebird-desktop))
 
 #### Web Frontend
index ec8d16b2cdb4d1ce057cf91cddea4e4812ced1dd..9fa438c8d2a6daa14a19b121d95dbe27b79e5055 100644 (file)
@@ -23,10 +23,10 @@ Database Tables
 | [contact-relation](help/database/db_contact-relation) | Contact relations |
 | [conv](help/database/db_conv) | private messages |
 | [delayed-post](help/database/db_delayed-post) | Posts that are about to be distributed at a later time |
+| [diaspora-contact](help/database/db_diaspora-contact) | Diaspora compatible contacts - used in the Diaspora implementation |
 | [diaspora-interaction](help/database/db_diaspora-interaction) | Signed Diaspora Interaction |
 | [endpoint](help/database/db_endpoint) | ActivityPub endpoints - used in the ActivityPub implementation |
 | [event](help/database/db_event) | Events |
-| [fcontact](help/database/db_fcontact) | Diaspora compatible contacts - used in the Diaspora implementation |
 | [fetch-entry](help/database/db_fetch-entry) |  |
 | [fetched-activity](help/database/db_fetched-activity) | Id of fetched activities |
 | [fsuggest](help/database/db_fsuggest) | friend suggestion stuff |
diff --git a/doc/database/db_diaspora-contact.md b/doc/database/db_diaspora-contact.md
new file mode 100644 (file)
index 0000000..4beaeb0
--- /dev/null
@@ -0,0 +1,52 @@
+Table diaspora-contact
+===========
+
+Diaspora compatible contacts - used in the Diaspora implementation
+
+Fields
+------
+
+| Field             | Description                                                  | Type         | Null | Key | Default             | Extra |
+| ----------------- | ------------------------------------------------------------ | ------------ | ---- | --- | ------------------- | ----- |
+| uri-id            | Id of the item-uri table entry that contains the contact URL | int unsigned | NO   | PRI | NULL                |       |
+| addr              |                                                              | varchar(255) | YES  |     | NULL                |       |
+| alias             |                                                              | varchar(255) | YES  |     | NULL                |       |
+| nick              |                                                              | varchar(255) | YES  |     | NULL                |       |
+| name              |                                                              | varchar(255) | YES  |     | NULL                |       |
+| given-name        |                                                              | varchar(255) | YES  |     | NULL                |       |
+| family-name       |                                                              | varchar(255) | YES  |     | NULL                |       |
+| photo             |                                                              | varchar(255) | YES  |     | NULL                |       |
+| photo-medium      |                                                              | varchar(255) | YES  |     | NULL                |       |
+| photo-small       |                                                              | varchar(255) | YES  |     | NULL                |       |
+| batch             |                                                              | varchar(255) | YES  |     | NULL                |       |
+| notify            |                                                              | varchar(255) | YES  |     | NULL                |       |
+| poll              |                                                              | varchar(255) | YES  |     | NULL                |       |
+| subscribe         |                                                              | varchar(255) | YES  |     | NULL                |       |
+| searchable        |                                                              | boolean      | YES  |     | NULL                |       |
+| pubkey            |                                                              | text         | YES  |     | NULL                |       |
+| gsid              | Global Server ID                                             | int unsigned | YES  |     | NULL                |       |
+| created           |                                                              | datetime     | NO   |     | 0001-01-01 00:00:00 |       |
+| updated           |                                                              | datetime     | NO   |     | 0001-01-01 00:00:00 |       |
+| interacting_count | Number of contacts this contact interactes with              | int unsigned | YES  |     | 0                   |       |
+| interacted_count  | Number of contacts that interacted with this contact         | int unsigned | YES  |     | 0                   |       |
+| post_count        | Number of posts and comments                                 | int unsigned | YES  |     | 0                   |       |
+
+Indexes
+------------
+
+| Name    | Fields       |
+| ------- | ------------ |
+| PRIMARY | uri-id       |
+| addr    | UNIQUE, addr |
+| alias   | alias        |
+| gsid    | gsid         |
+
+Foreign Keys
+------------
+
+| Field | Target Table | Target Field |
+|-------|--------------|--------------|
+| uri-id | [item-uri](help/database/db_item-uri) | id |
+| gsid | [gserver](help/database/db_gserver) | id |
+
+Return to [database documentation](help/database)
diff --git a/doc/database/db_fcontact.md b/doc/database/db_fcontact.md
deleted file mode 100644 (file)
index 095c47a..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-Table fcontact
-===========
-
-Diaspora compatible contacts - used in the Diaspora implementation
-
-Fields
-------
-
-| Field             | Description                                                   | Type             | Null | Key | Default             | Extra          |
-| ----------------- | ------------------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
-| id                | sequential ID                                                 | int unsigned     | NO   | PRI | NULL                | auto_increment |
-| guid              | unique id                                                     | varbinary(255)   | NO   |     |                     |                |
-| url               |                                                               | varbinary(383)   | NO   |     |                     |                |
-| uri-id            | Id of the item-uri table entry that contains the fcontact url | int unsigned     | YES  |     | NULL                |                |
-| name              |                                                               | varchar(255)     | NO   |     |                     |                |
-| photo             |                                                               | varbinary(383)   | NO   |     |                     |                |
-| request           |                                                               | varbinary(383)   | NO   |     |                     |                |
-| nick              |                                                               | varchar(255)     | NO   |     |                     |                |
-| addr              |                                                               | varchar(255)     | NO   |     |                     |                |
-| batch             |                                                               | varbinary(383)   | NO   |     |                     |                |
-| notify            |                                                               | varbinary(383)   | NO   |     |                     |                |
-| poll              |                                                               | varbinary(383)   | NO   |     |                     |                |
-| confirm           |                                                               | varbinary(383)   | NO   |     |                     |                |
-| priority          |                                                               | tinyint unsigned | NO   |     | 0                   |                |
-| network           |                                                               | char(4)          | NO   |     |                     |                |
-| alias             |                                                               | varbinary(383)   | NO   |     |                     |                |
-| pubkey            |                                                               | text             | YES  |     | NULL                |                |
-| created           |                                                               | datetime         | NO   |     | 0001-01-01 00:00:00 |                |
-| updated           |                                                               | datetime         | NO   |     | 0001-01-01 00:00:00 |                |
-| interacting_count | Number of contacts this contact interactes with               | int unsigned     | YES  |     | 0                   |                |
-| interacted_count  | Number of contacts that interacted with this contact          | int unsigned     | YES  |     | 0                   |                |
-| post_count        | Number of posts and comments                                  | int unsigned     | YES  |     | 0                   |                |
-
-Indexes
-------------
-
-| Name    | Fields           |
-| ------- | ---------------- |
-| PRIMARY | id               |
-| addr    | addr(32)         |
-| url     | UNIQUE, url(190) |
-| uri-id  | UNIQUE, uri-id   |
-
-Foreign Keys
-------------
-
-| Field | Target Table | Target Field |
-|-------|--------------|--------------|
-| uri-id | [item-uri](help/database/db_item-uri) | id |
-
-Return to [database documentation](help/database)
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 74bd5b42379fb335ca8f7a9ef7b82b0ab9319810..6e50c9fb2838bc6968df0f2a39555ac6ee9e5b22 100644 (file)
@@ -74,7 +74,7 @@ class DBStructure
                $old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data',
                        'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule',
                        'deliverq', 'dsprphotoq', 'ffinder', 'sign', 'spam', 'term', 'user-item', 'thread', 'item', 'challenge',
-                       'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation'];
+                       'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation', 'fcontact'];
 
                $tables = DBA::selectToArray('INFORMATION_SCHEMA.TABLES', ['TABLE_NAME'],
                        ['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']);
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 adc88b13e82ec425edc0995724bc3a9bf04ef6bc..e57d92ceb738104e251276dd0b4cd0404bf3ac4c 100644 (file)
@@ -899,6 +899,11 @@ class PostUpdate
                        return true;
                }
 
+               if (!DBStructure::existsTable('fcontact')) {
+                       DI::config()->set('system', 'post_update_version', 1425);
+                       return true;
+               }
+
                $condition = ["`uri-id` IS NULL"];
                Logger::info('Start', ['rest' => DBA::count('fcontact', $condition)]);
 
index dba74b140743873c72502d488f5b05b7e938df21..94b42ce79cb5f41d9cd6b077c333e9fef7533b87 100644 (file)
@@ -86,7 +86,7 @@ class Status extends BaseFactory
         */
        public function createFromUriId(int $uriId, int $uid = 0, bool $reblog = true): \Friendica\Object\Api\Mastodon\Status
        {
-               $fields = ['uri-id', 'uid', 'author-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
+               $fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
                        'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id'];
                $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
                if (!$item) {
@@ -97,9 +97,18 @@ class Status extends BaseFactory
                        throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
                }
 
-               $is_reshare = $reblog && !is_null($item['causer-uri-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT);
-
-               $account = $this->mstdnAccountFactory->createFromUriId($is_reshare ? $item['causer-uri-id']:$item['author-uri-id'], $uid);
+               if (($item['gravity'] == Item::GRAVITY_ACTIVITY) && ($item['vid'] == Verb::getID(Activity::ANNOUNCE))) {
+                       $is_reshare = true;
+                       $account    = $this->mstdnAccountFactory->createFromUriId($item['author-uri-id'], $uid);
+                       $uriId      = $item['thr-parent-id'];
+                       $item       = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
+                       if (!$item) {
+                               throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
+                       }
+               } else {
+                       $is_reshare = $reblog && !is_null($item['causer-uri-id']) && ($item['causer-id'] != $item['author-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT);
+                       $account    = $this->mstdnAccountFactory->createFromUriId($is_reshare ? $item['causer-uri-id'] : $item['author-uri-id'], $uid);
+               }
 
                $count_announce = Post::countPosts([
                        'thr-parent-id' => $uriId,
@@ -190,19 +199,13 @@ class Status extends BaseFactory
                        }
                }
 
-               if ($item['vid'] == Verb::getID(Activity::ANNOUNCE)) {
-                       $reshare       = $this->createFromUriId($item['thr-parent-id'], $uid)->toArray();
-                       $reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'],'uid' => [0, $uid]]);
-                       $item['title'] = $reshared_item['title'] ?? $item['title'];
-                       $item['body']  = $reshared_item['body'] ?? $item['body'];
-               } else {
-                       $item['body']     = $this->contentItem->addSharedPost($item);
-                       $item['raw-body'] = $this->contentItem->addSharedPost($item, $item['raw-body']);
-                       $reshare = [];
-               }
+               $item['body']     = $this->contentItem->addSharedPost($item);
+               $item['raw-body'] = $this->contentItem->addSharedPost($item, $item['raw-body']);
 
                if ($is_reshare) {
                        $reshare = $this->createFromUriId($uriId, $uid, false)->toArray();
+               } else {
+                       $reshare = [];
                }
 
                return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $reshare, $poll);
index 34ce8e684e7272c91c482922c5315a8d656ad689..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);
@@ -2057,9 +2057,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
@@ -2089,7 +2090,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) : '');
        }
 
        /**
@@ -2114,9 +2123,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
@@ -2147,7 +2157,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) : '');
        }
 
        /**
@@ -2468,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);
diff --git a/src/Model/FContact.php b/src/Model/FContact.php
deleted file mode 100644 (file)
index e13dcd4..0000000
+++ /dev/null
@@ -1,161 +0,0 @@
-<?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\Model;
-
-use Friendica\Core\Logger;
-use Friendica\Core\Protocol;
-use Friendica\Core\Worker;
-use Friendica\Database\DBA;
-use Friendica\DI;
-use Friendica\Model\Item;
-use Friendica\Network\Probe;
-use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Strings;
-
-class FContact
-{
-       /**
-        * Fetches data for a given handle
-        *
-        * @param string $handle The handle
-        * @param boolean $update true = always update, false = never update, null = update when not found or outdated
-        *
-        * @return array the queried data
-        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
-        * @throws \ImagickException
-        */
-       public static function getByURL(string $handle, $update = null): array
-       {
-               Logger::debug('Fetch fcontact', ['handle' => $handle, 'update' => $update]);
-               $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
-               if (!DBA::isResult($person)) {
-                       $urls = [$handle, str_replace('http://', 'https://', $handle), Strings::normaliseLink($handle)];
-                       $person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'url' => $urls]);
-               }
-
-               if (DBA::isResult($person)) {
-                       Logger::debug('In cache', ['handle' => $handle]);
-
-                       if (is_null($update)) {
-                               $update = empty($person['guid']) || empty($person['uri-id']) || ($person['created'] <= DBA::NULL_DATETIME);
-                               if (GServer::getNextUpdateDate(true, $person['created'], $person['updated'], false) < DateTimeFormat::utcNow()) {
-                                       Logger::debug('Start background update', ['handle' => $handle]);
-                                       Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateFContact', $handle);
-                               }
-                       }
-               } elseif (is_null($update)) {
-                       $update = true;
-               } else {
-                       $person = [];
-               }
-
-               if ($update) {
-                       Logger::info('create or refresh', ['handle' => $handle]);
-                       $data = Probe::uri($handle, Protocol::DIASPORA);
-
-                       // Note that Friendica contacts will return a "Diaspora person"
-                       // if Diaspora connectivity is enabled on their server
-                       if ($data['network'] ?? '' === Protocol::DIASPORA) {
-                               self::updateFromProbeArray($data);
-
-                               $person = self::getByURL($handle, false);
-                       }
-               }
-
-               return $person;
-       }
-
-       /**
-        * Updates the fcontact table
-        *
-        * @param array $arr The fcontact data
-        * @throws \Exception
-        */
-       public static function updateFromProbeArray(array $arr)
-       {
-               $uriid = ItemURI::insert(['uri' => $arr['url'], 'guid' => $arr['guid']]);
-
-               $fcontact  = DBA::selectFirst('fcontact', ['created'], ['url' => $arr['url'], 'network' => $arr['network']]);
-               $contact   = Contact::getByUriId($uriid, ['id', 'created']);
-               $apcontact = APContact::getByURL($arr['url'], false);
-               if (!empty($apcontact)) {
-                       $interacted  = $apcontact['following_count'];
-                       $interacting = $apcontact['followers_count'];
-                       $posts       = $apcontact['statuses_count'];
-               } elseif (!empty($contact['id'])) {
-                       $last_interaction = DateTimeFormat::utc('now - 180 days');
-
-                       $interacted  = DBA::count('contact-relation', ["`cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
-                       $interacting = DBA::count('contact-relation', ["`relation-cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
-                       $posts       = DBA::count('post', ['author-id' => $contact['id'], 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]]);
-               }
-
-               $fields = [
-                       'name'              => $arr['name'],
-                       'photo'             => $arr['photo'],
-                       'request'           => $arr['request'],
-                       'nick'              => $arr['nick'],
-                       'addr'              => strtolower($arr['addr']),
-                       'guid'              => $arr['guid'],
-                       'batch'             => $arr['batch'],
-                       'notify'            => $arr['notify'],
-                       'poll'              => $arr['poll'],
-                       'confirm'           => $arr['confirm'],
-                       'alias'             => $arr['alias'],
-                       'pubkey'            => $arr['pubkey'],
-                       'uri-id'            => $uriid,
-                       'interacting_count' => $interacting ?? 0,
-                       'interacted_count'  => $interacted ?? 0,
-                       'post_count'        => $posts ?? 0,
-                       'updated'           => DateTimeFormat::utcNow(),
-               ];
-
-               if (empty($fcontact['created'])) {
-                       $fields['created'] = $fields['updated'];
-               } elseif (!empty($contact['created']) && ($fcontact['created'] <= DBA::NULL_DATETIME)) {
-                       $fields['created'] = $contact['created'];
-               }
-
-               $fields = DI::dbaDefinition()->truncateFieldsForTable('fcontact', $fields);
-               DBA::update('fcontact', $fields, ['url' => $arr['url'], 'network' => $arr['network']], true);
-       }
-
-       /**
-        * get a url (scheme://domain.tld/u/user) from a given Diaspora*
-        * fcontact guid
-        *
-        * @param string $fcontact_guid Hexadecimal string guid
-        * @return string|null the contact url or null
-        * @throws \Exception
-        */
-       public static function getUrlByGuid(string $fcontact_guid)
-       {
-               Logger::info('fcontact', ['guid' => $fcontact_guid]);
-
-               $fcontact = DBA::selectFirst('fcontact', ['url'], ["`url` != ? AND `network` = ? AND `guid` = ?", '', Protocol::DIASPORA, $fcontact_guid]);
-               if (DBA::isResult($fcontact)) {
-                       return $fcontact['url'];
-               }
-
-               return null;
-       }
-}
index 7de8699401dc14da7f7b96eeb710815e8c503376..b9bb63ae78bc0e487134dd667ef0390669a3cd24 100644 (file)
@@ -80,8 +80,15 @@ class Statuses extends BaseApi
                }
 
                if (!$request['pinned'] && !$request['only_media']) {
-                       $condition = DBA::mergeConditions($condition, ["(`gravity` IN (?, ?) OR (`gravity` = ? AND `vid` = ?))",
-                               Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE)]);
+                       if ($request['exclude_replies']) {
+                               $condition = DBA::mergeConditions($condition, ["(`gravity` = ? OR (`gravity` = ? AND `vid` = ?))",
+                                       Item::GRAVITY_PARENT, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE)]);
+                       } else {
+                               $condition = DBA::mergeConditions($condition, ["(`gravity` IN (?, ?) OR (`gravity` = ? AND `vid` = ?))",
+                                       Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE)]);
+                       }
+               } elseif ($request['exclude_replies']) {
+                       $condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
                }
 
                if (!empty($request['max_id'])) {
@@ -97,16 +104,10 @@ class Statuses extends BaseApi
                        $params['order'] = ['uri-id'];
                }
 
-               if (($request['pinned'] || $request['only_media']) && $request['exclude_replies']) {
-                       $condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
-               }
-
                if ($request['pinned']) {
                        $items = DBA::select('collection-view', ['uri-id'], $condition, $params);
                } elseif ($request['only_media']) {
                        $items = DBA::select('media-view', ['uri-id'], $condition, $params);
-               } elseif ($request['exclude_replies']) {
-                       $items = Post::selectThreadForUser($uid, ['uri-id'], $condition, $params);
                } else {
                        $items = Post::selectForUser($uid, ['uri-id'], $condition, $params);
                }
index 718c95c107cc4de8b438a5c0cc2684313e73eb31..8e5850ac0b4ad30efbfef9a67378054121eeffad 100644 (file)
@@ -23,7 +23,6 @@ namespace Friendica\Module;
 
 use Friendica\BaseModule;
 use Friendica\Core\System;
-use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\APContact;
 use Friendica\Model\User;
index a4ef6ab414e005ca3a0248544556a1eba8c28ca9..78e403e6c751cda090c977b82a99ef33e1935d6a 100644 (file)
@@ -182,6 +182,12 @@ class Photo extends BaseModule
                        throw new HTTPException\InternalServerErrorException($error);
                }
 
+               if (!empty($request['static'])) {
+                       $img = new Image($imgdata, $photo['type']);
+                       $img->toStatic();
+                       $imgdata = $img->asString();
+               }
+
                // if customsize is set and image is not a gif, resize it
                if ($photo['type'] !== 'image/gif' && $customsize > 0 && $customsize <= Proxy::PIXEL_THUMB && $square_resize) {
                        $img = new Image($imgdata, $photo['type']);
index 0a4a17e462e1b3deb9d0145235834105be2ef50c..2ffe91feb5541c5713691d4f8edfc14975dcc171 100644 (file)
@@ -173,7 +173,7 @@ class Status extends BaseProfile
 
                $condition = DBA::mergeConditions($condition, ["((`gravity` = ? AND `wall`) OR
                        (`gravity` = ? AND `vid` = ? AND `origin`
-                       AND `thr-parent-id` IN (SELECT `uri-id` FROM `post` WHERE `gravity` = ? AND `network` IN (?, ?))))",
+                       AND EXISTS(SELECT `uri-id` FROM `post` WHERE `uri-id` = `post-user-view`.`thr-parent-id` AND `gravity` = ? AND `network` IN (?, ?))))",
                        Item::GRAVITY_PARENT, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Item::GRAVITY_PARENT, Protocol::ACTIVITYPUB, Protocol::DFRN]);
 
                $condition = DBA::mergeConditions($condition, ['uid' => $profile['uid'], 'network' => Protocol::FEDERATED,
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 3369a18c00a6855106d2efc5cd794fb3d10db8a4..afc739e1c620215f5f1b7ff2a0cc7a154dbb2936 100644 (file)
@@ -109,9 +109,9 @@ class Account extends BaseDataTransferObject
                $this->note            = BBCode::convertForUriId($account['uri-id'], $account['about'], BBCode::EXTERNAL);
                $this->url             = $account['url'];
                $this->avatar          = Contact::getAvatarUrlForId($account['id'] ?? 0 ?: $account['pid'], Proxy::SIZE_SMALL, $account['updated'], $account['guid'] ?? '');
-               $this->avatar_static   = $this->avatar;
+               $this->avatar_static   = Contact::getAvatarUrlForId($account['id'] ?? 0 ?: $account['pid'], Proxy::SIZE_SMALL, $account['updated'], $account['guid'] ?? '', true);
                $this->header          = Contact::getHeaderUrlForId($account['id'] ?? 0 ?: $account['pid'], '', $account['updated'], $account['guid'] ?? '');
-               $this->header_static   = $this->header;
+               $this->header_static   = Contact::getHeaderUrlForId($account['id'] ?? 0 ?: $account['pid'], '', $account['updated'], $account['guid'] ?? '', true);
                $this->followers_count = $account['ap-followers_count'] ?? $account['diaspora-interacted_count'] ?? 0;
                $this->following_count = $account['ap-following_count'] ?? $account['diaspora-interacting_count'] ?? 0;
                $this->statuses_count  = $account['ap-statuses_count'] ?? $account['diaspora-post_count'] ?? 0;
index 87401304db8af895ee8d46b36a1096c5d1004b79..6eb86200310aa6bb28d8b908e90fefe0227499d1 100644 (file)
@@ -25,6 +25,7 @@ use Exception;
 use Friendica\DI;
 use Friendica\Util\Images;
 use Imagick;
+use ImagickDraw;
 use ImagickPixel;
 use GDImage;
 use kornrunner\Blurhash\Blurhash;
@@ -64,7 +65,7 @@ class Image
                }
                $this->type = $type;
 
-               if ($this->isImagick() && $this->loadData($data)) {
+               if ($this->isImagick() && (empty($data) || $this->loadData($data))) {
                        return;
                } else {
                        // Failed to load with Imagick, fallback
@@ -732,11 +733,6 @@ class Image
         */
        public function getBlurHash(): string
        {
-               if ($this->isImagick()) {
-                       // Imagick is not supported
-                       return '';
-               }
-
                $width = $this->getWidth();
                $height = $this->getHeight();
 
@@ -750,10 +746,14 @@ class Image
                for ($y = 0; $y < $height; ++$y) {
                        $row = [];
                        for ($x = 0; $x < $width; ++$x) {
-                               $index = imagecolorat($this->image, $x, $y);
-                               $colors = imagecolorsforindex($this->image, $index);
-
-                               $row[] = [$colors['red'], $colors['green'], $colors['blue']];
+                               if ($this->isImagick()) {
+                                       $colors = $this->image->getImagePixelColor($x, $y)->getColor();
+                                       $row[] = [$colors['r'], $colors['g'], $colors['b']];
+                               } else {
+                                       $index = imagecolorat($this->image, $x, $y);
+                                       $colors = @imagecolorsforindex($this->image, $index);
+                                       $row[] = [$colors['red'], $colors['green'], $colors['blue']];
+                               }
                        }
                        $pixels[] = $row;
                }
@@ -775,25 +775,37 @@ class Image
         */
        public function getFromBlurHash(string $blurhash, int $width, int $height)
        {
-               if ($this->isImagick()) {
-                       // Imagick is not supported
-                       return;
-               }
-
                $scaled = Images::getScalingDimensions($width, $height, 90);
                $pixels = Blurhash::decode($blurhash, $scaled['width'], $scaled['height']);
 
-               $this->image = imagecreatetruecolor($scaled['width'], $scaled['height']);
+               if ($this->isImagick()) {
+                       $this->image = new Imagick();
+                       $draw  = new ImagickDraw();
+                       $this->image->newImage($scaled['width'], $scaled['height'], '', 'png');
+               } else {
+                       $this->image = imagecreatetruecolor($scaled['width'], $scaled['height']);
+               }
+
                for ($y = 0; $y < $scaled['height']; ++$y) {
                        for ($x = 0; $x < $scaled['width']; ++$x) {
                                [$r, $g, $b] = $pixels[$y][$x];
-                               imagesetpixel($this->image, $x, $y, imagecolorallocate($this->image, $r, $g, $b));
+                               if ($this->isImagick()) {
+                                       $draw->setFillColor("rgb($r, $g, $b)");
+                                       $draw->point($x, $y);
+                               } else {
+                                       imagesetpixel($this->image, $x, $y, imagecolorallocate($this->image, $r, $g, $b));
+                               }
                        }
                }
 
-               $this->width  = imagesx($this->image);
-               $this->height = imagesy($this->image);
-               $this->valid  = true;
+               if ($this->isImagick()) {
+                       $this->image->drawImage($draw);
+               } else {
+                       $this->width  = imagesx($this->image);
+                       $this->height = imagesy($this->image);
+               }
+
+               $this->valid = true;
 
                $this->scaleUp(min($width, $height));
        }
index a86cf2094dc2343fc353426da5bbd52810dfb3df..92f3e8154548aa891a8f9d3797ce9cece8a11f2c 100644 (file)
@@ -34,7 +34,6 @@ use Friendica\DI;
 use Friendica\Model\Contact;
 use Friendica\Model\Conversation;
 use Friendica\Model\Event;
-use Friendica\Model\FContact;
 use Friendica\Model\GServer;
 use Friendica\Model\Item;
 use Friendica\Model\ItemURI;
@@ -45,6 +44,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 +981,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 54f09e9d970c8ce209249a39a609b17d2c989b90..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,6 +41,7 @@ use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
+use Friendica\Network\HTTPException;
 use Friendica\Network\Probe;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
@@ -161,8 +161,12 @@ class Diaspora
                        return false;
                }
 
-               $key = self::key($handle);
-               if ($key == '') {
+               try {
+                       $key = self::key(WebFingerUri::fromString($handle));
+                       if ($key == '') {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\InvalidArgumentException $e) {
                        Logger::notice("Couldn't get a key for handle " . $handle . ". Discarding.");
                        return false;
                }
@@ -300,8 +304,13 @@ class Diaspora
                        }
                }
 
-               $key = self::key($author_addr);
-               if ($key == '') {
+               try {
+                       $author = WebFingerUri::fromString($author_addr);
+                       $key = self::key($author);
+                       if ($key == '') {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\InvalidArgumentException $e) {
                        Logger::notice("Couldn't get a key for handle " . $author_addr . ". Discarding.");
                        if ($no_exit) {
                                return false;
@@ -322,8 +331,8 @@ class Diaspora
 
                return [
                        'message' => (string)Strings::base64UrlDecode($base->data),
-                       'author' => XML::unescape($author_addr),
-                       'key' => (string)$key
+                       'author'  => $author->getAddr(),
+                       'key'     => (string)$key
                ];
        }
 
@@ -356,7 +365,7 @@ class Diaspora
 
                if ($children->header) {
                        $public = true;
-                       $author_link = str_replace('acct:', '', $children->header->author_id);
+                       $idom = $children->header;
                } else {
                        // This happens with posts from a relais
                        if (empty($privKey)) {
@@ -384,8 +393,13 @@ class Diaspora
 
                        $inner_iv = base64_decode($idom->iv);
                        $inner_aes_key = base64_decode($idom->aes_key);
+               }
 
-                       $author_link = str_replace('acct:', '', $idom->author_id);
+               try {
+                       $author = WebFingerUri::fromString($idom->author_id);
+               } catch (\Throwable $e) {
+                       Logger::notice('Could not retrieve author URI.', ['idom' => $idom]);
+                       throw new \Friendica\Network\HTTPException\BadRequestException();
                }
 
                $dom = $basedom->children(ActivityNamespace::SALMON_ME);
@@ -439,17 +453,11 @@ class Diaspora
                        $inner_decrypted = self::aesDecrypt($inner_aes_key, $inner_iv, $inner_encrypted);
                }
 
-               if (!$author_link) {
-                       Logger::notice('Could not retrieve author URI.');
-                       throw new \Friendica\Network\HTTPException\BadRequestException();
-               }
                // 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_link);
-               $key = self::key($author_link);
-
+               Logger::notice('Fetching key for ' . $author);
+               $key = self::key($author);
                if (!$key) {
                        Logger::notice('Could not retrieve author key.');
                        throw new \Friendica\Network\HTTPException\BadRequestException();
@@ -465,9 +473,9 @@ class Diaspora
                Logger::notice('Message verified.');
 
                return [
-                       'message' => (string)$inner_decrypted,
-                       'author' => XML::unescape($author_link),
-                       'key' => (string)$key
+                       'message' => $inner_decrypted,
+                       'author'  => $author->getAddr(),
+                       'key'     => $key
                ];
        }
 
@@ -520,7 +528,7 @@ class Diaspora
        {
                // The sender is the handle of the contact that sent the message.
                // This will often be different with relayed messages (for example "like" and "comment")
-               $sender = $msg['author'];
+               $sender = WebFingerUri::fromString($msg['author']);
 
                // This is only needed for private postings since this is already done for public ones before
                if (is_null($fields)) {
@@ -535,7 +543,7 @@ class Diaspora
 
                $type = $fields->getName();
 
-               Logger::info('Received message', ['type' => $type, 'sender' => $sender, 'user' => $importer['uid']]);
+               Logger::info('Received message', ['type' => $type, 'sender' => $sender->getAddr(), 'user' => $importer['uid']]);
 
                switch ($type) {
                        case 'account_migration':
@@ -743,7 +751,7 @@ class Diaspora
                }
 
                if (isset($parent_author_signature)) {
-                       $key = self::key($msg['author']);
+                       $key = self::key(WebFingerUri::fromString($msg['author']));
                        if (empty($key)) {
                                Logger::info('No key found for parent', ['author' => $msg['author']]);
                                return false;
@@ -755,8 +763,12 @@ class Diaspora
                        }
                }
 
-               $key = self::key($fields->author);
-               if (empty($key)) {
+               try {
+                       $key = self::key(WebFingerUri::fromString($fields->author));
+                       if (empty($key)) {
+                               throw new \InvalidArgumentException();
+                       }
+               } catch (\Throwable $e) {
                        Logger::info('No key found', ['author' => $fields->author]);
                        return false;
                }
@@ -772,55 +784,51 @@ class Diaspora
        /**
         * Fetches the public key for a given handle
         *
-        * @param string $handle The handle
+        * @param WebFingerUri $uri The handle
         *
         * @return string The public key
-        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function key(string $handle = null): string
+       private static function key(WebFingerUri $uri): string
        {
-               $handle = strval($handle);
-
-               Logger::notice('Fetching diaspora key', ['handle' => $handle, 'callstack' => System::callstack(20)]);
-
-               $fcontact = FContact::getByURL($handle);
-               if (!empty($fcontact['pubkey'])) {
-                       return $fcontact['pubkey'];
+               Logger::notice('Fetching diaspora key', ['handle' => $uri->getAddr(), 'callstack' => System::callstack(20)]);
+               try {
+                       return DI::dsprContact()->getByAddr($uri)->pubKey;
+               } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                       return '';
                }
-
-               return '';
        }
 
        /**
         * Get a contact id for a given handle
         *
-        * @todo  Move to Friendica\Model\Contact
-        *
-        * @param int    $uid    The user id
-        * @param string $handle The handle in the format user@domain.tld
+        * @param int          $uid The user id
+        * @param WebFingerUri $uri
         *
         * @return array Contact data
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function contactByHandle(int $uid, string $handle): array
+       private static function contactByHandle(int $uid, WebFingerUri $uri): array
        {
-               return Contact::getByURL($handle, null, [], $uid);
+               return Contact::getByURL($uri->getAddr(), null, [], $uid);
        }
 
        /**
         * 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);
        }
 
        /**
@@ -874,21 +882,22 @@ class Diaspora
        /**
         * Fetches the contact id for a handle and checks if posting is allowed
         *
-        * @param array  $importer   Array of the importer user
-        * @param string $handle     The checked handle in the format user@domain.tld
-        * @param bool   $is_comment Is the check for a comment?
+        * @param array        $importer    Array of the importer user
+        * @param WebFingerUri $contact_uri The checked contact
+        * @param bool         $is_comment  Is the check for a comment?
         *
         * @return array|bool The contact data or false on error
-        * @throws \Exception
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
-       private static function allowedContactByHandle(array $importer, string $handle, bool $is_comment = false)
+       private static function allowedContactByHandle(array $importer, WebFingerUri $contact_uri, bool $is_comment = false)
        {
-               $contact = self::contactByHandle($importer['uid'], $handle);
+               $contact = self::contactByHandle($importer['uid'], $contact_uri);
                if (!$contact) {
-                       Logger::notice('A Contact for handle ' . $handle . ' and user ' . $importer['uid'] . ' was not found');
+                       Logger::notice('A Contact for handle ' . $contact_uri . ' and user ' . $importer['uid'] . ' was not found');
                        // If a contact isn't found, we accept it anyway if it is a comment
                        if ($is_comment && ($importer['uid'] != 0)) {
-                               return self::contactByHandle(0, $handle);
+                               return self::contactByHandle(0, $contact_uri);
                        } elseif ($is_comment) {
                                return $importer;
                        } else {
@@ -897,7 +906,7 @@ class Diaspora
                }
 
                if (!self::postAllow($importer, $contact, $is_comment)) {
-                       Logger::notice('The handle: ' . $handle . ' is not allowed to post to user ' . $importer['uid']);
+                       Logger::notice('The handle: ' . $contact_uri . ' is not allowed to post to user ' . $importer['uid']);
                        return false;
                }
                return $contact;
@@ -966,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]';
@@ -1011,7 +1020,7 @@ class Diaspora
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       public static function storeByGuid(string $guid, string $server, bool $force)
+       private static function storeByGuid(string $guid, string $server, bool $force)
        {
                $serverparts = parse_url($server);
 
@@ -1092,25 +1101,27 @@ class Diaspora
                        return self::message($source_xml->root_guid, $server, ++$level);
                }
 
-               $author = '';
+               $author_handle = '';
 
                // Fetch the author - for the old and the new Diaspora version
                if ($source_xml->post->status_message && $source_xml->post->status_message->diaspora_handle) {
-                       $author = (string)$source_xml->post->status_message->diaspora_handle;
+                       $author_handle = (string)$source_xml->post->status_message->diaspora_handle;
                } elseif ($source_xml->author && ($source_xml->getName() == 'status_message')) {
-                       $author = (string)$source_xml->author;
+                       $author_handle = (string)$source_xml->author;
                }
 
-               // If this isn't a "status_message" then quit
-               if (!$author) {
+               try {
+                       $author = WebFingerUri::fromString($author_handle);
+               } catch (\InvalidArgumentException $e) {
+                       // If this isn't a "status_message" then quit
                        Logger::info("Message doesn't seem to be a status message");
                        return false;
                }
 
                return [
                        'message' => $x,
-                       'author' => $author,
-                       'key' => self::key($author)
+                       'author'  => $author->getAddr(),
+                       'key'     => self::key($author)
                ];
        }
 
@@ -1157,15 +1168,15 @@ class Diaspora
        /**
         * Fetches the item record of a given guid
         *
-        * @param int    $uid     The user id
-        * @param string $guid    message guid
-        * @param string $author  The handle of the item
-        * @param array  $contact The contact of the item owner
+        * @param int          $uid     The user id
+        * @param string       $guid    message guid
+        * @param WebFingerUri $author
+        * @param array        $contact The contact of the item owner
         *
         * @return array|bool the item record or false on failure
         * @throws \Exception
         */
-       private static function parentItem(int $uid, string $guid, string $author, array $contact)
+       private static function parentItem(int $uid, string $guid, WebFingerUri $author, array $contact)
        {
                $fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
                        'author-name', 'author-link', 'author-avatar', 'gravity',
@@ -1175,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()]);
                        }
                }
 
@@ -1200,20 +1214,20 @@ class Diaspora
        }
 
        /**
-        * returns contact details
+        * returns contact details for the given user
         *
-        * @param array $def_contact The default contact if the person isn't found
-        * @param array $person      The record of the person
-        * @param int   $uid         The user id
+        * @param array  $def_contact The default details if the contact isn't found
+        * @param string $contact_url The url of the contact
+        * @param int    $uid         The user id
         *
         * @return array
         *      'cid' => contact id
         *      'network' => network type
         * @throws \Exception
         */
-       private static function authorContactByUrl(array $def_contact, array $person, int $uid): array
+       private static function authorContactByUrl(array $def_contact, string $contact_url, int $uid): array
        {
-               $condition = ['nurl' => Strings::normaliseLink($person['url']), 'uid' => $uid];
+               $condition = ['nurl' => Strings::normaliseLink($contact_url), 'uid' => $uid];
                $contact = DBA::selectFirst('contact', ['id', 'network'], $condition);
                if (DBA::isResult($contact)) {
                        $cid = $contact['id'];
@@ -1318,21 +1332,27 @@ class Diaspora
         */
        private static function receiveAccountMigration(array $importer, SimpleXMLElement $data): bool
        {
-               $old_handle = XML::unescape($data->author);
-               $new_handle = XML::unescape($data->profile->author);
+               try {
+                       $old_author = WebFingerUri::fromString(XML::unescape($data->author));
+                       $new_author = WebFingerUri::fromString(XML::unescape($data->profile->author));
+               } catch (\Throwable $e) {
+                       Logger::notice('Cannot find handles for sender and user', ['data' => $data]);
+                       return false;
+               }
+
                $signature = XML::unescape($data->signature);
 
-               $contact = self::contactByHandle($importer['uid'], $old_handle);
+               $contact = self::contactByHandle($importer['uid'], $old_author);
                if (!$contact) {
-                       Logger::notice('Cannot find contact for sender: ' . $old_handle . ' and user ' . $importer['uid']);
+                       Logger::notice('Cannot find contact for sender: ' . $old_author . ' and user ' . $importer['uid']);
                        return false;
                }
 
-               Logger::notice('Got migration for ' . $old_handle . ', to ' . $new_handle . ' with user ' . $importer['uid']);
+               Logger::notice('Got migration for ' . $old_author . ', to ' . $new_author . ' with user ' . $importer['uid']);
 
                // Check signature
-               $signed_text = 'AccountMigration:' . $old_handle . ':' . $new_handle;
-               $key = self::key($old_handle);
+               $signed_text = 'AccountMigration:' . $old_author . ':' . $new_author;
+               $key = self::key($old_author);
                if (!Crypto::rsaVerify($signed_text, $signature, $key, 'sha256')) {
                        Logger::notice('No valid signature for migration.');
                        return false;
@@ -1342,9 +1362,9 @@ class Diaspora
                self::receiveProfile($importer, $data->profile);
 
                // change the technical stuff in contact
-               $data = Probe::uri($new_handle);
+               $data = Probe::uri($new_author);
                if ($data['network'] == Protocol::PHANTOM) {
-                       Logger::notice("Account for " . $new_handle . " couldn't be probed.");
+                       Logger::notice("Account for " . $new_author . " couldn't be probed.");
                        return false;
                }
 
@@ -1360,7 +1380,7 @@ class Diaspora
                        'network' => $data['network'],
                ];
 
-               Contact::update($fields, ['addr' => $old_handle]);
+               Contact::update($fields, ['addr' => $old_author->getAddr()]);
 
                Logger::notice('Contacts are updated.');
 
@@ -1377,15 +1397,15 @@ class Diaspora
         */
        private static function receiveAccountDeletion(SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
 
-               $contacts = DBA::select('contact', ['id'], ['addr' => $author]);
+               $contacts = DBA::select('contact', ['id'], ['addr' => $author_handle]);
                while ($contact = DBA::fetch($contacts)) {
                        Contact::remove($contact['id']);
                }
                DBA::close($contacts);
 
-               Logger::notice('Removed contacts for ' . $author);
+               Logger::notice('Removed contacts for ' . $author_handle);
 
                return true;
        }
@@ -1393,27 +1413,24 @@ class Diaspora
        /**
         * Fetch the uri from our database if we already have this item (maybe from ourselves)
         *
-        * @param string  $author    Author handle
-        * @param string  $guid      Message guid
-        * @param boolean $onlyfound Only return uri when found in the database
+        * @param string            $guid       Message guid
+        * @param WebFingerUri|null $person_uri Optional person to derive the base URL from
         *
-        * @return string The constructed uri or the one from our database or empty string on if $onlyfound is true
+        * @return string The constructed uri or the one from our database or empty string
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function getUriFromGuid(string $author, string $guid, bool $onlyfound = false): string
+       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 (!$onlyfound) {
-                       $person = FContact::getByURL($author);
-
-                       $parts = parse_url($person['url']);
-                       unset($parts['path']);
-                       $host_url = (string)Uri::fromParts($parts);
-
-                       return $host_url . '/objects/' . $guid;
+               } elseif ($person_uri) {
+                       try {
+                               return DI::dsprContact()->selectOneByAddr($person_uri)->baseurl . '/objects/' . $guid;
+                       } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) {
+                               return '';
+                       }
                }
 
                return '';
@@ -1444,31 +1461,31 @@ 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']);
+                       }
                }
        }
 
        /**
         * Processes an incoming comment
         *
-        * @param array  $importer  Array of the importer user
-        * @param string $sender    The sender of the message
+        * @param array            $importer  Array of the importer user
+        * @param WebFingerUri     $sender    The sender of the message
         * @param SimpleXMLElement $data      The message object
-        * @param string $xml       The original XML of the message
-        * @param int    $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
+        * @param string           $xml       The original XML of the message
+        * @param int              $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
         *
-        * @return int The message id of the generated comment or "false" if there was an error
+        * @return bool The message id of the generated comment or "false" if there was an error
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function receiveComment(array $importer, string $sender, SimpleXMLElement $data, string $xml, int $direction): bool
+       private static function receiveComment(array $importer, WebFingerUri $sender, SimpleXMLElement $data, string $xml, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
                $text = XML::unescape($data->text);
@@ -1481,7 +1498,7 @@ class Diaspora
 
                if (isset($data->thread_parent_guid)) {
                        $thread_parent_guid = XML::unescape($data->thread_parent_guid);
-                       $thr_parent = self::getUriFromGuid('', $thread_parent_guid, true);
+                       $thr_parent = self::getUriFromGuid($thread_parent_guid);
                } else {
                        $thr_parent = '';
                }
@@ -1505,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, $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                $datarray = [];
 
@@ -1520,17 +1538,17 @@ 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);
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray['verb'] = Activity::POST;
@@ -1551,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']);
@@ -1601,20 +1619,26 @@ class Diaspora
         */
        private static function receiveConversationMessage(array $importer, array $contact, SimpleXMLElement $data, array $msg, $mesg, array $conversation): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $guid = XML::unescape($data->guid);
                $subject = XML::unescape($data->subject);
 
                // "diaspora_handle" is the element name from the old version
                // "author" is the element name from the new version
                if ($mesg->author) {
-                       $msg_author = XML::unescape($mesg->author);
+                       $msg_author_handle = XML::unescape($mesg->author);
                } elseif ($mesg->diaspora_handle) {
-                       $msg_author = XML::unescape($mesg->diaspora_handle);
+                       $msg_author_handle = XML::unescape($mesg->diaspora_handle);
                } else {
                        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);
@@ -1625,23 +1649,20 @@ class Diaspora
                        return false;
                }
 
-               $body = Markdown::toBBCode($msg_text);
-               $message_uri = $msg_author . ':' . $msg_guid;
-
-               $person = FContact::getByURL($msg_author);
+               $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,
-                       'uri'        => $message_uri,
-                       'parent-uri' => $author . ':' . $guid,
+                       'body'       => Markdown::toBBCode($msg_text),
+                       'uri'        => $msg_author_handle . ':' . $msg_guid,
+                       'parent-uri' => $author_handle . ':' . $guid,
                        'created'    => $msg_created_at
                ]);
        }
@@ -1658,7 +1679,7 @@ class Diaspora
         */
        private static function receiveConversation(array $importer, array $msg, SimpleXMLElement $data)
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $guid = XML::unescape($data->guid);
                $subject = XML::unescape($data->subject);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
@@ -1671,7 +1692,7 @@ class Diaspora
                        return false;
                }
 
-               $contact = self::allowedContactByHandle($importer, $msg['author'], true);
+               $contact = self::allowedContactByHandle($importer, WebFingerUri::fromString($msg['author']), true);
                if (!$contact) {
                        return false;
                }
@@ -1685,7 +1706,7 @@ class Diaspora
                        $r = DBA::insert('conv', [
                                'uid'     => $importer['uid'],
                                'guid'    => $guid,
-                               'creator' => $author,
+                               'creator' => $author_handle,
                                'created' => $created_at,
                                'updated' => DateTimeFormat::utcNow(),
                                'subject' => $subject,
@@ -1711,18 +1732,18 @@ class Diaspora
        /**
         * Processes "like" messages
         *
-        * @param array  $importer  Array of the importer user
-        * @param string $sender    The sender of the message
+        * @param array            $importer  Array of the importer user
+        * @param WebFingerUri     $sender    The sender of the message
         * @param SimpleXMLElement $data      The message object
-        * @param int    $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
+        * @param int              $direction Indicates if the message had been fetched or pushed (self::PUSHED, self::FETCHED, self::FORCED_FETCH)
         *
         * @return bool Success or failure
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         * @throws \ImagickException
         */
-       private static function receiveLike(array $importer, string $sender, SimpleXMLElement $data, int $direction): bool
+       private static function receiveLike(array $importer, WebFingerUri $sender, SimpleXMLElement $data, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
                $parent_type = XML::unescape($data->parent_type);
@@ -1753,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, $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.
@@ -1780,11 +1802,11 @@ 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($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
 
                $datarray['verb'] = $verb;
                $datarray['gravity'] = Item::GRAVITY_ACTIVITY;
@@ -1843,13 +1865,13 @@ class Diaspora
         */
        private static function receiveMessage(array $importer, SimpleXMLElement $data): bool
        {
-               $author = 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;
                }
@@ -1858,41 +1880,37 @@ class Diaspora
                        GServer::setProtocol($contact['gsid'], Post\DeliveryData::DIASPORA);
                }
 
-               $conversation = null;
-
                $condition = ['uid' => $importer['uid'], 'guid' => $conversation_guid];
                $conversation = DBA::selectFirst('conv', [], $condition);
-
                if (!DBA::isResult($conversation)) {
                        Logger::notice('Conversation not available.');
                        return false;
                }
 
-               $message_uri = $author . ':' . $guid;
-
-               $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'        => $message_uri,
-                       'parent-uri' => $author . ':' . $conversation['guid'],
+                       'uri'        => $author_uri . ':' . $guid,
+                       'parent-uri' => $author_uri . ':' . $conversation['guid'],
                        'created'    => $created_at
                ]);
        }
@@ -1910,7 +1928,7 @@ class Diaspora
         */
        private static function receiveParticipation(array $importer, SimpleXMLElement $data, int $direction): bool
        {
-               $author = strtolower(XML::unescape($data->author));
+               $author = WebFingerUri::fromString(strtolower(XML::unescape($data->author)));
                $guid = XML::unescape($data->guid);
                $parent_guid = XML::unescape($data->parent_guid);
 
@@ -1941,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, $importer['uid']);
+               $author_contact = self::authorContactByUrl($contact, $author_url, $importer['uid']);
 
                // Store participation
                $datarray = [];
@@ -1960,11 +1979,11 @@ 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($author, $guid);
+               $datarray['uri'] = self::getUriFromGuid($guid, $author);
 
                $datarray['verb'] = Activity::FOLLOW;
                $datarray['gravity'] = Item::GRAVITY_ACTIVITY;
@@ -2056,7 +2075,7 @@ class Diaspora
         */
        private static function receiveProfile(array $importer, SimpleXMLElement $data): bool
        {
-               $author = strtolower(XML::unescape($data->author));
+               $author = WebFingerUri::fromString(strtolower(XML::unescape($data->author)));
 
                $contact = self::contactByHandle($importer['uid'], $author);
                if (!$contact) {
@@ -2084,16 +2103,13 @@ class Diaspora
 
                $keywords = implode(', ', $keywords);
 
-               $handle_parts = explode('@', $author);
-               $nick = $handle_parts[0];
-
                if ($name === '') {
-                       $name = $handle_parts[0];
+                       $name = $author->getUser();
                }
 
                if (preg_match('|^https?://|', $image_url) === 0) {
                        // @TODO No HTTPS here?
-                       $image_url = 'http://' . $handle_parts[1] . $image_url;
+                       $image_url = 'http://' . $author->getFullHost() . $image_url;
                }
 
                Contact::updateAvatar($contact['id'], $image_url);
@@ -2115,7 +2131,7 @@ class Diaspora
 
                $fields = ['name' => $name, 'location' => $location,
                        'name-date' => DateTimeFormat::utcNow(), 'about' => $about,
-                       'addr' => $author, 'nick' => $nick, 'keywords' => $keywords,
+                       'addr' => $author->getAddr(), 'nick' => $author->getUser(), 'keywords' => $keywords,
                        'unsearchable' => !$searchable, 'sensitive' => $nsfw];
 
                if (!empty($birthday)) {
@@ -2158,13 +2174,15 @@ class Diaspora
         */
        private static function receiveContactRequest(array $importer, SimpleXMLElement $data): bool
        {
-               $author = XML::unescape($data->author);
+               $author_handle = XML::unescape($data->author);
                $recipient = XML::unescape($data->recipient);
 
-               if (!$author || !$recipient) {
+               if (!$author_handle || !$recipient) {
                        return false;
                }
 
+               $author = WebFingerUri::fromString($author_handle);
+
                // the current protocol version doesn't know these fields
                // That means that we will assume their existance
                if (isset($data->following)) {
@@ -2222,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) {
@@ -2262,12 +2282,15 @@ class Diaspora
        /**
         * Stores a reshare activity
         *
-        * @param array   $item              Array of reshare post
-        * @param integer $parent_message_id Id of the parent post
-        * @param string  $guid              GUID string of reshare action
-        * @param string  $author            Author handle
+        * @param array        $item              Array of reshare post
+        * @param integer      $parent_message_id Id of the parent post
+        * @param string       $guid              GUID string of reshare action
+        * @param WebFingerUri $author            Author handle
+        * @return false|void
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
-       private static function addReshareActivity(array $item, int $parent_message_id, string $guid, string $author)
+       private static function addReshareActivity(array $item, int $parent_message_id, string $guid, WebFingerUri $author)
        {
                $parent = Post::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]);
 
@@ -2284,7 +2307,7 @@ class Diaspora
                $datarray['owner-id'] = $datarray['author-id'];
 
                $datarray['guid'] = $parent['guid'] . '-' . $guid;
-               $datarray['uri'] = self::getUriFromGuid($author, $datarray['guid']);
+               $datarray['uri'] = self::getUriFromGuid($datarray['guid'], $author);
                $datarray['thr-parent'] = $parent['uri'];
 
                $datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE;
@@ -2329,15 +2352,20 @@ class Diaspora
         */
        private static function receiveReshare(array $importer, SimpleXMLElement $data, string $xml, int $direction): bool
        {
-               $author = XML::unescape($data->author);
+               $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);
 
-               $contact = self::allowedContactByHandle($importer, $author, false);
+               $contact = self::allowedContactByHandle($importer, $author);
                if (!$contact) {
                        return false;
                }
@@ -2351,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;
                }
 
@@ -2369,7 +2398,7 @@ class Diaspora
                $datarray['owner-id'] = $datarray['author-id'];
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                $datarray['verb'] = Activity::POST;
@@ -2380,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;
                }
@@ -2448,19 +2477,18 @@ class Diaspora
         */
        private static function itemRetraction(array $importer, array $contact, SimpleXMLElement $data): bool
        {
-               $author = 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);
-               if (!is_array($person)) {
-                       Logger::notice('Unable to find author detail for ' . $author);
+               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'];
@@ -2488,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;
                        }
 
@@ -2505,14 +2533,14 @@ class Diaspora
        /**
         * Receives retraction messages
         *
-        * @param array  $importer Array of the importer user
-        * @param string $sender   The sender of the message
+        * @param array            $importer Array of the importer user
+        * @param WebFingerUri     $sender   The sender of the message
         * @param SimpleXMLElement $data     The message object
         *
         * @return bool Success
         * @throws \Exception
         */
-       private static function receiveRetraction(array $importer, string $sender, SimpleXMLElement $data)
+       private static function receiveRetraction(array $importer, WebFingerUri $sender, SimpleXMLElement $data)
        {
                $target_type = XML::unescape($data->target_type);
 
@@ -2639,14 +2667,14 @@ class Diaspora
         */
        private static function receiveStatusMessage(array $importer, SimpleXMLElement $data, string $xml, int $direction)
        {
-               $author = XML::unescape($data->author);
+               $author = WebFingerUri::fromString(XML::unescape($data->author));
                $guid = XML::unescape($data->guid);
                $created_at = DateTimeFormat::utc(XML::unescape($data->created_at));
                $public = XML::unescape($data->public);
                $text = XML::unescape($data->text);
                $provider_display_name = XML::unescape($data->provider_display_name);
 
-               $contact = self::allowedContactByHandle($importer, $author, false);
+               $contact = self::allowedContactByHandle($importer, $author);
                if (!$contact) {
                        return false;
                }
@@ -2672,7 +2700,7 @@ class Diaspora
                $datarray = [];
 
                $datarray['guid'] = $guid;
-               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($author, $guid);
+               $datarray['uri'] = $datarray['thr-parent'] = self::getUriFromGuid($guid, $author);
                $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
 
                // Attach embedded pictures to the body
@@ -2954,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)) {
@@ -3029,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)]);
                }
 
@@ -3089,16 +3118,16 @@ class Diaspora
                        $owner = User::getOwnerDataById($item['uid']);
                }
 
-               $author = self::myHandle($owner);
+               $author_handle = self::myHandle($owner);
 
                $message = [
-                       'author' => $author,
+                       'author' => $author_handle,
                        'guid' => System::createUUID(),
                        'parent_type' => 'Post',
                        'parent_guid' => $item['guid']
                ];
 
-               Logger::info('Send participation for ' . $item['guid'] . ' by ' . $author);
+               Logger::info('Send participation for ' . $item['guid'] . ' by ' . $author_handle);
 
                // It doesn't matter what we store, we only want to avoid sending repeated notifications for the same item
                DI::cache()->set($cachekey, $item['guid'], Duration::QUARTER_HOUR);
@@ -4024,6 +4053,8 @@ class Diaspora
         *
         * @param integer $parent_id
         * @return boolean
+        * @throws InternalServerErrorException
+        * @throws \ImagickException
         */
        private static function parentSupportDiaspora(int $parent_id): bool
        {
@@ -4033,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;
+       }
+}
diff --git a/src/Protocol/WebFingerUri.php b/src/Protocol/WebFingerUri.php
new file mode 100644 (file)
index 0000000..b9959f2
--- /dev/null
@@ -0,0 +1,113 @@
+<?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;
+
+use GuzzleHttp\Psr7\Uri;
+
+class WebFingerUri
+{
+       /**
+        * @var string
+        */
+       private $user;
+       /**
+        * @var string
+        */
+       private $host;
+       /**
+        * @var int|null
+        */
+       private $port;
+       /**
+        * @var string|null
+        */
+       private $path;
+
+       private function __construct(string $user, string $host, int $port = null, string $path = null)
+       {
+               $this->user = $user;
+               $this->host = $host;
+               $this->port = $port;
+               $this->path = $path;
+
+               $this->validate();
+       }
+
+       /**
+        * @param string $addr
+        * @return WebFingerUri
+        */
+       public static function fromString(string $addr): WebFingerUri
+       {
+               $uri = new Uri('acct://' . preg_replace('/^acct:/', '', $addr));
+
+               return new self($uri->getUserInfo(), $uri->getHost(), $uri->getPort(), $uri->getPath());
+       }
+
+       private function validate()
+       {
+               if (!$this->user) {
+                       throw new \InvalidArgumentException('WebFinger URI User part is required');
+               }
+
+               if (!$this->host) {
+                       throw new \InvalidArgumentException('WebFinger URI Host part is required');
+               }
+       }
+
+       public function getUser(): string
+       {
+               return $this->user;
+       }
+
+       public function getHost(): string
+       {
+               return $this->host;
+       }
+
+       public function getFullHost(): string
+       {
+               return $this->host
+                       . ($this->port ? ':' . $this->port : '') .
+                       ($this->path ?: '');
+       }
+
+       public function getLongForm(): string
+       {
+               return 'acct:' . $this->getShortForm();
+       }
+
+       public function getShortForm(): string
+       {
+               return $this->user . '@' . $this->getFullHost();
+       }
+
+       public function getAddr(): string
+       {
+               return $this->getShortForm();
+       }
+
+       public function __toString(): string
+       {
+               return $this->getShortForm();
+       }
+}
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`)
diff --git a/src/Worker/UpdateFContact.php b/src/Worker/UpdateFContact.php
deleted file mode 100644 (file)
index 260e071..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-<?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\Worker;
-
-use Friendica\Core\Logger;
-use Friendica\Model\FContact;
-
-class UpdateFContact
-{
-       /**
-        * Update fcontact data via probe
-        *
-        * @param string $handle Contact handle
-        * @return void
-        */
-       public static function execute(string $handle)
-       {
-               $success = FContact::getByURL($handle, true);
-
-               Logger::info('Updated from probe', ['handle' => $handle, 'success' => $success]);
-       }
-}
index f8e01e51eec7e93f139c2b0f5494efbc33be6c4c..d5bbed666a172632e758f75b53b32bb7869c7ab5 100644 (file)
@@ -55,7 +55,7 @@
 use Friendica\Database\DBA;
 
 if (!defined('DB_UPDATE_VERSION')) {
-       define('DB_UPDATE_VERSION', 1497);
+       define('DB_UPDATE_VERSION', 1500);
 }
 
 return [
@@ -637,6 +637,39 @@ return [
                        "wid" => ["wid"],
                ]
        ],
+       "diaspora-contact" => [
+               "comment" => "Diaspora compatible contacts - used in the Diaspora implementation",
+               "fields" => [
+                       "uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the contact URL"],
+                       "addr" => ["type" => "varchar(255)", "comment" => ""],
+                       "alias" => ["type" => "varchar(255)", "comment" => ""],
+                       "nick" => ["type" => "varchar(255)", "comment" => ""],
+                       "name" => ["type" => "varchar(255)", "comment" => ""],
+                       "given-name" => ["type" => "varchar(255)", "comment" => ""],
+                       "family-name" => ["type" => "varchar(255)", "comment" => ""],
+                       "photo" => ["type" => "varchar(255)", "comment" => ""],
+                       "photo-medium" => ["type" => "varchar(255)", "comment" => ""],
+                       "photo-small" => ["type" => "varchar(255)", "comment" => ""],
+                       "batch" => ["type" => "varchar(255)", "comment" => ""],
+                       "notify" => ["type" => "varchar(255)", "comment" => ""],
+                       "poll" => ["type" => "varchar(255)", "comment" => ""],
+                       "subscribe" => ["type" => "varchar(255)", "comment" => ""],
+                       "searchable" => ["type" => "boolean", "comment" => ""],
+                       "pubkey" => ["type" => "text", "comment" => ""],
+                       "gsid" => ["type" => "int unsigned", "foreign" => ["gserver" => "id", "on delete" => "restrict"], "comment" => "Global Server ID"],
+                       "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
+                       "updated" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
+                       "interacting_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of contacts this contact interactes with"],
+                       "interacted_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of contacts that interacted with this contact"],
+                       "post_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of posts and comments"],
+               ],
+               "indexes" => [
+                       "PRIMARY" => ["uri-id"],
+                       "addr" => ["UNIQUE", "addr"],
+                       "alias" => ["alias"],
+                       "gsid" => ["gsid"],
+               ]
+       ],
        "diaspora-interaction" => [
                "comment" => "Signed Diaspora Interaction",
                "fields" => [
@@ -690,39 +723,6 @@ return [
                        "uri-id" => ["uri-id"],
                ]
        ],
-       "fcontact" => [
-               "comment" => "Diaspora compatible contacts - used in the Diaspora implementation",
-               "fields" => [
-                       "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
-                       "guid" => ["type" => "varbinary(255)", "not null" => "1", "default" => "", "comment" => "unique id"],
-                       "url" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the fcontact url"],
-                       "name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
-                       "photo" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "request" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "nick" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
-                       "addr" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
-                       "batch" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "notify" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "poll" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "confirm" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "priority" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
-                       "network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => ""],
-                       "alias" => ["type" => "varbinary(383)", "not null" => "1", "default" => "", "comment" => ""],
-                       "pubkey" => ["type" => "text", "comment" => ""],
-                       "created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
-                       "updated" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
-                       "interacting_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of contacts this contact interactes with"],
-                       "interacted_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of contacts that interacted with this contact"],
-                       "post_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of posts and comments"],
-               ],
-               "indexes" => [
-                       "PRIMARY" => ["id"],
-                       "addr" => ["addr(32)"],
-                       "url" => ["UNIQUE", "url(190)"],
-                       "uri-id" => ["UNIQUE", "uri-id"],
-               ]
-       ],
        "fetch-entry" => [
                "comment" => "",
                "fields" => [
@@ -870,7 +870,7 @@ return [
                "fields" => [
                        "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
                        "uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "foreign" => ["user" => "uid"], "comment" => "User id"],
-                       "fid" => ["type" => "int unsigned", "relation" => ["fcontact" => "id"], "comment" => "deprecated"],
+                       "fid" => ["type" => "int unsigned", "comment" => "deprecated"],
                        "contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => ""],
                        "suggest-cid" => ["type" => "int unsigned", "foreign" => ["contact" => "id"], "comment" => "Suggested contact"],
                        "knowyou" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
index 66f3de3b63f556ee2e3f81a5064b156c70d1ca22..c82e7dc77f68f5a85f3f4a491c438e01c3455e4f 100644 (file)
                        "author-hidden" => ["author", "hidden"],
                        "author-updated" => ["author", "updated"],
                        "author-gsid" => ["author", "gsid"],
-                       "author-uri-id" => ["author", "uri-id"],
                        "owner-id" => ["post-user", "owner-id"],
                        "owner-uri-id" => ["owner", "uri-id"],
                        "owner-link" => ["owner", "url"],
                        "author-hidden" => ["author", "hidden"],
                        "author-updated" => ["author", "updated"],
                        "author-gsid" => ["author", "gsid"],
-                       "author-uri-id" => ["author", "uri-id"],
                        "owner-id" => ["post-thread-user", "owner-id"],
                        "owner-uri-id" => ["owner", "uri-id"],
                        "owner-link" => ["owner", "url"],
                        "author-hidden" => ["author", "hidden"],
                        "author-updated" => ["author", "updated"],
                        "author-gsid" => ["author", "gsid"],
-                       "author-uri-id" => ["author", "uri-id"],
                        "owner-id" => ["post", "owner-id"],
                        "owner-uri-id" => ["owner", "uri-id"],
                        "owner-link" => ["owner", "url"],
                        "author-hidden" => ["author", "hidden"],
                        "author-updated" => ["author", "updated"],
                        "author-gsid" => ["author", "gsid"],
-                       "author-uri-id" => ["author", "uri-id"],
                        "owner-id" => ["post-thread", "owner-id"],
                        "owner-uri-id" => ["owner", "uri-id"],
                        "owner-link" => ["owner", "url"],
                        "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"                      
+                       WHERE `contact`.`uid` = 0"
        ],
        "account-user-view" => [
                "fields" => [
                        "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" => [
                "query" => "FROM `profile_field`
                        INNER JOIN `permissionset` ON `permissionset`.`id` = `profile_field`.`psid`"
        ],
+       "diaspora-contact-view" => [
+               "fields" => [
+                       "uri-id" => ["diaspora-contact", "uri-id"],
+                       "url" => ["item-uri", "uri"],
+                       "guid" => ["item-uri", "guid"],
+                       "addr" => ["diaspora-contact", "addr"],
+                       "alias" => ["diaspora-contact", "alias"],
+                       "nick" => ["diaspora-contact", "nick"],
+                       "name" => ["diaspora-contact", "name"],
+                       "given-name" => ["diaspora-contact", "given-name"],
+                       "family-name" => ["diaspora-contact", "family-name"],
+                       "photo" => ["diaspora-contact", "photo"],
+                       "photo-medium" => ["diaspora-contact", "photo-medium"],
+                       "photo-small" => ["diaspora-contact", "photo-small"],
+                       "batch" => ["diaspora-contact", "batch"],
+                       "notify" => ["diaspora-contact", "notify"],
+                       "poll" => ["diaspora-contact", "poll"],
+                       "subscribe" => ["diaspora-contact", "subscribe"],
+                       "searchable" => ["diaspora-contact", "searchable"],
+                       "pubkey" => ["diaspora-contact", "pubkey"],
+                       "baseurl" => ["gserver", "url"],
+                       "gsid" => ["diaspora-contact", "gsid"],
+                       "created" => ["diaspora-contact", "created"],
+                       "updated" => ["diaspora-contact", "updated"],
+                       "interacting_count" => ["diaspora-contact", "interacting_count"],
+                       "interacted_count" => ["diaspora-contact", "interacted_count"],
+                       "post_count" => ["diaspora-contact", "post_count"],
+               ],
+               "query" => "FROM `diaspora-contact`
+                       INNER JOIN `item-uri` ON `item-uri`.`id` = `diaspora-contact`.`uri-id`
+                       LEFT JOIN `gserver` ON `gserver`.`id` = `diaspora-contact`.`gsid`"
+       ],
 ];
-
diff --git a/tests/src/Protocol/WebFingerUriTest.php b/tests/src/Protocol/WebFingerUriTest.php
new file mode 100644 (file)
index 0000000..7378040
--- /dev/null
@@ -0,0 +1,135 @@
+<?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/>.
+ *
+ * Main database structure configuration file.
+ *
+ * Here are described all the tables, fields and indexes Friendica needs to work.
+ * The entry order is mostly alphabetic - with the exception of tables that are used in foreign keys.
+ *
+ * Syntax (braces indicate optionale values):
+ * "<table name>" => [
+ *    "comment" => "Description of the table",
+ *    "fields" => [
+ *        "<field name>" => [
+ *            "type" => "<field type>{(<field size>)} <unsigned>",
+ *            "not null" => 0|1,
+ *            {"extra" => "auto_increment",}
+ *            {"default" => "<default value>",}
+ *            {"default" => NULL_DATE,} (for datetime fields)
+ *            {"primary" => "1",}
+ *            {"foreign|relation" => ["<foreign key table name>" => "<foreign key field name>"],}
+ *            "comment" => "Description of the fields"
+ *        ],
+ *        ...
+ *    ],
+ *    "indexes" => [
+ *        "PRIMARY" => ["<primary key field name>", ...],
+ *        "<index name>" => [{"UNIQUE",} "<field name>{(<key size>)}", ...]
+ *        ...
+ *    ],
+ * ],
+ *
+ * Whenever possible prefer "foreign" before "relation" with the foreign keys.
+ * "foreign" adds true foreign keys on the database level, while "relation" is just an indicator of a table relation without any consequences
+ *
+ * If you need to make any change, make sure to increment the DB_UPDATE_VERSION constant value below.
+ *
+ */
+
+namespace Friendica\Test\src\Protocol;
+
+use Friendica\Protocol\WebFingerUri;
+use PHPUnit\Framework\TestCase;
+
+class WebFingerUriTest extends TestCase
+{
+       public function dataFromString(): array
+       {
+               return [
+                       'long' => [
+                               'expectedLong'  => 'acct:selma@www.example.com:8080/friend',
+                               'expectedShort' => 'selma@www.example.com:8080/friend',
+                               'input'         => 'acct:selma@www.example.com:8080/friend',
+                       ],
+                       'short' => [
+                               'expectedLong'  => 'acct:selma@www.example.com:8080/friend',
+                               'expectedShort' => 'selma@www.example.com:8080/friend',
+                               'input'         => 'selma@www.example.com:8080/friend',
+                       ],
+                       'minimal' => [
+                               'expectedLong'  => 'acct:bob@example.com',
+                               'expectedShort' => 'bob@example.com',
+                               'input'         => 'bob@example.com',
+                       ],
+                       'acct:' => [
+                               'expectedLong'  => 'acct:alice@example.acct:90',
+                               'expectedShort' => 'alice@example.acct:90',
+                               'input'         => 'alice@example.acct:90',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider dataFromString
+        * @param string $expectedLong
+        * @param string $expectedShort
+        * @param string $input
+        * @return void
+        */
+       public function testFromString(string $expectedLong, string $expectedShort, string $input)
+       {
+               $uri = WebFingerUri::fromString($input);
+
+               $this->assertEquals($expectedLong, $uri->getLongForm());
+               $this->assertEquals($expectedShort, $uri->getShortForm());
+       }
+
+       public function dataFromStringFailure()
+       {
+               return [
+                       'missing user' => [
+                               'input' => 'example.com',
+                       ],
+                       'missing user @' => [
+                               'input' => '@example.com',
+                       ],
+                       'missing host' => [
+                               'input' => 'alice',
+                       ],
+                       'missing host @' => [
+                               'input' => 'alice@',
+                       ],
+                       'missing everything' => [
+                               'input' => '',
+                       ],
+               ];
+       }
+
+       /**
+        * @dataProvider dataFromStringFailure
+        * @param string $input
+        * @return void
+        */
+       public function testFromStringFailure(string $input)
+       {
+               $this->expectException(\InvalidArgumentException::class);
+
+               WebFingerUri::fromString($input);
+       }
+}
index 176747057199e26fff2da3d5f41eed1124e19b24..12a3fb74e61800c094512a046aa93431f6e67ee5 100644 (file)
@@ -974,7 +974,7 @@ function update_1429()
                return Update::FAILED;
        }
 
-       if (!DBA::e("UPDATE `fcontact` SET `uri-id` = null WHERE NOT `uri-id` IS NULL")) {
+       if (DBStructure::existsTable('fcontact') && !DBA::e("UPDATE `fcontact` SET `uri-id` = null WHERE NOT `uri-id` IS NULL")) {
                return Update::FAILED;
        }
 
@@ -1013,6 +1013,10 @@ function update_1438()
 
 function update_1439()
 {
+       if (!DBStructure::existsTable('fcontact')) {
+               return Update::SUCCESS;
+       }
+
        $intros = DBA::select('intro', ['id', 'fid'], ["NOT `fid` IS NULL AND `fid` != ?", 0]);
        while ($intro = DBA::fetch($intros)) {
                $fcontact = DBA::selectFirst('fcontact', ['url'], ['id' => $intro['fid']]);
@@ -1024,6 +1028,8 @@ function update_1439()
                }
        }
        DBA::close($intros);
+
+       return Update::SUCCESS;
 }
 
 function update_1440()