diff --git a/database.sql b/database.sql index 49f282db0a..7285d7fa1e 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.12-rc (Giant Rhubarb) --- DB_UPDATE_VERSION 1501 +-- DB_UPDATE_VERSION 1502 -- ------------------------------------------ @@ -1439,7 +1439,7 @@ CREATE TABLE IF NOT EXISTS `post-user` ( `event-id` int unsigned COMMENT 'Used to link to the event.id', `unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen', `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user', - `notification-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', + `notification-type` smallint unsigned NOT NULL DEFAULT 0 COMMENT '', `wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid', `origin` boolean NOT NULL DEFAULT '0' COMMENT 'item originated at this site', `psid` int unsigned COMMENT 'ID of the permission set of this post', diff --git a/doc/database/db_post-user.md b/doc/database/db_post-user.md index ce213941da..2fe91d144b 100644 --- a/doc/database/db_post-user.md +++ b/doc/database/db_post-user.md @@ -34,7 +34,7 @@ Fields | event-id | Used to link to the event.id | int unsigned | YES | | NULL | | | unseen | post has not been seen | boolean | NO | | 1 | | | hidden | Marker to hide the post from the user | boolean | NO | | 0 | | -| notification-type | | tinyint unsigned | NO | | 0 | | +| notification-type | | smallint unsigned | NO | | 0 | | | wall | This item was posted to the wall of uid | boolean | NO | | 0 | | | origin | item originated at this site | boolean | NO | | 0 | | | psid | ID of the permission set of this post | int unsigned | YES | | NULL | | diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 6d19eb1635..f8885b5b07 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -985,7 +985,8 @@ class Conversation $thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params); - $items = []; + $items = []; + $quoteuriids = []; while ($row = Post::fetch($thread_items)) { if (!empty($items[$row['uri-id']]) && ($row['uid'] == 0)) { @@ -1005,11 +1006,37 @@ class Conversation } } + if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT])) { + $quoteuriids[$row['uri-id']] = [ + 'uri-id' => $row['uri-id'], + 'uri' => $row['uri'], + 'parent-uri-id' => $row['parent-uri-id'], + 'parent-uri' => $row['parent-uri'], + ]; + } + $items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []); } DBA::close($thread_items); + $quotes = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), ['quote-uri-id' => array_column($quoteuriids, 'uri-id'), 'uid' => 0]); + while ($quote = Post::fetch($quotes)) { + $row = $quote; + + $row['uid'] = $uid; + $row['verb'] = $row['body'] = $row['raw-body'] = Activity::ANNOUNCE; + $row['gravity'] = ItemModel::GRAVITY_ACTIVITY; + $row['object-type'] = Activity\ObjectType::NOTE; + $row['parent-uri'] = $quoteuriids[$quote['quote-uri-id']]['parent-uri']; + $row['parent-uri-id'] = $quoteuriids[$quote['quote-uri-id']]['parent-uri-id']; + $row['thr-parent'] = $quoteuriids[$quote['quote-uri-id']]['uri']; + $row['thr-parent-id'] = $quoteuriids[$quote['quote-uri-id']]['uri-id']; + + $items[$row['uri-id']] = $this->addRowInformation($row, [], []); + } + DBA::close($quotes); + $items = $this->convSort($items, $order); $this->profiler->stopRecording(); diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 18701bb661..698c127f72 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -115,13 +115,17 @@ class Status extends BaseFactory 'gravity' => Item::GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE), 'deleted' => false - ], []); + ]) + Post::countPosts([ + 'quote-uri-id' => $uriId, + 'deleted' => false + ]); + $count_like = Post::countPosts([ 'thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE), 'deleted' => false - ], []); + ]); $counts = new \Friendica\Object\Api\Mastodon\Status\Counts( Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false], []), @@ -144,6 +148,11 @@ class Status extends BaseFactory 'gravity' => Item::GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE), 'deleted' => false + ]) || Post::exists([ + 'quote-uri-id' => $uriId, + 'uid' => $uid, + 'origin' => true, + 'deleted' => false ]); $userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes( $origin_like, diff --git a/src/Model/Post/UserNotification.php b/src/Model/Post/UserNotification.php index d872d5c41c..44a7dd9000 100644 --- a/src/Model/Post/UserNotification.php +++ b/src/Model/Post/UserNotification.php @@ -27,7 +27,6 @@ use Friendica\Core\Hook; use Friendica\Core\Logger; use Friendica\Database\Database; use Friendica\Database\DBA; -use Friendica\Database\DBStructure; use Friendica\DI; use Friendica\Model\Contact; use Friendica\Model\Item; @@ -52,6 +51,7 @@ class UserNotification const TYPE_DIRECT_THREAD_COMMENT = 64; const TYPE_SHARED = 128; const TYPE_FOLLOW = 256; + const TYPE_QUOTED = 512; /** * Insert a new user notification entry @@ -273,6 +273,14 @@ class UserNotification } } + if (($item['verb'] != Activity::ANNOUNCE) && self::checkQuoted($item, $contacts)) { + $notification_type = $notification_type | self::TYPE_QUOTED; + if (!$notified) { + self::insertNotificationByItem(self::TYPE_QUOTED, $uid, $item); + $notified = true; + } + } + if (($item['verb'] != Activity::ANNOUNCE) && self::checkFollowParticipation($item, $contacts)) { $notification_type = $notification_type | self::TYPE_FOLLOW; if (!$notified) { @@ -581,4 +589,23 @@ class UserNotification $condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY]; return Post::exists($condition); } + + /** + * Check for a quoted post of a post of the given user + * + * @param array $item + * @param array $contacts Array of contact IDs + * @return bool The item is a quoted post of a user's post or comment + * @throws Exception + */ + private static function checkQuoted(array $item, array $contacts): bool + { + if (empty($item['quote-uri-id'])) { + return false; + } + $condition = ['uri-id' => $item['quote-uri-id'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => [item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]]; + return Post::exists($condition); + } + + } diff --git a/src/Module/Api/Mastodon/Statuses/Reblog.php b/src/Module/Api/Mastodon/Statuses/Reblog.php index 65b641ab89..bf4a739466 100644 --- a/src/Module/Api/Mastodon/Statuses/Reblog.php +++ b/src/Module/Api/Mastodon/Statuses/Reblog.php @@ -29,6 +29,7 @@ use Friendica\DI; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Module\BaseApi; +use Friendica\Protocol\Diaspora; /** * @see https://docs.joinmastodon.org/methods/statuses/ @@ -49,12 +50,14 @@ class Reblog extends BaseApi DI::mstdnError()->RecordNotFound(); } - if (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { + if ($item['network'] == Protocol::DIASPORA) { + Diaspora::performReshare($this->parameters['id'], $uid); + } elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network']))); + } else { + Item::performActivity($item['id'], 'announce', $uid); } - Item::performActivity($item['id'], 'announce', $uid); - System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unreblog.php b/src/Module/Api/Mastodon/Statuses/Unreblog.php index a6741c6de2..faacefe38d 100644 --- a/src/Module/Api/Mastodon/Statuses/Unreblog.php +++ b/src/Module/Api/Mastodon/Statuses/Unreblog.php @@ -49,12 +49,21 @@ class Unreblog extends BaseApi DI::mstdnError()->RecordNotFound(); } - if (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { + if ($item['network'] == Protocol::DIASPORA) { + $item = Post::selectFirstForUser($uid, ['id'], ['quote-uri-id' => $this->parameters['id'], 'origin' => true, 'uid' => $uid]); + if (empty($item['id'])) { + DI::mstdnError()->RecordNotFound(); + } + + if (!Item::markForDeletionById($item['id'])) { + DI::mstdnError()->RecordNotFound(); + } + } elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) { DI::mstdnError()->UnprocessableEntity(DI::l10n()->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network']))); + } else { + Item::performActivity($item['id'], 'unannounce', $uid); } - Item::performActivity($item['id'], 'unannounce', $uid); - System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); } } diff --git a/src/Module/Item/Activity.php b/src/Module/Item/Activity.php index 3fa6d38fc9..c52882efb8 100644 --- a/src/Module/Item/Activity.php +++ b/src/Module/Item/Activity.php @@ -46,17 +46,26 @@ class Activity extends BaseModule throw new HTTPException\BadRequestException(); } - $verb = $this->parameters['verb']; - $itemId = $this->parameters['id']; + $verb = $this->parameters['verb']; + $itemId = $this->parameters['id']; + $handled = false; if (in_array($verb, ['announce', 'unannounce'])) { $item = Post::selectFirst(['network', 'uri-id'], ['id' => $itemId, 'uid' => [DI::userSession()->getLocalUserId(), 0]]); if ($item['network'] == Protocol::DIASPORA) { - Diaspora::performReshare($item['uri-id'], DI::userSession()->getLocalUserId()); + $quote = Post::selectFirst(['id'], ['quote-uri-id' => $item['uri-id'], 'origin' => true, 'uid' => DI::userSession()->getLocalUserId()]); + if (!empty($quote['id'])) { + if (!Item::markForDeletionById($quote['id'])) { + throw new HTTPException\BadRequestException(); + } + } else { + Diaspora::performReshare($item['uri-id'], DI::userSession()->getLocalUserId()); + } + $handled = true; } } - if (!Item::performActivity($itemId, $verb, DI::userSession()->getLocalUserId())) { + if (!$handled && !Item::performActivity($itemId, $verb, DI::userSession()->getLocalUserId())) { throw new HTTPException\BadRequestException(); } diff --git a/src/Navigation/Notifications/Factory/Notification.php b/src/Navigation/Notifications/Factory/Notification.php index 7f6988b3bf..86d462e0b2 100644 --- a/src/Navigation/Notifications/Factory/Notification.php +++ b/src/Navigation/Notifications/Factory/Notification.php @@ -311,7 +311,11 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow $msg = $l10n->t('%1$s shared a post'); } break; - } + + case Post\UserNotification::TYPE_QUOTED: + $msg = $l10n->t('%1$s shared your post %2$s'); + break; + } break; } } diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index fe5b27d334..8e2dc56d9e 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -2285,65 +2285,6 @@ class Diaspora return true; } - /** - * 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 WebFingerUri $author Author handle - * @return false|void - * @throws InternalServerErrorException - * @throws \ImagickException - */ - private static function addReshareActivity(array $item, int $parent_message_id, string $guid, WebFingerUri $author) - { - $parent = Post::selectFirst(['uri', 'guid'], ['id' => $parent_message_id]); - - $datarray = []; - - $datarray['uid'] = $item['uid']; - $datarray['contact-id'] = $item['contact-id']; - $datarray['network'] = $item['network']; - - $datarray['author-link'] = $item['author-link']; - $datarray['author-id'] = $item['author-id']; - - $datarray['owner-link'] = $datarray['author-link']; - $datarray['owner-id'] = $datarray['author-id']; - - $datarray['guid'] = $parent['guid'] . '-' . $guid; - $datarray['uri'] = self::getUriFromGuid($datarray['guid'], $author); - $datarray['thr-parent'] = $parent['uri']; - - $datarray['verb'] = $datarray['body'] = Activity::ANNOUNCE; - $datarray['gravity'] = Item::GRAVITY_ACTIVITY; - $datarray['object-type'] = Activity\ObjectType::NOTE; - - $datarray['protocol'] = $item['protocol']; - $datarray['source'] = $item['source']; - $datarray['direction'] = $item['direction']; - $datarray['post-reason'] = $item['post-reason']; - - $datarray['plink'] = self::plink($author, $datarray['guid']); - $datarray['private'] = $item['private']; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = $item['created']; - - if (Item::isTooOld($datarray)) { - Logger::info('Reshare activity is too old', ['created' => $datarray['created'], 'uid' => $datarray['uid'], 'guid' => $datarray['guid']]); - return false; - } - - $message_id = Item::insert($datarray); - - if ($message_id) { - Logger::info('Stored reshare activity.', ['guid' => $guid, 'id' => $message_id]); - if ($datarray['uid'] == 0) { - Item::distribute($message_id); - } - } - } - /** * Processes a reshare message * @@ -2436,11 +2377,6 @@ class Diaspora self::sendParticipation($contact, $datarray); - $root_message_id = self::messageExists($importer['uid'], $root_guid); - if ($root_message_id) { - self::addReshareActivity($datarray, $root_message_id, $guid, $author); - } - if ($message_id) { Logger::info('Stored reshare ' . $datarray['guid'] . ' with message id ' . $message_id); if ($datarray['uid'] == 0) { diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index d9ff07178b..4a340d1f7b 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', 1501); + define('DB_UPDATE_VERSION', 1502); } return [ @@ -1458,7 +1458,7 @@ return [ "event-id" => ["type" => "int unsigned", "foreign" => ["event" => "id"], "comment" => "Used to link to the event.id"], "unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "post has not been seen"], "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide the post from the user"], - "notification-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], + "notification-type" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""], "wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"], "origin" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item originated at this site"], "psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"],