diff --git a/database.sql b/database.sql index e181fd73f7..120edf3294 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2024.06-dev (Yellow Archangel) --- DB_UPDATE_VERSION 1558 +-- DB_UPDATE_VERSION 1559 -- ------------------------------------------ @@ -1347,7 +1347,7 @@ CREATE TABLE IF NOT EXISTS `post-engagement` ( `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', `owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner', `contact-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Person, organisation, news, community, relay', - `media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio', + `media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)', `language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format', `searchtext` mediumtext COMMENT 'Simplified text for the full text search', `size` int unsigned COMMENT 'Body size', @@ -1471,7 +1471,7 @@ CREATE TABLE IF NOT EXISTS `post-question-option` ( CREATE TABLE IF NOT EXISTS `post-searchindex` ( `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', `owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner', - `media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio', + `media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)', `language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format', `searchtext` mediumtext COMMENT 'Simplified text for the full text search', `size` int unsigned COMMENT 'Body size', @@ -1550,6 +1550,7 @@ CREATE TABLE IF NOT EXISTS `post-user` ( `post-reason` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Reason why the post arrived at the user', `vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs', `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted', + `restrictions` tinyint unsigned COMMENT 'Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce)', `global` boolean NOT NULL DEFAULT '0' COMMENT '', `visible` boolean NOT NULL DEFAULT '0' COMMENT '', `deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been marked for deletion', @@ -2201,6 +2202,7 @@ CREATE VIEW `post-user-view` AS SELECT `post-content`.`location` AS `location`, `post-content`.`coord` AS `coord`, `post-content`.`sensitive` AS `sensitive`, + `post-user`.`restrictions` AS `restrictions`, `post-content`.`app` AS `app`, `post-content`.`object-type` AS `object-type`, `post-content`.`object` AS `object`, @@ -2386,6 +2388,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT `post-content`.`location` AS `location`, `post-content`.`coord` AS `coord`, `post-content`.`sensitive` AS `sensitive`, + `post-user`.`restrictions` AS `restrictions`, `post-content`.`app` AS `app`, `post-content`.`object-type` AS `object-type`, `post-content`.`object` AS `object`, diff --git a/doc/database/db_post-engagement.md b/doc/database/db_post-engagement.md index 32f3ce76b6..31ae5e4e41 100644 --- a/doc/database/db_post-engagement.md +++ b/doc/database/db_post-engagement.md @@ -11,7 +11,7 @@ Fields | uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | | | owner-id | Item owner | int unsigned | NO | | 0 | | | contact-type | Person, organisation, news, community, relay | tinyint | NO | | 0 | | -| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | | +| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | | | language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | | | searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | | | size | Body size | int unsigned | YES | | NULL | | diff --git a/doc/database/db_post-searchindex.md b/doc/database/db_post-searchindex.md index c6504a7ed3..203a2dbab3 100644 --- a/doc/database/db_post-searchindex.md +++ b/doc/database/db_post-searchindex.md @@ -10,7 +10,7 @@ Fields | ---------- | --------------------------------------------------------------------- | ------------ | ---- | --- | ------- | ----- | | uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | | | owner-id | Item owner | int unsigned | NO | | 0 | | -| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | | +| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | | | language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | | | searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | | | size | Body size | int unsigned | YES | | NULL | | diff --git a/doc/database/db_post-user.md b/doc/database/db_post-user.md index 2823391d47..c20d8bcb47 100644 --- a/doc/database/db_post-user.md +++ b/doc/database/db_post-user.md @@ -25,6 +25,7 @@ Fields | post-reason | Reason why the post arrived at the user | tinyint unsigned | NO | | 0 | | | vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | | | private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | | +| restrictions | Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce) | tinyint unsigned | YES | | NULL | | | global | | boolean | NO | | 0 | | | visible | | boolean | NO | | 0 | | | deleted | item has been marked for deletion | boolean | NO | | 0 | | diff --git a/src/Model/Item.php b/src/Model/Item.php index 535e8f7989..a3a9fdb184 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -108,7 +108,7 @@ class Item 'owner-id', 'owner-link', 'owner-alias', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'owner-gsid', 'causer-id', 'causer-link', 'causer-alias', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'causer-gsid', 'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar', - 'writable', 'self', 'cid', 'alias', + 'writable', 'restrictions', 'self', 'cid', 'alias', 'event-created', 'event-edited', 'event-start', 'event-finish', 'event-summary', 'event-desc', 'event-location', 'event-type', 'event-nofinish', 'event-ignore', 'event-id', @@ -168,6 +168,11 @@ class Item const GRAVITY_COMMENT = 6; const GRAVITY_UNKNOWN = 9; + // Restrictions + const CANT_REPLY = 1; + const CANT_LIKE = 2; + const CANT_ANNOUNCE = 4; + /** * Update existing item entries * @@ -759,7 +764,7 @@ class Item { $fields = [ 'uid', 'uri', 'parent-uri', 'id', 'deleted', - 'uri-id', 'parent-uri-id', + 'uri-id', 'parent-uri-id', 'restrictions', 'verb', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'wall', 'private', 'origin', 'author-id' ]; @@ -783,6 +788,11 @@ class Item return []; } + if (self::hasRestrictions($item, $parent['author-id'], $parent['restrictions'])) { + Logger::notice('Restrictions apply - ignoring item', ['restrictions' => $parent['restrictions'], 'verb' => $parent['verb'], 'uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); + return 0; + } + if ($parent['uri-id'] == $parent['parent-uri-id']) { return $parent; } @@ -1439,6 +1449,27 @@ class Item return $post_user_id; } + private static function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool + { + if (empty($restrictions) || ($author_id == $item['author-id'])) { + return false; + } + + if (($restrictions & self::CANT_REPLY) && ($item['verb'] == Activity::POST)) { + return true; + } + + if (($restrictions & self::CANT_ANNOUNCE) && ($item['verb'] == Activity::ANNOUNCE)) { + return true; + } + + if (($restrictions & self::CANT_LIKE) && in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDMAYBE, Activity::ATTENDNO])) { + return true; + } + + return false; + } + private static function reshareChannelPost(int $uri_id, int $reshare_id = 0) { if (!DI::config()->get('system', 'allow_relay_channels')) { diff --git a/src/Object/Post.php b/src/Object/Post.php index a4a4831389..396157c8fe 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -212,12 +212,25 @@ class Post $shareable = in_array($conv->getProfileOwner(), [0, DI::userSession()->getLocalUserId()]) && $item['private'] != Item::PRIVATE; $announceable = $shareable && in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER, Protocol::TUMBLR, Protocol::BLUESKY]); $commentable = ($item['network'] != Protocol::TUMBLR); + $likeable = true; // On Diaspora only toplevel posts can be reshared if ($announceable && ($item['network'] == Protocol::DIASPORA) && ($item['gravity'] != Item::GRAVITY_PARENT)) { $announceable = false; } + if ($item['restrictions'] & Item::CANT_REPLY) { + $commentable = false; + } + + if ($item['restrictions'] & Item::CANT_LIKE) { + $likeable = false; + } + + if ($item['restrictions'] & Item::CANT_ANNOUNCE) { + $announceable = false; + } + $edpost = false; if (DI::userSession()->getLocalUserId()) { @@ -423,8 +436,10 @@ class Post } if ($conv->isWritable()) { - $buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29"), DI::l10n()->t('Like')]; - $buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t('Dislike')]; + if ($likeable) { + $buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29"), DI::l10n()->t('Like')]; + $buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t('Dislike')]; + } if ($shareable) { $buttons['share'] = [DI::l10n()->t('Quote share this'), DI::l10n()->t('Quote Share')]; } diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 8db7be44a7..d42483a4e0 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -927,7 +927,16 @@ class Processor $restrictions = []; } - // @todo Store restrictions + $item['restrictions'] = null; + foreach ($restrictions as $restriction) { + if ($restriction == Tag::CAN_REPLY) { + $item['restrictions'] = $item['restrictions'] | Item::CANT_REPLY; + } elseif ($restriction == Tag::CAN_LIKE) { + $item['restrictions'] = $item['restrictions'] | Item::CANT_LIKE; + } elseif ($restriction == Tag::CAN_ANNOUNCE) { + $item['restrictions'] = $item['restrictions'] | Item::CANT_ANNOUNCE; + } + } $item['location'] = $activity['location']; diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 57b0fe009f..4068ce97ce 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -56,7 +56,7 @@ use Friendica\Database\DBA; // This file is required several times during the test in DbaDefinition which justifies this condition if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1558); + define('DB_UPDATE_VERSION', 1559); } return [ @@ -1368,7 +1368,7 @@ return [ "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 item uri"], "owner-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => "Item owner"], "contact-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Person, organisation, news, community, relay"], - "media-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Type of media in a bit array (1 = image, 2 = video, 4 = audio"], + "media-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Type of media in a bit array (1 = image, 2 = video, 4 = audio)"], "language" => ["type" => "char(2)", "comment" => "Language information about this post in the ISO 639-1 format"], "searchtext" => ["type" => "mediumtext", "comment" => "Simplified text for the full text search"], "size" => ["type" => "int unsigned", "comment" => "Body size"], @@ -1490,7 +1490,7 @@ return [ "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 item uri"], "owner-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => "Item owner"], - "media-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Type of media in a bit array (1 = image, 2 = video, 4 = audio"], + "media-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => "Type of media in a bit array (1 = image, 2 = video, 4 = audio)"], "language" => ["type" => "char(2)", "comment" => "Language information about this post in the ISO 639-1 format"], "searchtext" => ["type" => "mediumtext", "comment" => "Simplified text for the full text search"], "size" => ["type" => "int unsigned", "comment" => "Body size"], @@ -1562,6 +1562,7 @@ return [ "post-reason" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Reason why the post arrived at the user"], "vid" => ["type" => "smallint unsigned", "foreign" => ["verb" => "id", "on delete" => "restrict"], "comment" => "Id of the verb table entry that contains the activity verbs"], "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "0=public, 1=private, 2=unlisted"], + "restrictions" => ["type" => "tinyint unsigned", "comment" => "Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce)"], "global" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been marked for deletion"], diff --git a/static/dbview.config.php b/static/dbview.config.php index f84cb3b8e1..1262e24656 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -228,6 +228,7 @@ "location" => ["post-content", "location"], "coord" => ["post-content", "coord"], "sensitive" => ["post-content", "sensitive"], + "restrictions" => ["post-user", "restrictions"], "app" => ["post-content", "app"], "object-type" => ["post-content", "object-type"], "object" => ["post-content", "object"], @@ -411,6 +412,7 @@ "location" => ["post-content", "location"], "coord" => ["post-content", "coord"], "sensitive" => ["post-content", "sensitive"], + "restrictions" => ["post-user", "restrictions"], "app" => ["post-content", "app"], "object-type" => ["post-content", "object-type"], "object" => ["post-content", "object"],