diff --git a/database.sql b/database.sql index c1b64fbed..ab8c300a8 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.12-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1485 +-- DB_UPDATE_VERSION 1486 -- ------------------------------------------ @@ -1292,6 +1292,7 @@ CREATE TABLE IF NOT EXISTS `post-media` ( `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 item uri', `url` varbinary(1024) NOT NULL COMMENT 'Media URL', + `media-uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the activities uri-id', `type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Media type', `mimetype` varchar(60) COMMENT '', `height` smallint unsigned COMMENT 'Height of the media', @@ -1311,7 +1312,9 @@ CREATE TABLE IF NOT EXISTS `post-media` ( PRIMARY KEY(`id`), UNIQUE INDEX `uri-id-url` (`uri-id`,`url`(512)), INDEX `uri-id-id` (`uri-id`,`id`), - FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE + INDEX `media-uri-id` (`media-uri-id`), + FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, + FOREIGN KEY (`media-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media'; -- diff --git a/doc/database/db_post-media.md b/doc/database/db_post-media.md index 59c87cd36..d6e3a703c 100644 --- a/doc/database/db_post-media.md +++ b/doc/database/db_post-media.md @@ -6,36 +6,38 @@ Attached media 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 item uri | int unsigned | NO | | NULL | | -| url | Media URL | varbinary(1024) | NO | | NULL | | -| type | Media type | tinyint unsigned | NO | | 0 | | -| mimetype | | varchar(60) | YES | | NULL | | -| height | Height of the media | smallint unsigned | YES | | NULL | | -| width | Width of the media | smallint unsigned | YES | | NULL | | -| size | Media size | bigint unsigned | YES | | NULL | | -| preview | Preview URL | varbinary(512) | YES | | NULL | | -| preview-height | Height of the preview picture | smallint unsigned | YES | | NULL | | -| preview-width | Width of the preview picture | smallint unsigned | YES | | NULL | | -| description | | text | YES | | NULL | | -| name | Name of the media | varchar(255) | YES | | NULL | | -| author-url | URL of the author of the media | varbinary(383) | YES | | NULL | | -| author-name | Name of the author of the media | varchar(255) | YES | | NULL | | -| author-image | Image of the author of the media | varbinary(383) | YES | | NULL | | -| publisher-url | URL of the publisher of the media | varbinary(383) | YES | | NULL | | -| publisher-name | Name of the publisher of the media | varchar(255) | YES | | NULL | | -| publisher-image | Image of the publisher of the media | varbinary(383) | YES | | NULL | | +| 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 item uri | int unsigned | NO | | NULL | | +| url | Media URL | varbinary(1024) | NO | | NULL | | +| media-uri-id | Id of the item-uri table entry that contains the activities uri-id | int unsigned | YES | | NULL | | +| type | Media type | tinyint unsigned | NO | | 0 | | +| mimetype | | varchar(60) | YES | | NULL | | +| height | Height of the media | smallint unsigned | YES | | NULL | | +| width | Width of the media | smallint unsigned | YES | | NULL | | +| size | Media size | bigint unsigned | YES | | NULL | | +| preview | Preview URL | varbinary(512) | YES | | NULL | | +| preview-height | Height of the preview picture | smallint unsigned | YES | | NULL | | +| preview-width | Width of the preview picture | smallint unsigned | YES | | NULL | | +| description | | text | YES | | NULL | | +| name | Name of the media | varchar(255) | YES | | NULL | | +| author-url | URL of the author of the media | varbinary(383) | YES | | NULL | | +| author-name | Name of the author of the media | varchar(255) | YES | | NULL | | +| author-image | Image of the author of the media | varbinary(383) | YES | | NULL | | +| publisher-url | URL of the publisher of the media | varbinary(383) | YES | | NULL | | +| publisher-name | Name of the publisher of the media | varchar(255) | YES | | NULL | | +| publisher-image | Image of the publisher of the media | varbinary(383) | YES | | NULL | | Indexes ------------ -| Name | Fields | -| ---------- | ------------------------ | -| PRIMARY | id | -| uri-id-url | UNIQUE, uri-id, url(512) | -| uri-id-id | uri-id, id | +| Name | Fields | +| ------------ | ------------------------ | +| PRIMARY | id | +| uri-id-url | UNIQUE, uri-id, url(512) | +| uri-id-id | uri-id, id | +| media-uri-id | media-uri-id | Foreign Keys ------------ @@ -43,5 +45,6 @@ Foreign Keys | Field | Target Table | Target Field | |-------|--------------|--------------| | uri-id | [item-uri](help/database/db_item-uri) | id | +| media-uri-id | [item-uri](help/database/db_item-uri) | id | Return to [database documentation](help/database) diff --git a/src/Content/Item.php b/src/Content/Item.php index 1838d4ac4..bfa107325 100644 --- a/src/Content/Item.php +++ b/src/Content/Item.php @@ -608,9 +608,10 @@ class Item * * @param integer $UriId * @param integer $uid + * @param bool $add_media * @return string */ - public function createSharedPostByUriId(int $UriId, int $uid = 0): string + public function createSharedPostByUriId(int $UriId, int $uid = 0, bool $add_media = false): string { $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network']; $shared_item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]); @@ -619,7 +620,7 @@ class Item return ''; } - return $this->createSharedBlockByArray($shared_item); + return $this->createSharedBlockByArray($shared_item, $add_media); } /** @@ -627,9 +628,10 @@ class Item * * @param string $guid * @param integer $uid + * @param bool $add_media * @return string */ - public function createSharedPostByGuid(string $guid, int $uid = 0, string $host = ''): string + public function createSharedPostByGuid(string $guid, int $uid = 0, string $host = '', bool $add_media = false): string { $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network']; $shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]); @@ -646,7 +648,7 @@ class Item return ''; } - return $this->createSharedBlockByArray($shared_item); + return $this->createSharedBlockByArray($shared_item, $add_media); } /** @@ -678,7 +680,7 @@ class Item // If it is a reshared post then reformat it to avoid display problems with two share elements if (Diaspora::isReshare($item['body'], false)) { - if (!empty($shared['guid']) && ($encaspulated_share = self::createSharedPostByGuid($shared['guid']))) { + if (!empty($shared['guid']) && ($encaspulated_share = self::createSharedPostByGuid($shared['guid'], 0, '', $add_media))) { $item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $encaspulated_share, $item['body']); } diff --git a/src/Model/Item.php b/src/Model/Item.php index 439c1b990..3270d170b 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2946,17 +2946,44 @@ class Item $body = $item['body'] ?? ''; $shared = BBCode::fetchShareAttributes($body); if (!empty($shared['guid'])) { - $shared_item = Post::selectFirst(['uri-id', 'plink', 'has-media'], ['guid' => $shared['guid']]); - $shared_uri_id = $shared_item['uri-id'] ?? 0; - $shared_links = [strtolower($shared_item['plink'] ?? '')]; - $shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid'], [], $shared_item['has-media'] ?? false); + $shared_item = Post::selectFirst(['uri-id', 'guid', 'plink', 'has-media'], ['guid' => $shared['guid'], 'uid' => [$item['uid'], 0]]); + } + + $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network', 'has-media']; + + $shared_uri_id = 0; + $shared_links = []; + + if (empty($shared_item['uri-id']) && !empty($item['quote-uri-id'])) { + $shared_item = Post::selectFirst($fields, ['uri-id' => $item['quote-uri-id']]); + $quote_uri_id = $item['quote-uri-id'] ?? 0; + $shared_links[] = strtolower($item['quote-uri']); + } elseif (empty($shared_item['uri-id']) && empty($item['quote-uri-id'])) { + $media = Post\Media::getByURIId($item['uri-id'], [Post\Media::ACTIVITY]); + if (!empty($media)) { + $shared_item = Post::selectFirst($fields, ['plink' => $media[0]['url'], 'uid' => [$item['uid'], 0]]); + + if (empty($shared_item['uri-id'])) { + $shared_item = Post::selectFirst($fields, ['uri' => $media[0]['url'], 'uid' => [$item['uid'], 0]]); + $shared_links[] = strtolower($media[0]['url']); + } + + $quote_uri_id = $shared_item['uri-id'] ?? 0; + } + } + + if (!empty($quote_uri_id)) { + $item['body'] .= "\n" . DI::contentItem()->createSharedBlockByArray($shared_item); + } + + if (!empty($shared_item['uri-id'])) { + $shared_uri_id = $shared_item['uri-id']; + $shared_links[] = strtolower($shared_item['plink']); + $shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared_item['guid'], [], $shared_item['has-media']); $shared_links = array_merge($shared_links, array_column($shared_attachments['visual'], 'url')); $shared_links = array_merge($shared_links, array_column($shared_attachments['link'], 'url')); $shared_links = array_merge($shared_links, array_column($shared_attachments['additional'], 'url')); $item['body'] = self::replaceVisualAttachments($shared_attachments, $item['body']); - } else { - $shared_uri_id = 0; - $shared_links = []; } $attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links, $item['has-media'] ?? false); @@ -3278,7 +3305,7 @@ class Item } DI::profiler()->stopRecording(); - if (isset($data['url']) && !in_array($data['url'], $ignore_links)) { + if (isset($data['url']) && !in_array(strtolower($data['url']), $ignore_links)) { if (!empty($data['description']) || !empty($data['image']) || !empty($data['preview'])) { $parts = parse_url($data['url']); if (!empty($parts['scheme']) && !empty($parts['host'])) { diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index ce00b205c..6d0707743 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -23,10 +23,12 @@ namespace Friendica\Model\Post; use Friendica\Content\Text\BBCode; use Friendica\Core\Logger; +use Friendica\Core\Protocol; use Friendica\Core\System; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; +use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\Photo; use Friendica\Model\Post; @@ -56,6 +58,7 @@ class Media const HTML = 17; const XML = 18; const PLAIN = 19; + const ACTIVITY = 20; const DOCUMENT = 128; /** @@ -215,6 +218,10 @@ class Media $media = self::addType($media); } + if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) { + $media = self::addActivity($media); + } + if ($media['type'] == self::HTML) { $data = ParseUrl::getSiteinfoCached($media['url'], false); $media['preview'] = $data['images'][0]['src'] ?? null; @@ -232,6 +239,65 @@ class Media return $media; } + /** + * Adds the activity type if the media entry is linked to an activity + * + * @param array $media + * @return array + */ + private static function addActivity(array $media): array + { + $id = Item::fetchByLink($media['url']); + if (empty($id)) { + return $media; + } + + $item = Post::selectFirst([], ['id' => $id, 'network' => Protocol::FEDERATED]); + if (empty($item['id'])) { + Logger::debug('Not a federated activity', ['id' => $id, 'uri-id' => $media['uri-id'], 'url' => $media['url']]); + return $media; + } + + if (!empty($item['plink']) && Strings::compareLink($item['plink'], $media['url']) && + parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST)) { + Logger::debug('Not a link to an activity', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]); + return $media; + } + + if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) { + $media['mimetype'] = 'application/activity+json'; + } elseif ($item['network'] == Protocol::DIASPORA) { + $media['mimetype'] = 'application/xml'; + } else { + $media['mimetype'] = ''; + } + + $contact = Contact::getById($item['author-id'], ['avatar', 'gsid']); + if (!empty($contact['gsid'])) { + $gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]); + } + + $media['type'] = self::ACTIVITY; + $media['media-uri-id'] = $item['uri-id']; + $media['height'] = null; + $media['width'] = null; + $media['size'] = null; + $media['preview'] = null; + $media['preview-height'] = null; + $media['preview-width'] = null; + $media['description'] = $item['body']; + $media['name'] = $item['title']; + $media['author-url'] = $item['author-link']; + $media['author-name'] = $item['author-name']; + $media['author-image'] = $contact['avatar'] ?? $item['author-avatar']; + $media['publisher-url'] = $gserver['url'] ?? null; + $media['publisher-name'] = $gserver['site_name'] ?? null; + $media['publisher-image'] = null; + + Logger::debug('Activity detected', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]); + return $media; + } + /** * Fetch media data from local resources * @param array $media diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 5183a82c3..04ea02f80 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -170,7 +170,7 @@ class Processor } /** - * Stire attachment data + * Store attachment data * * @param array $activity * @param array $item @@ -187,7 +187,7 @@ class Processor } /** - * Store attachment data + * Store question data * * @param array $activity * @param array $item diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index c37fefb42..8b9571e55 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -1674,13 +1674,8 @@ class Transmitter } $data['quoteUrl'] = $item['quote-uri']; } elseif (!empty($item['quote-uri']) && !Diaspora::isReshare($body, false)) { - $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network']; - $shared_item = Post::selectFirst($fields, ['uri-id' => $item['quote-uri-id']]); - if (!empty($shared_item['uri-id'])) { - $shared_item['body'] = Post\Media::addAttachmentsToBody($shared_item['uri-id'], $shared_item['body']); - $body .= "\n" . DI::contentItem()->createSharedBlockByArray($shared_item); - $item['body'] = Item::improveSharedDataInBody($item, true); - } + $body .= "\n" . DI::contentItem()->createSharedPostByUriId($item['quote-uri-id'], $item['uid'], true); + $item['body'] = Item::improveSharedDataInBody($item, true); } $data['content'] = BBCode::convertForUriId($item['uri-id'], $body, BBCode::ACTIVITYPUB); diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 5da1c1771..262d7d307 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', 1485); + define('DB_UPDATE_VERSION', 1486); } return [ @@ -1321,6 +1321,7 @@ return [ "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 item uri"], "url" => ["type" => "varbinary(1024)", "not null" => "1", "comment" => "Media URL"], + "media-uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the activities uri-id"], "type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Media type"], "mimetype" => ["type" => "varchar(60)", "comment" => ""], "height" => ["type" => "smallint unsigned", "comment" => "Height of the media"], @@ -1342,6 +1343,7 @@ return [ "PRIMARY" => ["id"], "uri-id-url" => ["UNIQUE", "uri-id", "url(512)"], "uri-id-id" => ["uri-id", "id"], + "media-uri-id" => ["media-uri-id"], ] ], "post-question" => [