From 79b64cc44f4b3656d9d833a465975d08e96c1095 Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 16 Sep 2022 05:00:06 +0000 Subject: [PATCH] Detect and remove contact duplicates --- database.sql | 16 ++- doc/database.md | 1 + doc/database/db_account-user.md | 32 ++++++ src/Console/MergeContacts.php | 3 +- src/Database/PostUpdate.php | 52 ++++++++- src/Model/Contact.php | 146 ++++++++++++++++++-------- src/Model/Item.php | 10 +- src/Module/Xrd.php | 2 +- src/Protocol/ActivityPub/Delivery.php | 2 +- src/Worker/Contact/Remove.php | 3 +- src/Worker/MergeContact.php | 52 ++++++++- src/Worker/Notifier.php | 2 +- src/Worker/PushSubscription.php | 3 +- src/Worker/RemoveUnusedContacts.php | 2 +- static/dbstructure.config.php | 15 ++- 15 files changed, 281 insertions(+), 60 deletions(-) create mode 100644 doc/database/db_account-user.md diff --git a/database.sql b/database.sql index b76a4e0206..75d31f291e 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.09-rc (Giant Rhubarb) --- DB_UPDATE_VERSION 1482 +-- DB_UPDATE_VERSION 1484 -- ------------------------------------------ @@ -309,6 +309,20 @@ CREATE TABLE IF NOT EXISTS `2fa_trusted_browser` ( FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Two-factor authentication trusted browsers'; +-- +-- TABLE account-user +-- +CREATE TABLE IF NOT EXISTS `account-user` ( + `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', + `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the account url', + `uid` mediumint unsigned NOT NULL COMMENT 'User ID', + PRIMARY KEY(`id`), + UNIQUE INDEX `uri-id_uid` (`uri-id`,`uid`), + INDEX `uid_uri-id` (`uid`,`uri-id`), + FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, + FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE +) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Remote and local accounts'; + -- -- TABLE addon -- diff --git a/doc/database.md b/doc/database.md index 5aed24e91a..1ffbf91f5d 100644 --- a/doc/database.md +++ b/doc/database.md @@ -8,6 +8,7 @@ Database Tables | [2fa_app_specific_password](help/database/db_2fa_app_specific_password) | Two-factor app-specific _password | | [2fa_recovery_codes](help/database/db_2fa_recovery_codes) | Two-factor authentication recovery codes | | [2fa_trusted_browser](help/database/db_2fa_trusted_browser) | Two-factor authentication trusted browsers | +| [account-user](help/database/db_account-user) | Remote and local accounts | | [addon](help/database/db_addon) | registered addons | | [apcontact](help/database/db_apcontact) | ActivityPub compatible contacts - used in the ActivityPub implementation | | [application](help/database/db_application) | OAuth application | diff --git a/doc/database/db_account-user.md b/doc/database/db_account-user.md new file mode 100644 index 0000000000..1c3c40172c --- /dev/null +++ b/doc/database/db_account-user.md @@ -0,0 +1,32 @@ +Table account-user +=========== + +Remote and local accounts + +Fields +------ + +| Field | Description | Type | Null | Key | Default | Extra | +| ------ | ------------------------------------------------------------ | ------------------ | ---- | --- | ------- | -------------- | +| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment | +| uri-id | Id of the item-uri table entry that contains the account url | int unsigned | NO | | NULL | | +| uid | User ID | mediumint unsigned | NO | | NULL | | + +Indexes +------------ + +| Name | Fields | +| ---------- | ------------------- | +| PRIMARY | id | +| uri-id_uid | UNIQUE, uri-id, uid | +| uid_uri-id | uid, uri-id | + +Foreign Keys +------------ + +| Field | Target Table | Target Field | +|-------|--------------|--------------| +| uri-id | [item-uri](help/database/db_item-uri) | id | +| uid | [user](help/database/db_user) | uid | + +Return to [database documentation](help/database) diff --git a/src/Console/MergeContacts.php b/src/Console/MergeContacts.php index 405622cc4b..e230499d2d 100644 --- a/src/Console/MergeContacts.php +++ b/src/Console/MergeContacts.php @@ -23,6 +23,7 @@ namespace Friendica\Console; use Friendica\Core\L10n; use Friendica\Database\Database; +use Friendica\Model\Contact; /** * tool to find and merge duplicated contact entries. @@ -137,7 +138,7 @@ HELP; $this->updateTable('post-thread-user', 'contact-id', $from, $to, false); $this->updateTable('user-contact', 'cid', $from, $to, true); - if (!$this->dba->delete('contact', ['id' => $from])) { + if (!Contact::deleteById($from)) { $this->err($this->l10n->t('Deletion of id %d failed', $from)); } else { $this->out($this->l10n->t('Deletion of id %d was successful', $from)); diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index 9216d800da..82fe70e3f1 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -50,7 +50,7 @@ class PostUpdate // Needed for the helper function to read from the legacy term table const OBJECT_TYPE_POST = 1; - const VERSION = 1452; + const VERSION = 1484; /** * Calls the post update functions @@ -114,6 +114,9 @@ class PostUpdate if (!self::update1483()) { return false; } + if (!self::update1484()) { + return false; + } return true; } @@ -1120,4 +1123,51 @@ class PostUpdate Logger::info('Done'); return true; } + + /** + * Handle duplicate contact entries + * + * @return bool "true" when the job is done + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \ImagickException + */ + private static function update1484() + { + // Was the script completed? + if (DI::config()->get('system', 'post_update_version') >= 1484) { + return true; + } + + $id = DI::config()->get('system', 'post_update_version_1484_id', 0); + + Logger::info('Start', ['id' => $id]); + + $rows = 0; + + $contacts = DBA::select('contact', ['id', 'uid', 'uri-id', 'url'], ["`id` > ?", $id], ['order' => ['id'], 'limit' => 1000]); + + if (DBA::errorNo() != 0) { + Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]); + return false; + } + + while ($contact = DBA::fetch($contacts)) { + $id = $contact['id']; + Contact::setAccountUser($contact['id'], $contact['uid'], $contact['uri-id'], $contact['url']); + ++$rows; + } + DBA::close($contacts); + + DI::config()->set('system', 'post_update_version_1484_id', $id); + + Logger::info('Processed', ['rows' => $rows, 'last' => $id]); + + if ($rows <= 100) { + DI::config()->set('system', 'post_update_version', 1484); + Logger::info('Done'); + return true; + } + + return false; + } } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 6a57693991..a4c4c0a140 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -169,16 +169,41 @@ class Contact return 0; } - Contact\User::insertForContactArray($contact); - - // Search for duplicated contacts and get rid of them - if (!$contact['self']) { - self::removeDuplicates($contact['nurl'], $contact['uid']); + $fields = DI::dbaDefinition()->truncateFieldsForTable('account-user', $contact); + DBA::insert('account-user', $fields, Database::INSERT_IGNORE); + $account_user = DBA::selectFirst('account-user', ['id'], ['uid' => $contact['uid'], 'uri-id' => $contact['uri-id']]); + if (empty($account_user['id'])) { + Logger::warning('Account-user entry not found', ['cid' => $contact['id'], 'uid' => $contact['uid'], 'uri-id' => $contact['uri-id'], 'url' => $contact['url']]); + } elseif ($account_user['id'] != $contact['id']) { + $duplicate = DBA::selectFirst('contact', [], ['id' => $account_user['id'], 'deleted' => false]); + if (!empty($duplicate['id'])) { + $ret = Contact::deleteById($contact['id']); + Logger::notice('Deleted duplicated contact', ['ret' => $ret, 'account-user' => $account_user, 'cid' => $duplicate['id'], 'uid' => $duplicate['uid'], 'uri-id' => $duplicate['uri-id'], 'url' => $duplicate['url']]); + $contact = $duplicate; + } else { + $ret = DBA::update('account-user', ['id' => $contact['id']], ['uid' => $contact['uid'], 'uri-id' => $contact['uri-id']]); + Logger::notice('Updated account-user', ['ret' => $ret, 'account-user' => $account_user, 'cid' => $contact['id'], 'uid' => $contact['uid'], 'uri-id' => $contact['uri-id'], 'url' => $contact['url']]); + } } + Contact\User::insertForContactArray($contact); + return $contact['id']; } + /** + * Delete contact by id + * + * @param integer $id + * @return boolean + */ + public static function deleteById(int $id): bool + { + Logger::debug('Delete contact', ['id' => $id]); + DBA::delete('account-user', ['id' => $id]); + return DBA::delete('contact', ['id' => $id]); + } + /** * Updates rows in the contact table * @@ -825,6 +850,8 @@ class Contact return; } + DBA::delete('account-user', ['id' => $id]); + self::clearFollowerFollowingEndpointCache($contact['uid']); // Archive the contact @@ -1253,6 +1280,11 @@ class Contact return 0; } + if (!$contact_id && !empty($data['account-type']) && $data['account-type'] == User::ACCOUNT_TYPE_DELETED) { + Logger::info('Contact is a tombstone. It will not be inserted', ['url' => $url, 'uid' => $uid]); + return 0; + } + if (!$contact_id) { $urls = [Strings::normaliseLink($url), Strings::normaliseLink($data['url'])]; if (!empty($data['alias'])) { @@ -1282,20 +1314,15 @@ class Contact $condition = ['nurl' => Strings::normaliseLink($data["url"]), 'uid' => $uid, 'deleted' => false]; // Before inserting we do check if the entry does exist now. - if (DI::lock()->acquire(self::LOCK_INSERT, 0)) { - $contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]); - if (DBA::isResult($contact)) { - $contact_id = $contact['id']; - Logger::notice('Contact had been created (shortly) before', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]); - } else { - $contact_id = self::insert($fields); - if ($contact_id) { - Logger::info('Contact inserted', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]); - } - } - DI::lock()->release(self::LOCK_INSERT); + $contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]); + if (DBA::isResult($contact)) { + $contact_id = $contact['id']; + Logger::notice('Contact had been created (shortly) before', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]); } else { - Logger::warning('Contact lock had not been acquired'); + $contact_id = self::insert($fields); + if ($contact_id) { + Logger::info('Contact inserted', ['id' => $contact_id, 'url' => $url, 'uid' => $uid]); + } } if (!$contact_id) { @@ -2219,25 +2246,22 @@ class Contact /** * Helper function for "updateFromProbe". Updates personal and public contact * - * @param integer $id contact id - * @param integer $uid user id - * @param string $old_url The previous profile URL of the contact - * @param string $new_url The profile URL of the contact - * @param array $fields The fields that are updated + * @param integer $id contact id + * @param integer $uid user id + * @param integer $uri_id Uri-Id + * @param string $url The profile URL of the contact + * @param array $fields The fields that are updated * * @throws \Exception */ - private static function updateContact(int $id, int $uid, string $old_url, string $new_url, array $fields) + private static function updateContact(int $id, int $uid, int $uri_id, string $url, array $fields) { if (!self::update($fields, ['id' => $id])) { Logger::info('Couldn\'t update contact.', ['id' => $id, 'fields' => $fields]); return; } - // Search for duplicated contacts and get rid of them - if (self::removeDuplicates(Strings::normaliseLink($new_url), $uid)) { - return; - } + self::setAccountUser($id, $uid, $uri_id, $url); // Archive or unarchive the contact. $contact = DBA::selectFirst('contact', [], ['id' => $id]); @@ -2259,7 +2283,7 @@ class Contact } // Update contact data for all users - $condition = ['self' => false, 'nurl' => Strings::normaliseLink($old_url)]; + $condition = ['self' => false, 'nurl' => Strings::normaliseLink($url)]; $condition['network'] = [Protocol::DFRN, Protocol::DIASPORA, Protocol::ACTIVITYPUB]; self::update($fields, $condition); @@ -2281,6 +2305,51 @@ class Contact self::update($fields, $condition); } + /** + * Create or update an "account-user" entry + * + * @param integer $id + * @param integer $uid + * @param integer $uri_id + * @param string $url + * @return void + */ + public static function setAccountUser(int $id, int $uid, int $uri_id, string $url) + { + if (empty($uri_id)) { + return; + } + + $account_user = DBA::selectFirst('account-user', ['id', 'uid', 'uri-id'], ['id' => $id]); + if (!empty($account_user['uri-id']) && ($account_user['uri-id'] != $uri_id)) { + if ($account_user['uid'] == $uid) { + $ret = DBA::update('account-user', ['uri-id' => $uri_id], ['id' => $id]); + Logger::notice('Updated account-user uri-id', ['ret' => $ret, 'account-user' => $account_user, 'cid' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + } else { + // This should never happen + Logger::warning('account-user exists for a different uri-id and uid', ['account_user' => $account_user, 'id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + } + } + + $account_user = DBA::selectFirst('account-user', ['id', 'uid', 'uri-id'], ['uid' => $uid, 'uri-id' => $uri_id]); + if (!empty($account_user['id'])) { + if ($account_user['id'] == $id) { + Logger::debug('account-user already exists', ['id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + return; + } elseif (!DBA::exists('contact', ['id' => $account_user['id'], 'deleted' => false])) { + $ret = DBA::update('account-user', ['id' => $id], ['uid' => $uid, 'uri-id' => $uri_id]); + Logger::notice('Updated account-user', ['ret' => $ret, 'account-user' => $account_user, 'cid' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + return; + } + Logger::warning('account-user exists for a different contact id', ['account_user' => $account_user, 'id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + Worker::add(PRIORITY_HIGH, 'MergeContact', $account_user['id'], $id, $uid); + } elseif (DBA::insert('account-user', ['id' => $id, 'uri-id' => $uri_id, 'uid' => $uid], Database::INSERT_IGNORE)) { + Logger::notice('account-user was added', ['id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + } else { + Logger::warning('account-user was not added', ['id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]); + } + } + /** * Remove duplicated contacts * @@ -2305,11 +2374,6 @@ class Contact $first = $first_contact['id']; Logger::info('Found duplicates', ['count' => $count, 'first' => $first, 'uid' => $uid, 'nurl' => $nurl]); - if (($uid != 0 && ($first_contact['network'] == Protocol::DFRN))) { - // Don't handle non public DFRN duplicates by now (legacy DFRN is very special because of the key handling) - Logger::info('Not handling non public DFRN duplicate', ['uid' => $uid, 'nurl' => $nurl]); - return false; - } // Find all duplicates $condition = ["`nurl` = ? AND `uid` = ? AND `id` != ? AND NOT `self` AND NOT `deleted`", $nurl, $uid, $first]; @@ -2474,7 +2538,7 @@ class Contact if (Strings::normaliseLink($contact['url']) != Strings::normaliseLink($ret['url'])) { Logger::notice('New URL differs from old URL', ['id' => $id, 'uid' => $uid, 'old' => $contact['url'], 'new' => $ret['url']]); - self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]); + self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]); return false; } @@ -2482,14 +2546,14 @@ class Contact // We check after the probing to be able to correct falsely detected contact types. if (($contact['contact-type'] == self::TYPE_RELAY) && (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))) { - self::updateContact($id, $uid, $contact['url'], $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]); + self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]); Logger::info('Not updating relais', ['id' => $id, 'url' => $contact['url']]); return true; } // If Probe::uri fails the network code will be different ("feed" or "unkn") if (($ret['network'] == Protocol::PHANTOM) || (($ret['network'] == Protocol::FEED) && ($ret['network'] != $contact['network']))) { - self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]); + self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => true, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $failed_next_update, 'failure_update' => $updated]); return false; } @@ -2558,10 +2622,8 @@ class Contact self::updateAvatar($id, $ret['photo'], $update); } - $uriid = ItemURI::insert(['uri' => $ret['url'], 'guid' => $guid]); - if (!$update) { - self::updateContact($id, $uid, $contact['url'], $ret['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]); + self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]); if (Contact\Relation::isDiscoverable($ret['url'])) { Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']); @@ -2578,7 +2640,7 @@ class Contact return true; } - $ret['uri-id'] = $uriid; + $ret['uri-id'] = ItemURI::insert(['uri' => $ret['url'], 'guid' => $guid]); $ret['nurl'] = Strings::normaliseLink($ret['url']); $ret['updated'] = $updated; $ret['failed'] = false; @@ -2605,7 +2667,7 @@ class Contact unset($ret['photo']); - self::updateContact($id, $uid, $contact['url'], $ret['url'], $ret); + self::updateContact($id, $uid, $ret['uri-id'], $ret['url'], $ret); if (Contact\Relation::isDiscoverable($ret['url'])) { Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']); diff --git a/src/Model/Item.php b/src/Model/Item.php index 79fe4c02ee..2ce582c322 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2472,16 +2472,16 @@ class Item return; } - $expire_items = DI::pConfig()->get($uid, 'expire', 'items', true); + $expire_items = (bool)DI::pConfig()->get($uid, 'expire', 'items', true); // Forcing expiring of items - but not notes and marked items if ($force) { $expire_items = true; } - $expire_notes = DI::pConfig()->get($uid, 'expire', 'notes', true); - $expire_starred = DI::pConfig()->get($uid, 'expire', 'starred', true); - $expire_photos = DI::pConfig()->get($uid, 'expire', 'photos', false); + $expire_notes = (bool)DI::pConfig()->get($uid, 'expire', 'notes', true); + $expire_starred = (bool)DI::pConfig()->get($uid, 'expire', 'starred', true); + $expire_photos = (bool)DI::pConfig()->get($uid, 'expire', 'photos', false); $expired = 0; @@ -2510,7 +2510,7 @@ class Item ++$expired; } DBA::close($items); - Logger::notice('User ' . $uid . ": expired $expired items; expire items: $expire_items, expire notes: $expire_notes, expire starred: $expire_starred, expire photos: $expire_photos"); + Logger::notice('Expired', ['user' => $uid, 'days' => $days, 'network' => $network, 'force' => $force, 'expired' => $expired, 'expire items' => $expire_items, 'expire notes' => $expire_notes, 'expire starred' => $expire_starred, 'expire photos' => $expire_photos, 'condition' => $condition]); } public static function firstPostDate(int $uid, bool $wall = false) diff --git a/src/Module/Xrd.php b/src/Module/Xrd.php index b12e94a290..098c1574ea 100644 --- a/src/Module/Xrd.php +++ b/src/Module/Xrd.php @@ -83,7 +83,7 @@ class Xrd extends BaseModule } else { $owner = User::getOwnerDataByNick($name); if (empty($owner)) { - DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name]); + DI::logger()->notice('No owner data for user id', ['uri' => $uri, 'name' => $name]); throw new NotFoundException('Owner was not found for user->uid=' . $name); } diff --git a/src/Protocol/ActivityPub/Delivery.php b/src/Protocol/ActivityPub/Delivery.php index 9aa45d0419..f66febb82d 100644 --- a/src/Protocol/ActivityPub/Delivery.php +++ b/src/Protocol/ActivityPub/Delivery.php @@ -84,7 +84,7 @@ class Delivery if (empty($item['id'])) { Logger::warning('Item not found, removing delivery', ['uri-id' => $uri_id, 'uid' => $uid, 'cmd' => $cmd, 'inbox' => $inbox]); Post\Delivery::remove($uri_id, $inbox); - return true; + return ['success' => true, 'serverfailure' => false, 'drop' => false]; } else { $item_id = $item['id']; } diff --git a/src/Worker/Contact/Remove.php b/src/Worker/Contact/Remove.php index fbede2cdaf..a3d26aa69e 100644 --- a/src/Worker/Contact/Remove.php +++ b/src/Worker/Contact/Remove.php @@ -23,6 +23,7 @@ namespace Friendica\Worker\Contact; use Friendica\Core\Logger; use Friendica\Database\DBA; +use Friendica\Model\Contact; /** * Removes a contact and all its related content @@ -41,7 +42,7 @@ class Remove extends RemoveContent return false; } - $ret = DBA::delete('contact', ['id' => $id]); + $ret = Contact::deleteById($id); Logger::info('Deleted contact', ['id' => $id, 'result' => $ret]); return true; diff --git a/src/Worker/MergeContact.php b/src/Worker/MergeContact.php index e1383f2110..15fedd4b86 100644 --- a/src/Worker/MergeContact.php +++ b/src/Worker/MergeContact.php @@ -24,6 +24,7 @@ namespace Friendica\Worker; use Friendica\Core\Logger; use Friendica\Database\DBA; use Friendica\Database\DBStructure; +use Friendica\Model\Contact; class MergeContact { @@ -68,10 +69,57 @@ class MergeContact DBA::update('thread', ['owner-id' => $new_cid], ['owner-id' => $old_cid]); } } else { - /// @todo Check if some other data needs to be adjusted as well, possibly the "rel" status? + self::mergePersonalContacts($new_cid, $old_cid); } // Remove the duplicate - DBA::delete('contact', ['id' => $old_cid]); + Contact::deleteById($old_cid); + } + + /** + * Merge important fields between two contacts + * + * @param integer $first + * @param integer $duplicate + * @return void + */ + private static function mergePersonalContacts(int $first, int $duplicate) + { + $fields = ['self', 'remote_self', 'rel', 'prvkey', 'subhub', 'hub-verify', 'priority', 'writable', 'archive', 'pending', + 'rating', 'notify_new_posts', 'fetch_further_information', 'ffi_keyword_denylist', 'block_reason']; + $c1 = Contact::getById($first, $fields); + $c2 = Contact::getById($duplicate, $fields); + + $ctarget = $c1; + + if ($c1['self'] || $c2['self']) { + return; + } + + $ctarget['rel'] = $c1['rel'] | $c2['rel']; + foreach (['prvkey', 'hub-verify', 'priority', 'rating', 'fetch_further_information', 'ffi_keyword_denylist', 'block_reason'] as $field) { + $ctarget[$field] = $c1[$field] ?: $c2[$field]; + } + + foreach (['remote_self', 'subhub', 'writable', 'notify_new_posts'] as $field) { + $ctarget[$field] = $c1[$field] || $c2[$field]; + } + + foreach (['archive', 'pending'] as $field) { + $ctarget[$field] = $c1[$field] && $c2[$field]; + } + + $data = []; + + foreach ($fields as $field) { + if ($ctarget[$field] != $c1[$field]) { + $data[$field] = $ctarget[$field]; + } + } + + if (empty($data)) { + return; + } + Contact::update($data, ['id' => $first]); } } diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index 7921097d4a..76eb260a08 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -502,7 +502,7 @@ class Notifier $a = DI::app(); $delivery_queue_count = 0; - if ($target_item['verb'] == Activity::ANNOUNCE) { + if (!empty($target_item['verb']) && ($target_item['verb'] == Activity::ANNOUNCE)) { Logger::notice('Announces are only delivery via ActivityPub', ['cmd' => $cmd, 'id' => $target_item['id'], 'guid' => $target_item['guid'], 'uri-id' => $target_item['uri-id'], 'uri' => $target_item['uri']]); return 0; } diff --git a/src/Worker/PushSubscription.php b/src/Worker/PushSubscription.php index 9b22ff4841..e6876ad3eb 100644 --- a/src/Worker/PushSubscription.php +++ b/src/Worker/PushSubscription.php @@ -31,7 +31,6 @@ use Friendica\Model\Contact; use Friendica\Model\Post; use Friendica\Model\Subscription as ModelSubscription; use Friendica\Model\User; -use Friendica\Navigation\Notifications; use Friendica\Network\HTTPException\NotFoundException; use Minishlink\WebPush\WebPush; use Minishlink\WebPush\Subscription; @@ -91,7 +90,7 @@ class PushSubscription } $message = DI::notificationFactory()->getMessageFromNotification($notification); - $title = $message['plain'] ?: ''; + $title = $message['plain'] ?? ''; $push = Subscription::create([ 'contentEncoding' => 'aesgcm', diff --git a/src/Worker/RemoveUnusedContacts.php b/src/Worker/RemoveUnusedContacts.php index 6a41996343..038376c22c 100644 --- a/src/Worker/RemoveUnusedContacts.php +++ b/src/Worker/RemoveUnusedContacts.php @@ -78,7 +78,7 @@ class RemoveUnusedContacts DBA::delete('post-thread-user', ['author-id' => $contact['id']]); DBA::delete('post-thread-user', ['causer-id' => $contact['id']]); - DBA::delete('contact', ['id' => $contact['id']]); + Contact::deleteById($contact['id']); if ((++$count % 1000) == 0) { Logger::info('In removal', ['count' => $count, 'total' => $total]); } diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 9f1ab15858..1f1982c989 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1482); + define('DB_UPDATE_VERSION', 1484); } return [ @@ -371,6 +371,19 @@ return [ "uid" => ["uid"], ] ], + "account-user" => [ + "comment" => "Remote and local accounts", + "fields" => [ + "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"], + "uri-id" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the account url"], + "uid" => ["type" => "mediumint unsigned", "not null" => "1", "foreign" => ["user" => "uid"], "comment" => "User ID"], + ], + "indexes" => [ + "PRIMARY" => ["id"], + "uri-id_uid" => ["UNIQUE", "uri-id", "uid"], + "uid_uri-id" => ["uid", "uri-id"], + ] + ], "addon" => [ "comment" => "registered addons", "fields" => [