From ca1b92bb34ef851d364f0174accbf265814fe311 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 07:57:23 +0000 Subject: [PATCH 1/9] Support unlisted public posts --- include/api.php | 20 +++++------ mod/display.php | 12 +++---- mod/item.php | 9 ++++- mod/lockview.php | 2 +- mod/poke.php | 2 +- mod/pubsubhubbub.php | 8 +---- mod/settings.php | 7 ++++ mod/share.php | 2 +- src/Content/Text/BBCode.php | 3 +- src/Model/Item.php | 36 ++++++++++--------- src/Model/Term.php | 7 ++-- src/Module/Conversation/Community.php | 10 +++--- src/Module/Diaspora/Fetch.php | 2 +- src/Module/Objects.php | 4 +-- src/Object/Post.php | 4 +-- src/Protocol/ActivityPub/Processor.php | 17 +++++++-- src/Protocol/ActivityPub/Receiver.php | 18 +++++++--- src/Protocol/ActivityPub/Transmitter.php | 15 +++++--- src/Protocol/DFRN.php | 11 +++--- src/Protocol/Diaspora.php | 16 ++++----- src/Protocol/Feed.php | 2 +- src/Protocol/OStatus.php | 10 +++--- src/Util/JsonLD.php | 4 ++- src/Worker/Delivery.php | 2 +- src/Worker/Notifier.php | 9 +++-- src/Worker/OnePoll.php | 4 +-- static/dbstructure.config.php | 6 ++-- view/templates/settings/settings.tpl | 2 ++ .../frio/templates/settings/settings.tpl | 2 ++ 29 files changed, 146 insertions(+), 100 deletions(-) diff --git a/include/api.php b/include/api.php index 567e3cc439..aae4e8d286 100644 --- a/include/api.php +++ b/include/api.php @@ -785,7 +785,7 @@ function api_item_get_user(App $a, $item) $author_user = $status_user; - $status_user["protected"] = $item['private'] ?? 0; + $status_user["protected"] = $item['private'] == Item::PRIVATE; if (($item['thr-parent'] ?? '') == ($item['uri'] ?? '')) { $owner_user = api_get_user($a, $item['owner-id'] ?? null); @@ -1344,7 +1344,7 @@ function api_get_last_status($ownerId, $uid) 'author-id'=> $ownerId, 'uid' => $uid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], - 'private' => false + 'private' => [Item::PUBLIC, Item::UNLISTED] ]; $item = api_get_item($condition); @@ -1734,8 +1734,8 @@ function api_statuses_public_timeline($type) $start = max(0, ($page - 1) * $count); if ($exclude_replies && !$conversation_id) { - $condition = ["`gravity` IN (?, ?) AND `iid` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND NOT `author`.`hidden`", - GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; + $condition = ["`gravity` IN (?, ?) AND `iid` > ? AND `private` = ? AND `wall` AND NOT `author`.`hidden`", + GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC]; if ($max_id > 0) { $condition[0] .= " AND `thread`.`iid` <= ?"; @@ -1747,8 +1747,8 @@ function api_statuses_public_timeline($type) $r = Item::inArray($statuses); } else { - $condition = ["`gravity` IN (?, ?) AND `id` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND `item`.`origin` AND NOT `author`.`hidden`", - GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; + $condition = ["`gravity` IN (?, ?) AND `id` > ? AND `private` = ? AND `wall` AND `item`.`origin` AND NOT `author`.`hidden`", + GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC]; if ($max_id > 0) { $condition[0] .= " AND `item`.`id` <= ?"; @@ -1813,8 +1813,8 @@ function api_statuses_networkpublic_timeline($type) $start = max(0, ($page - 1) * $count); - $condition = ["`uid` = 0 AND `gravity` IN (?, ?) AND `thread`.`iid` > ? AND NOT `private`", - GRAVITY_PARENT, GRAVITY_COMMENT, $since_id]; + $condition = ["`uid` = 0 AND `gravity` IN (?, ?) AND `thread`.`iid` > ? AND `private` = ?", + GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, Item::PUBLIC]; if ($max_id > 0) { $condition[0] .= " AND `thread`.`iid` <= ?"; @@ -2042,7 +2042,7 @@ function api_statuses_repeat($type) Logger::log('API: api_statuses_repeat: '.$id); $fields = ['body', 'title', 'attach', 'tag', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink']; - $item = Item::selectFirst($fields, ['id' => $id, 'private' => false]); + $item = Item::selectFirst($fields, ['id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED]]); if (DBA::isResult($item) && $item['body'] != "") { if (strpos($item['body'], "[/share]") !== false) { @@ -3007,7 +3007,7 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use 'user' => $status_user, 'friendica_author' => $author_user, 'friendica_owner' => $owner_user, - 'friendica_private' => $item['private'] == 1, + 'friendica_private' => $item['private'] == Item::PRIVATE, //'entities' => NULL, 'statusnet_html' => $converted["html"], 'statusnet_conversation_id' => $item['parent'], diff --git a/mod/display.php b/mod/display.php index dce8b25b9c..fd0079f9f6 100644 --- a/mod/display.php +++ b/mod/display.php @@ -70,7 +70,7 @@ function display_init(App $a) // Is this item private but could be visible to the remove visitor? if (!DBA::isResult($item) && remote_user()) { - $item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1, 'origin' => true]); + $item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => Item::PRIVATE, 'origin' => true]); if (DBA::isResult($item)) { if (!Contact::isFollower(remote_user(), $item['uid'])) { $item = null; @@ -82,14 +82,14 @@ function display_init(App $a) // Is it an item with uid=0? if (!DBA::isResult($item)) { - $item = Item::selectFirstForUser(local_user(), $fields, ['guid' => $a->argv[1], 'private' => [0, 2], 'uid' => 0]); + $item = Item::selectFirstForUser(local_user(), $fields, ['guid' => $a->argv[1], 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]); } } elseif ($a->argc >= 3 && $nick == 'feed-item') { $item_id = $a->argv[2]; if (substr($item_id, -5) == '.atom') { $item_id = substr($item_id, 0, -5); } - $item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item_id, 'private' => [0, 2], 'uid' => 0]); + $item = Item::selectFirstForUser(local_user(), $fields, ['id' => $item_id, 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]); } if (!DBA::isResult($item)) { @@ -216,7 +216,7 @@ function display_content(App $a, $update = false, $update_uid = 0) } if (($item_parent == 0) && remote_user()) { - $item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1, 'origin' => true]); + $item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => Item::PRIVATE, 'origin' => true]); if (DBA::isResult($item) && Contact::isFollower(remote_user(), $item['uid'])) { $item_id = $item["id"]; $item_parent = $item["parent"]; @@ -225,7 +225,7 @@ function display_content(App $a, $update = false, $update_uid = 0) } if ($item_parent == 0) { - $condition = ['private' => [0, 2], 'guid' => $a->argv[1], 'uid' => 0]; + $condition = ['private' => [Item::PUBLIC, Item::UNLISTED], 'guid' => $a->argv[1], 'uid' => 0]; $item = Item::selectFirstForUser(local_user(), $fields, $condition); if (DBA::isResult($item)) { $item_id = $item["id"]; @@ -241,7 +241,7 @@ function display_content(App $a, $update = false, $update_uid = 0) } // We are displaying an "alternate" link if that post was public. See issue 2864 - $is_public = Item::exists(['id' => $item_id, 'private' => [0, 2]]); + $is_public = Item::exists(['id' => $item_id, 'private' => [Item::PUBLIC, Item::UNLISTED]]); if ($is_public) { // For the atom feed the nickname doesn't matter at all, we only need the item id. $alternate = DI::baseUrl().'/display/feed-item/'.$item_id.'.atom'; diff --git a/mod/item.php b/mod/item.php index 2e5a082036..4e14a047f5 100644 --- a/mod/item.php +++ b/mod/item.php @@ -40,6 +40,7 @@ use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Attach; +use Friendica\Model\Config\PConfig; use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\FileTag; @@ -300,7 +301,13 @@ function item_post(App $a) { $postopts = $_REQUEST['postopts'] ?? ''; - $private = ((strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) ? 1 : 0); + if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) { + $private = Item::PRIVATE; + } elseif (PConfig::get($profile_uid, 'system', 'unlisted')) { + $private == Item::UNLISTED; + } else { + $private == Item::PUBLIC; + } // If this is a comment, set the permissions from the parent. diff --git a/mod/lockview.php b/mod/lockview.php index d3eceb4e07..e48debfc6b 100644 --- a/mod/lockview.php +++ b/mod/lockview.php @@ -66,7 +66,7 @@ function lockview_content(App $a) } if (isset($item['private']) - && $item['private'] == 1 + && $item['private'] == Item::PRIVATE && empty($item['allow_cid']) && empty($item['allow_gid']) && empty($item['deny_cid']) diff --git a/mod/poke.php b/mod/poke.php index 0034a2d87a..a683b11411 100644 --- a/mod/poke.php +++ b/mod/poke.php @@ -84,7 +84,7 @@ function poke_init(App $a) $deny_gid = $item['deny_gid']; } } else { - $private = (!empty($_GET['private']) ? intval($_GET['private']) : 0); + $private = (!empty($_GET['private']) ? intval($_GET['private']) : Item::PUBLIC); $allow_cid = ($private ? '<' . $target['id']. '>' : $a->user['allow_cid']); $allow_gid = ($private ? '' : $a->user['allow_gid']); diff --git a/mod/pubsubhubbub.php b/mod/pubsubhubbub.php index 3d6b48fd4b..4d33503796 100644 --- a/mod/pubsubhubbub.php +++ b/mod/pubsubhubbub.php @@ -84,18 +84,12 @@ function pubsubhubbub_init(App $a) { // fetch user from database given the nickname $condition = ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]; - $owner = DBA::selectFirst('user', ['uid', 'hidewall', 'nickname'], $condition); + $owner = DBA::selectFirst('user', ['uid', 'nickname'], $condition); if (!DBA::isResult($owner)) { Logger::log('Local account not found: ' . $nick . ' - topic: ' . $hub_topic . ' - callback: ' . $hub_callback); throw new \Friendica\Network\HTTPException\NotFoundException(); } - // abort if user's wall is supposed to be private - if ($owner['hidewall']) { - Logger::log('Local user ' . $nick . 'has chosen to hide wall, ignoring.'); - throw new \Friendica\Network\HTTPException\ForbiddenException(); - } - // get corresponding row from contact table $condition = ['uid' => $owner['uid'], 'blocked' => false, 'pending' => false, 'self' => true]; diff --git a/mod/settings.php b/mod/settings.php index fcbd8208d4..8b89667662 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -317,6 +317,7 @@ function settings_post(App $a) $cntunkmail = (!empty($_POST['cntunkmail']) ? intval($_POST['cntunkmail']) : 0); $hide_friends = (($_POST['hide-friends'] == 1) ? 1: 0); $hidewall = (($_POST['hidewall'] == 1) ? 1: 0); + $unlisted = (($_POST['unlisted'] == 1) ? 1: 0); $email_textonly = (($_POST['email_textonly'] == 1) ? 1 : 0); $detailed_notif = (($_POST['detailed_notif'] == 1) ? 1 : 0); @@ -414,6 +415,7 @@ function settings_post(App $a) DI::pConfig()->set(local_user(), 'system', 'email_textonly', $email_textonly); DI::pConfig()->set(local_user(), 'system', 'detailed_notif', $detailed_notif); + DI::pConfig()->set(local_user(), 'system', 'unlisted', $unlisted); if ($page_flags == User::PAGE_FLAGS_PRVGROUP) { $hidewall = 1; @@ -836,6 +838,10 @@ function settings_content(App $a) '$field' => ['hidewall', DI::l10n()->t('Hide your profile details from anonymous viewers?'), $a->user['hidewall'], DI::l10n()->t('Anonymous visitors will only see your profile picture, your display name and the nickname you are using on your profile page. Your public posts and replies will still be accessible by other means.')], ]); + $unlisted = Renderer::replaceMacros($opt_tpl, [ + '$field' => ['unlisted', DI::l10n()->t('Should public posts be unlisted?'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community page or in search results, nor will they be transported to relay servers.')], + ]); + $blockwall = Renderer::replaceMacros($opt_tpl, [ '$field' => ['blockwall', DI::l10n()->t('Allow friends to post to your profile page?'), (intval($a->user['blockwall']) ? '0' : '1'), DI::l10n()->t('Your contacts may write posts on your profile wall. These posts will be distributed to your contacts')], ]); @@ -949,6 +955,7 @@ function settings_content(App $a) '$profile_in_net_dir' => $profile_in_net_dir, '$hide_friends' => $hide_friends, '$hide_wall' => $hide_wall, + '$unlisted' => $unlisted, '$unkmail' => $unkmail, '$cntunkmail' => ['cntunkmail', DI::l10n()->t('Maximum private messages per day from unknown people:'), $cntunkmail , DI::l10n()->t("\x28to prevent spam abuse\x29")], diff --git a/mod/share.php b/mod/share.php index 998ebe5fde..3e9b6aee6f 100644 --- a/mod/share.php +++ b/mod/share.php @@ -34,7 +34,7 @@ function share_init(App $a) { 'guid', 'created', 'plink', 'title']; $item = Item::selectFirst($fields, ['id' => $post_id]); - if (!DBA::isResult($item) || $item['private'] == 1) { + if (!DBA::isResult($item) || $item['private'] == Item::PRIVATE) { exit(); } diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index 7b4b7b73de..19fe774233 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -975,7 +975,8 @@ class BBCode Contact::getIdForURL($attributes['profile'], 0, true, $default); $author_contact = Contact::getDetailsByURL($attributes['profile']); - $author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']); + $author_contact['url'] = ($author_contact['url'] ?? $attributes['profile']); + $author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']); $attributes['author'] = ($author_contact['name'] ?? '') ?: $attributes['author']; $attributes['avatar'] = ($author_contact['micro'] ?? '') ?: $attributes['avatar']; diff --git a/src/Model/Item.php b/src/Model/Item.php index b960ba38f3..71e37bd18e 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -113,6 +113,10 @@ class Item Activity::FOLLOW, Activity::ANNOUNCE]; + const PUBLIC = 0; + const PRIVATE = 1; + const UNLISTED = 2; + private static $legacy_mode = null; public static function isLegacyMode() @@ -1541,7 +1545,7 @@ class Item $item['allow_gid'] = trim($item['allow_gid'] ?? ''); $item['deny_cid'] = trim($item['deny_cid'] ?? ''); $item['deny_gid'] = trim($item['deny_gid'] ?? ''); - $item['private'] = intval($item['private'] ?? 0); + $item['private'] = intval($item['private'] ?? self::PUBLIC); $item['body'] = trim($item['body'] ?? ''); $item['tag'] = trim($item['tag'] ?? ''); $item['attach'] = trim($item['attach'] ?? ''); @@ -1737,8 +1741,8 @@ class Item * The original author commented, but as this is a comment, the permissions * weren't fixed up so it will still show the comment as private unless we fix it here. */ - if ((intval($parent['forum_mode']) == 1) && $parent['private']) { - $item['private'] = 0; + if ((intval($parent['forum_mode']) == 1) && ($parent['private'] != self::PUBLIC)) { + $item['private'] = self::PUBLIC; } // If its a post that originated here then tag the thread as "mention" @@ -1808,7 +1812,7 @@ class Item // ACL settings if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid)) { - $private = 1; + $private = self::PRIVATE; } else { $private = $item['private']; } @@ -2217,7 +2221,7 @@ class Item // Only distribute public items from native networks $condition = ['id' => $itemid, 'uid' => 0, 'network' => array_merge(Protocol::FEDERATED ,['']), - 'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false]; + 'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => [self::PUBLIC, self::UNLISTED]]; $item = self::selectFirst(self::ITEM_FIELDLIST, $condition); if (!DBA::isResult($item)) { return; @@ -2367,7 +2371,7 @@ class Item } // Is it a visible public post? - if (!$item["visible"] || $item["deleted"] || $item["moderated"] || $item["private"]) { + if (!$item["visible"] || $item["deleted"] || $item["moderated"] || ($item["private"] == Item::PRIVATE)) { return; } @@ -2558,7 +2562,7 @@ class Item Contact::unmarkForArchival($contact); } - $update = (!$arr['private'] && ((($arr['author-link'] ?? '') === ($arr['owner-link'] ?? '')) || ($arr["parent-uri"] === $arr["uri"]))); + $update = (($arr['private'] != self::PRIVATE) && ((($arr['author-link'] ?? '') === ($arr['owner-link'] ?? '')) || ($arr["parent-uri"] === $arr["uri"]))); // Is it a forum? Then we don't care about the rules from above if (!$update && in_array($arr["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN]) && ($arr["parent-uri"] === $arr["uri"])) { @@ -2572,7 +2576,7 @@ class Item ['id' => $arr['contact-id']]); } // Now do the same for the system wide contacts with uid=0 - if (!$arr['private']) { + if ($arr['private'] != self::PRIVATE) { DBA::update('contact', ['success_update' => $arr['received'], 'last-item' => $arr['received']], ['id' => $arr['owner-id']]); @@ -2753,7 +2757,7 @@ class Item // also reset all the privacy bits to the forum default permissions - $private = ($user['allow_cid'] || $user['allow_gid'] || $user['deny_cid'] || $user['deny_gid']) ? 1 : 0; + $private = ($user['allow_cid'] || $user['allow_gid'] || $user['deny_cid'] || $user['deny_gid']) ? self::PRIVATE : self::PUBLIC; $psid = PermissionSet::getIdFromACL( $user['uid'], @@ -2800,7 +2804,7 @@ class Item return false; } - if (($contact['network'] != Protocol::FEED) && $datarray['private']) { + if (($contact['network'] != Protocol::FEED) && ($datarray['private'] == self::PRIVATE)) { Logger::log('Not public', Logger::DEBUG); return false; } @@ -2838,7 +2842,7 @@ class Item $urlpart = parse_url($datarray2['author-link']); $datarray["app"] = $urlpart["host"]; } else { - $datarray['private'] = 0; + $datarray['private'] = self::PUBLIC; } } @@ -3382,7 +3386,7 @@ class Item * * default permissions - anonymous user */ - $sql = " AND NOT `item`.`private`"; + $sql = sprintf(" AND `item`.`private` != %d", self::PRIVATE); // Profile owner - everything is visible if ($local_user && ($local_user == $owner_id)) { @@ -3398,12 +3402,12 @@ class Item $set = PermissionSet::get($owner_id, $remote_user); if (!empty($set)) { - $sql_set = " OR (`item`.`private` IN (1,2) AND `item`.`wall` AND `item`.`psid` IN (" . implode(',', $set) . "))"; + $sql_set = sprintf(" OR (`item`.`private` = %d AND `item`.`wall` AND `item`.`psid` IN (", self::PRIVATE) . implode(',', $set) . "))"; } else { $sql_set = ''; } - $sql = " AND (NOT `item`.`private`" . $sql_set . ")"; + $sql = sprintf(" AND (`item`.`private` != %d", self::PRIVATE) . $sql_set . ")"; } return $sql; @@ -3505,7 +3509,7 @@ class Item continue; } - if ((local_user() == $item['uid']) && ($item['private'] == 1) && ($item['contact-id'] != $app->contact['id']) && ($item['network'] == Protocol::DFRN)) { + if ((local_user() == $item['uid']) && ($item['private'] == self::PRIVATE) && ($item['contact-id'] != $app->contact['id']) && ($item['network'] == Protocol::DFRN)) { $img_url = 'redir/' . $item['contact-id'] . '?url=' . urlencode($mtch[1]); $item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']); } @@ -3683,7 +3687,7 @@ class Item $ret["title"] = DI::l10n()->t('link to source'); } - } elseif (!empty($item['plink']) && ($item['private'] != 1)) { + } elseif (!empty($item['plink']) && ($item['private'] != self::PRIVATE)) { $ret = [ 'href' => $item['plink'], 'orig' => $item['plink'], diff --git a/src/Model/Term.php b/src/Model/Term.php index 6e92c9ce18..fc3405e0b8 100644 --- a/src/Model/Term.php +++ b/src/Model/Term.php @@ -82,7 +82,7 @@ class Term WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated` - AND NOT `thread`.`private` + AND `thread`.`private` = ? AND t.`uid` = 0 AND t.`otype` = ? AND t.`type` = ? @@ -91,6 +91,7 @@ class Term GROUP BY `term` ORDER BY `score` DESC LIMIT ?", + Item::PUBLIC, Term::OBJECT_TYPE_POST, Term::HASHTAG, $period, @@ -122,11 +123,10 @@ class Term FROM `term` t JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid` JOIN `thread` ON `thread`.`iid` = i.`id` - JOIN `user` ON `user`.`uid` = `thread`.`uid` AND NOT `user`.`hidewall` WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated` - AND NOT `thread`.`private` + AND `thread`.`private` = ? AND `thread`.`wall` AND `thread`.`origin` AND t.`otype` = ? @@ -136,6 +136,7 @@ class Term GROUP BY `term` ORDER BY `score` DESC LIMIT ?", + Item::PUBLIC, Term::OBJECT_TYPE_POST, Term::HASHTAG, $period, diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index 58af38eb16..c65672ac07 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -301,22 +301,20 @@ class Community extends BaseModule $values[] = $itemspage; - /// @todo Use "unsearchable" here as well (instead of "hidewall") $r = DBA::p("SELECT `item`.`uri`, `author`.`url` AS `author-link`, `thread`.`commented` FROM `thread` - STRAIGHT_JOIN `user` ON `user`.`uid` = `thread`.`uid` AND NOT `user`.`hidewall` STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid` STRAIGHT_JOIN `contact` AS `author` ON `author`.`id`=`item`.`author-id` WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated` - AND NOT `thread`.`private` AND `thread`.`wall` AND `thread`.`origin` + AND `thread`.`private` = ? AND `thread`.`wall` AND `thread`.`origin` $sql_accounttype $sql_boundaries ORDER BY `thread`.`commented` DESC - LIMIT ?", $values); + LIMIT ?", Item::PUBLIC, $values); } elseif (self::$content == 'global') { if (!is_null(self::$accounttype)) { - $condition = ["`uid` = ? AND NOT `author`.`unsearchable` AND NOT `owner`.`unsearchable` AND `owner`.`contact-type` = ?", 0, self::$accounttype]; + $condition = ["`uid` = ? AND `private` = ? AND `owner`.`contact-type` = ?", 0, Item::PUBLIC, self::$accounttype]; } else { - $condition = ["`uid` = ? AND NOT `author`.`unsearchable` AND NOT `owner`.`unsearchable`", 0]; + $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC]; } if (isset($max_id)) { diff --git a/src/Module/Diaspora/Fetch.php b/src/Module/Diaspora/Fetch.php index 70f982f70d..aba9d33be7 100644 --- a/src/Module/Diaspora/Fetch.php +++ b/src/Module/Diaspora/Fetch.php @@ -54,7 +54,7 @@ class Fetch extends BaseModule 'uid', 'title', 'body', 'guid', 'contact-id', 'private', 'created', 'received', 'app', 'location', 'coord', 'network', 'event-id', 'resource-id', 'author-link', 'author-avatar', 'author-name', 'plink', 'owner-link', 'attach' ]; - $condition = ['wall' => true, 'private' => false, 'guid' => $guid, 'network' => [Protocol::DFRN, Protocol::DIASPORA]]; + $condition = ['wall' => true, 'private' => [Item::PUBLIC, Item::UNLISTED], 'guid' => $guid, 'network' => [Protocol::DFRN, Protocol::DIASPORA]]; $item = Item::selectFirst($fields, $condition); if (empty($item)) { $condition = ['guid' => $guid, 'network' => [Protocol::DFRN, Protocol::DIASPORA]]; diff --git a/src/Module/Objects.php b/src/Module/Objects.php index 9c57665ffa..023ced08ce 100644 --- a/src/Module/Objects.php +++ b/src/Module/Objects.php @@ -49,11 +49,11 @@ class Objects extends BaseModule // At first we try the original post with that guid // @TODO: Replace with parameter from router - $item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'origin' => true, 'private' => false]); + $item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'origin' => true, 'private' => [item::PRIVATE, Item::UNLISTED]]); if (!DBA::isResult($item)) { // If no original post could be found, it could possibly be a forum post, there we remove the "origin" field. // @TODO: Replace with parameter from router - $item = Item::selectFirst(['id', 'author-link'], ['guid' => $a->argv[1], 'private' => false]); + $item = Item::selectFirst(['id', 'author-link'], ['guid' => $a->argv[1], 'private' => [item::PRIVATE, Item::UNLISTED]]); if (!DBA::isResult($item) || !strstr($item['author-link'], DI::baseUrl()->get())) { throw new \Friendica\Network\HTTPException\NotFoundException(); } diff --git a/src/Object/Post.php b/src/Object/Post.php index c92c5a32bb..c8390c840e 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -170,12 +170,12 @@ class Post $conv = $this->getThread(); - $lock = ((($item['private'] == 1) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) + $lock = ((($item['private'] == Item::PRIVATE) || (($item['uid'] == local_user()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? DI::l10n()->t('Private Message') : false); - $shareable = in_array($conv->getProfileOwner(), [0, local_user()]) && $item['private'] != 1; + $shareable = in_array($conv->getProfileOwner(), [0, local_user()]) && $item['private'] != Item::PRIVATE; $edpost = false; diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 089d41729f..75dcf3dbd1 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -375,7 +375,7 @@ class Processor Logger::warning('Unknown parent item.', ['uri' => $item['thr-parent']]); return false; } - if ($item_private && !$parent['private']) { + if ($item_private && ($parent['private'] == Item::PRIVATE)) { Logger::warning('Item is private but the parent is not. Dropping.', ['item-uri' => $item['uri'], 'thr-parent' => $item['thr-parent']]); return false; } @@ -442,12 +442,19 @@ class Processor } $item['network'] = Protocol::ACTIVITYPUB; - $item['private'] = !in_array(0, $activity['receiver']); $item['author-link'] = $activity['author']; $item['author-id'] = Contact::getIdForURL($activity['author'], 0, true); $item['owner-link'] = $activity['actor']; $item['owner-id'] = Contact::getIdForURL($activity['actor'], 0, true); + if (in_array(0, $activity['receiver']) && !empty($activity['unlisted'])) { + $item['private'] = Item::UNLISTED; + } elseif (in_array(0, $activity['receiver'])) { + $item['private'] = Item::PUBLIC; + } else { + $item['private'] = Item::PRIVATE; + } + $isForum = false; if (!empty($activity['thread-completion'])) { @@ -481,6 +488,10 @@ class Processor $stored = false; foreach ($activity['receiver'] as $receiver) { + if ($receiver == -1) { + continue; + } + $item['uid'] = $receiver; if ($isForum) { @@ -530,7 +541,7 @@ class Processor } // Store send a follow request for every reshare - but only when the item had been stored - if ($stored && !$item['private'] && ($item['gravity'] == GRAVITY_PARENT) && ($item['author-link'] != $item['owner-link'])) { + if ($stored && ($item['private'] != Item::PRIVATE) && ($item['gravity'] == GRAVITY_PARENT) && ($item['author-link'] != $item['owner-link'])) { $author = APContact::getByURL($item['owner-link'], false); // We send automatic follow requests for reshared messages. (We don't need though for forum posts) if ($author['type'] != 'Group') { diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 7d25d94f17..8f23c91a3a 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -506,14 +506,15 @@ class Receiver /** * Fetch the receiver list from an activity array * - * @param array $activity - * @param string $actor - * @param array $tags + * @param array $activity + * @param string $actor + * @param array $tags + * @param boolean $fetch_unlisted * * @return array with receivers (user id) * @throws \Exception */ - private static function getReceivers($activity, $actor, $tags = []) + private static function getReceivers($activity, $actor, $tags = [], $fetch_unlisted = false) { $receivers = []; @@ -551,6 +552,11 @@ class Receiver $receivers['uid:0'] = 0; } + // Add receiver "-1" for unlisted posts + if ($fetch_unlisted && ($receiver == self::PUBLIC_COLLECTION) && ($element == 'as:cc')) { + $receivers['uid:-1'] = -1; + } + if (($receiver == self::PUBLIC_COLLECTION) && !empty($actor)) { // This will most likely catch all OStatus connections to Mastodon $condition = ['alias' => [$actor, Strings::normaliseLink($actor)], 'rel' => [Contact::SHARING, Contact::FRIEND] @@ -1022,7 +1028,9 @@ class Receiver } } - $object_data['receiver'] = self::getReceivers($object, $object_data['actor'], $object_data['tags']); + $object_data['receiver'] = self::getReceivers($object, $object_data['actor'], $object_data['tags'], true); + $object_data['unlisted'] = in_array(-1, $object_data['receiver']); + unset($object_data['receiver']['uid:-1']); // Common object data: diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 38f1022942..0973fa6e63 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -173,7 +173,7 @@ class Transmitter $public_contact = Contact::getIdForURL($owner['url'], 0, true); $condition = ['uid' => 0, 'contact-id' => $public_contact, 'author-id' => $public_contact, - 'private' => false, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], + 'private' => [Item::PUBLIC, Item::UNLISTED], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'deleted' => false, 'visible' => true, 'moderated' => false]; $count = DBA::count('item', $condition); @@ -401,7 +401,7 @@ class Transmitter $terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]); - if (!$item['private']) { + if ($item['private'] != Item::PRIVATE) { // Directly mention the original author upon a quoted reshare. // Else just ensure that the original author receives the reshare. $announce = self::getAnnounceArray($item); @@ -413,7 +413,12 @@ class Transmitter $data = array_merge($data, self::fetchPermissionBlockFromConversation($item)); - $data['to'][] = ActivityPub::PUBLIC_COLLECTION; + // Check if the item is completely public or unlisted + if ($item['private'] == Item::PUBLIC) { + $data['to'][] = ActivityPub::PUBLIC_COLLECTION; + } else { + $data['cc'][] = ActivityPub::PUBLIC_COLLECTION; + } foreach ($terms as $term) { $profile = APContact::getByURL($term['url'], false); @@ -467,13 +472,13 @@ class Transmitter $data['to'][] = $profile['url']; } else { $data['cc'][] = $profile['url']; - if (!$item['private'] && !empty($actor_profile['followers'])) { + if (($item['private'] != Item::PRIVATE) && $item['private'] && !empty($actor_profile['followers'])) { $data['cc'][] = $actor_profile['followers']; } } } else { // Public thread parent post always are directed to the followers - if (!$item['private'] && !$forum_mode) { + if (($item['private'] != Item::PRIVATE) && !$forum_mode) { $data['cc'][] = $actor_profile['followers']; } } diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index f065cd67ec..078deff7a3 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -182,7 +182,7 @@ class DFRN // default permissions - anonymous user - $sql_extra = " AND NOT `item`.`private` "; + $sql_extra = sprintf(" AND `item`.`private` != %s ", Item::PRIVATE); $r = q( "SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`, `user`.`account-type` @@ -234,7 +234,7 @@ class DFRN if (!empty($set)) { $sql_extra = " AND `item`.`psid` IN (" . implode(',', $set) .")"; } else { - $sql_extra = " AND NOT `item`.`private`"; + $sql_extra = sprintf(" AND `item`.`private` != %s", Item::PRIVATE); } } @@ -332,7 +332,7 @@ class DFRN if ($public_feed) { $type = 'html'; // catch any email that's in a public conversation and make sure it doesn't leak - if ($item['private']) { + if ($item['private'] == Item::PRIVATE) { continue; } } else { @@ -955,7 +955,7 @@ class DFRN $entry->setAttribute("xmlns:statusnet", ActivityNamespace::STATUSNET); } - if ($item['private']) { + if ($item['private'] == Item::PRIVATE) { $body = Item::fixPrivatePhotos($item['body'], $owner['uid'], $item, $cid); } else { $body = $item['body']; @@ -1050,7 +1050,8 @@ class DFRN } if ($item['private']) { - XML::addElement($doc, $entry, "dfrn:private", ($item['private'] ? $item['private'] : 1)); + // Friendica versions prior to 2020.3 can't handle "unlisted" properly. So we can only transmit public and private + XML::addElement($doc, $entry, "dfrn:private", ($item['private'] == Item::PRIVATE ? Item::PRIVATE : Item::PUBLIC)); } if ($item['extid']) { diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index fd20991108..cbec8e5503 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -2211,7 +2211,7 @@ class Diaspora return false; } - $item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => false]); + $item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => [Item::PUBLIC, Item::UNLISTED]]); if (!DBA::isResult($item)) { Logger::log('Item not found, no origin or private: '.$parent_guid); return false; @@ -2523,7 +2523,7 @@ class Diaspora // Do we already have this item? $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid', 'author-name', 'author-link', 'author-avatar']; - $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => false]; + $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; $item = Item::selectFirst($fields, $condition); if (DBA::isResult($item)) { @@ -2567,7 +2567,7 @@ class Diaspora if ($stored) { $fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid', 'author-name', 'author-link', 'author-avatar']; - $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => false]; + $condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]; $item = Item::selectFirst($fields, $condition); if (DBA::isResult($item)) { @@ -2711,7 +2711,7 @@ class Diaspora $datarray["app"] = $original_item["app"]; $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["private"] = (($public == "false") ? Item::PRIVATE : Item::PUBLIC); $datarray["changed"] = $datarray["created"] = $datarray["edited"] = $created_at; $datarray["object-type"] = $original_item["object-type"]; @@ -2941,7 +2941,7 @@ class Diaspora } $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["private"] = (($public == "false") ? Item::PRIVATE : Item::PUBLIC); $datarray["changed"] = $datarray["created"] = $datarray["edited"] = $created_at; if (isset($address["address"])) { @@ -3245,7 +3245,7 @@ class Diaspora private static function sendParticipation(array $contact, array $item) { // Don't send notifications for private postings - if ($item['private']) { + if ($item['private'] == Item::PRIVATE) { return; } @@ -3536,12 +3536,12 @@ class Diaspora $myaddr = self::myHandle($owner); - $public = ($item["private"] ? "false" : "true"); + $public = ($item["private"] == Item::PRIVATE ? "false" : "true"); $created = DateTimeFormat::utc($item['received'], DateTimeFormat::ATOM); $edited = DateTimeFormat::utc($item["edited"] ?? $item["created"], DateTimeFormat::ATOM); // Detect a share element and do a reshare - if (!$item['private'] && ($ret = self::isReshare($item["body"]))) { + if (($item['private'] != Item::PRIVATE) && ($ret = self::isReshare($item["body"]))) { $message = ["author" => $myaddr, "guid" => $item["guid"], "created_at" => $created, diff --git a/src/Protocol/Feed.php b/src/Protocol/Feed.php index cbd50a0976..4eb6c72949 100644 --- a/src/Protocol/Feed.php +++ b/src/Protocol/Feed.php @@ -220,7 +220,7 @@ class Feed { $header["wall"] = 0; $header["origin"] = 0; $header["gravity"] = GRAVITY_PARENT; - $header["private"] = 2; + $header["private"] = Item::PUBLIC; $header["verb"] = Activity::POST; $header["object-type"] = Activity\ObjectType::NOTE; diff --git a/src/Protocol/OStatus.php b/src/Protocol/OStatus.php index b5167aa72d..96b8447b4c 100644 --- a/src/Protocol/OStatus.php +++ b/src/Protocol/OStatus.php @@ -1682,7 +1682,7 @@ class OStatus $entry = self::entryHeader($doc, $owner, $item, $toplevel); - $condition = ['uid' => $owner["uid"], 'guid' => $repeated_guid, 'private' => false, + $condition = ['uid' => $owner["uid"], 'guid' => $repeated_guid, 'private' => [Item::PUBLIC, Item::UNLISTED], 'network' => [Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]]; $repeated_item = Item::selectFirst([], $condition); if (!DBA::isResult($repeated_item)) { @@ -1827,7 +1827,7 @@ class OStatus { $item["id"] = $item["parent"] = 0; $item["created"] = $item["edited"] = date("c"); - $item["private"] = true; + $item["private"] = Item::PRIVATE; $contact = Probe::uri($item['follow']); @@ -2120,7 +2120,7 @@ class OStatus ]); } - if (!$item["private"] && !$feed_mode) { + if (($item['private'] != Item::PRIVATE) && !$feed_mode) { XML::addElement($doc, $entry, "link", "", ["rel" => "ostatus:attention", "href" => "http://activityschema.org/collection/public"]); XML::addElement($doc, $entry, "link", "", ["rel" => "mentioned", @@ -2212,8 +2212,8 @@ class OStatus $authorid = Contact::getIdForURL($owner["url"], 0, true); $condition = ["`uid` = ? AND `received` > ? AND NOT `deleted` - AND NOT `private` AND `visible` AND `wall` AND `parent-network` IN (?, ?)", - $owner["uid"], $check_date, Protocol::OSTATUS, Protocol::DFRN]; + AND `private` != ? AND `visible` AND `wall` AND `parent-network` IN (?, ?)", + $owner["uid"], $check_date, Item::PRIVATE, Protocol::OSTATUS, Protocol::DFRN]; if ($filter === 'comments') { $condition[0] .= " AND `object-type` = ? "; diff --git a/src/Util/JsonLD.php b/src/Util/JsonLD.php index 1452318b52..b4ff53fdb0 100644 --- a/src/Util/JsonLD.php +++ b/src/Util/JsonLD.php @@ -122,7 +122,9 @@ class JsonLD 'ostatus' => (object)['@id' => 'http://ostatus.org#', '@type' => '@id'], 'dc' => (object)['@id' => 'http://purl.org/dc/terms/', '@type' => '@id'], 'toot' => (object)['@id' => 'http://joinmastodon.org/ns#', '@type' => '@id'], - 'litepub' => (object)['@id' => 'http://litepub.social/ns#', '@type' => '@id']]; + 'litepub' => (object)['@id' => 'http://litepub.social/ns#', '@type' => '@id'], + 'sc' => (object)['@id' => 'http://schema.org#', '@type' => '@id'], + 'pt' => (object)['@id' => 'https://joinpeertube.org/ns#', '@type' => '@id']]; // Preparation for adding possibly missing content to the context if (!empty($json['@context']) && is_string($json['@context'])) { diff --git a/src/Worker/Delivery.php b/src/Worker/Delivery.php index c69545bbd1..01f7476448 100644 --- a/src/Worker/Delivery.php +++ b/src/Worker/Delivery.php @@ -176,7 +176,7 @@ class Delivery && empty($parent['allow_gid']) && empty($parent['deny_cid']) && empty($parent['deny_gid']) - && !$parent["private"]) { + && ($parent["private"] != Model\Item::PRIVATE)) { $public_message = true; } } diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index b3741e546d..35a228fce1 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -151,6 +151,8 @@ class Notifier // If this is a public conversation, notify the feed hub $public_message = true; + $unlisted = false; + // Do a PuSH $push_notify = false; @@ -183,6 +185,8 @@ class Notifier Logger::info('Threaded comment', ['diaspora_delivery' => (int)$diaspora_delivery]); } + $unlisted = $target_item['private'] == Item::UNLISTED; + // This is IMPORTANT!!!! // We will only send a "notify owner to relay" or followup message if the referenced post @@ -245,8 +249,7 @@ class Notifier Logger::info('Followup', ['target' => $target_id, 'guid' => $target_item['guid'], 'to' => $parent['contact-id']]); - //if (!$target_item['private'] && $target_item['wall'] && - if (!$target_item['private'] && + if (($target_item['private'] != Item::PRIVATE) && (strlen($target_item['allow_cid'].$target_item['allow_gid']. $target_item['deny_cid'].$target_item['deny_gid']) == 0)) $push_notify = true; @@ -410,7 +413,7 @@ class Notifier if ($public_message && !in_array($cmd, [Delivery::MAIL, Delivery::SUGGESTION]) && !$followup) { $relay_list = []; - if ($diaspora_delivery) { + if ($diaspora_delivery && !$unlisted) { $batch_delivery = true; $relay_list_stmt = DBA::p( diff --git a/src/Worker/OnePoll.php b/src/Worker/OnePoll.php index c87bfcf25d..959d282374 100644 --- a/src/Worker/OnePoll.php +++ b/src/Worker/OnePoll.php @@ -657,11 +657,11 @@ class OnePoll $datarray['owner-avatar'] = $contact['photo']; if ($datarray['parent-uri'] === $datarray['uri']) { - $datarray['private'] = 1; + $datarray['private'] = Item::PRIVATE; } if (!DI::pConfig()->get($importer_uid, 'system', 'allow_public_email_replies')) { - $datarray['private'] = 1; + $datarray['private'] = Item::PRIVATE; $datarray['allow_cid'] = '<' . $contact['id'] . '>'; } diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 9786664601..ad9d27fc98 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -51,7 +51,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1333); + define('DB_UPDATE_VERSION', 1334); } return [ @@ -647,7 +647,7 @@ return [ "extid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "post-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Post type (personal note, bookmark, ...)"], "global" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], - "private" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "distribution is restricted"], + "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "distribution is restricted"], "visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "moderated" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been deleted"], @@ -1294,7 +1294,7 @@ return [ "received" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "changed" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], - "private" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], + "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], "pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "moderated" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], diff --git a/view/templates/settings/settings.tpl b/view/templates/settings/settings.tpl index e232ec1773..f8b1991805 100644 --- a/view/templates/settings/settings.tpl +++ b/view/templates/settings/settings.tpl @@ -53,6 +53,8 @@ {{$hide_wall nofilter}} +{{$unlisted nofilter}} + {{$blockwall nofilter}} {{$blocktags nofilter}} diff --git a/view/theme/frio/templates/settings/settings.tpl b/view/theme/frio/templates/settings/settings.tpl index 11506a70a4..8960fedeed 100644 --- a/view/theme/frio/templates/settings/settings.tpl +++ b/view/theme/frio/templates/settings/settings.tpl @@ -89,6 +89,8 @@ {{$hide_wall nofilter}} + {{$unlisted nofilter}} + {{$blockwall nofilter}} {{$blocktags nofilter}} From 4bf04dd6ee5a5a8a680773194aebaef67aa0d05f Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 12:29:08 +0000 Subject: [PATCH 2/9] Fixed fatal error --- mod/item.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mod/item.php b/mod/item.php index 4e14a047f5..47da7c3c6b 100644 --- a/mod/item.php +++ b/mod/item.php @@ -40,7 +40,6 @@ use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Attach; -use Friendica\Model\Config\PConfig; use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\FileTag; @@ -303,7 +302,7 @@ function item_post(App $a) { if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) { $private = Item::PRIVATE; - } elseif (PConfig::get($profile_uid, 'system', 'unlisted')) { + } elseif (DI::pConfig()->get($profile_uid, 'system', 'unlisted')) { $private == Item::UNLISTED; } else { $private == Item::PUBLIC; From 31f912b78e3efa6e821a67a5dc7225b655e0576b Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 14:51:53 +0000 Subject: [PATCH 3/9] Warning removed --- mod/item.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mod/item.php b/mod/item.php index 47da7c3c6b..a9f1ef808b 100644 --- a/mod/item.php +++ b/mod/item.php @@ -303,9 +303,9 @@ function item_post(App $a) { if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) { $private = Item::PRIVATE; } elseif (DI::pConfig()->get($profile_uid, 'system', 'unlisted')) { - $private == Item::UNLISTED; + $private = Item::UNLISTED; } else { - $private == Item::PUBLIC; + $private = Item::PUBLIC; } // If this is a comment, set the permissions from the parent. From d3c6f6de2a9128e2ef68c34ca551e692d503961f Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 14:59:16 +0000 Subject: [PATCH 4/9] New query for community page --- src/Module/Conversation/Community.php | 57 ++++++++------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index c65672ac07..5637c6f419 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -280,56 +280,33 @@ class Community extends BaseModule $r = false; if (self::$content == 'local') { - $values = []; - - $sql_accounttype = ''; - $sql_boundaries = ''; if (!is_null(self::$accounttype)) { - $sql_accounttype = " AND `user`.`account-type` = ?"; - $values[] = [self::$accounttype]; + $condition = ["`wall` AND `origin` AND `private` = ? AND `owner`.`contact-type` = ?", Item::PUBLIC, self::$accounttype]; + } else { + $condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC]; } - - if (isset($since_id)) { - $sql_boundaries .= " AND `thread`.`commented` > ?"; - $values[] = $since_id; - } - - if (isset($max_id)) { - $sql_boundaries .= " AND `thread`.`commented` < ?"; - $values[] = $max_id; - } - - $values[] = $itemspage; - - $r = DBA::p("SELECT `item`.`uri`, `author`.`url` AS `author-link`, `thread`.`commented` FROM `thread` - STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid` - STRAIGHT_JOIN `contact` AS `author` ON `author`.`id`=`item`.`author-id` - WHERE `thread`.`visible` AND NOT `thread`.`deleted` AND NOT `thread`.`moderated` - AND `thread`.`private` = ? AND `thread`.`wall` AND `thread`.`origin` - $sql_accounttype - $sql_boundaries - ORDER BY `thread`.`commented` DESC - LIMIT ?", Item::PUBLIC, $values); } elseif (self::$content == 'global') { if (!is_null(self::$accounttype)) { $condition = ["`uid` = ? AND `private` = ? AND `owner`.`contact-type` = ?", 0, Item::PUBLIC, self::$accounttype]; } else { $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC]; } - - if (isset($max_id)) { - $condition[0] .= " AND `commented` < ?"; - $condition[] = $max_id; - } - - if (isset($since_id)) { - $condition[0] .= " AND `commented` > ?"; - $condition[] = $since_id; - } - - $r = Item::selectThreadForUser(0, ['uri', 'commented'], $condition, ['order' => ['commented' => true], 'limit' => $itemspage]); + } else { + return []; } + if (isset($max_id)) { + $condition[0] .= " AND `commented` < ?"; + $condition[] = $max_id; + } + + if (isset($since_id)) { + $condition[0] .= " AND `commented` > ?"; + $condition[] = $since_id; + } + + $r = Item::selectThreadForUser(0, ['uri', 'commented', 'author-link'], $condition, ['order' => ['commented' => true], 'limit' => $itemspage]); + return DBA::toArray($r); } } From 3a6d3cd4392baec94e3bdb591072f3bc0d45a137 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 17:20:18 +0000 Subject: [PATCH 5/9] Fixing tests --- include/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/api.php b/include/api.php index 62c69767f3..3a29184ec7 100644 --- a/include/api.php +++ b/include/api.php @@ -783,7 +783,7 @@ function api_item_get_user(App $a, $item) $author_user = $status_user; - $status_user["protected"] = $item['private'] == Item::PRIVATE; + $status_user["protected"] = isset($item['private']) && ($item['private'] == Item::PRIVATE); if (($item['thr-parent'] ?? '') == ($item['uri'] ?? '')) { $owner_user = api_get_user($a, $item['owner-id'] ?? null); From 530ecb27cfec60614ce7730e5b29749d58c1558a Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 20:31:16 +0000 Subject: [PATCH 6/9] Updated database.sql --- database.sql | 35 +++++++++++++++++------------------ 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/database.sql b/database.sql index 9d4086994a..6fa67d743e 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2020.03-dev (Dalmatian Bellflower) --- DB_UPDATE_VERSION 1333 +-- DB_UPDATE_VERSION 1334 -- ------------------------------------------ @@ -179,7 +179,7 @@ CREATE TABLE IF NOT EXISTS `contact` ( `location` varchar(255) DEFAULT '' COMMENT '', `about` text COMMENT '', `keywords` text COMMENT 'public keywords (interests) of the contact', - `gender` varchar(32) NOT NULL DEFAULT '' COMMENT '', + `gender` varchar(32) NOT NULL DEFAULT '' COMMENT 'Deprecated', `xmpp` varchar(255) NOT NULL DEFAULT '' COMMENT '', `attag` varchar(255) NOT NULL DEFAULT '' COMMENT '', `avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '', @@ -397,7 +397,7 @@ CREATE TABLE IF NOT EXISTS `gcontact` ( `location` varchar(255) NOT NULL DEFAULT '' COMMENT '', `about` text COMMENT '', `keywords` text COMMENT 'puplic keywords (interests)', - `gender` varchar(32) NOT NULL DEFAULT '' COMMENT '', + `gender` varchar(32) NOT NULL DEFAULT '' COMMENT 'Deprecated', `birthday` varchar(32) NOT NULL DEFAULT '0001-01-01' COMMENT '', `community` boolean NOT NULL DEFAULT '0' COMMENT '1 if contact is forum account', `contact-type` tinyint NOT NULL DEFAULT -1 COMMENT '', @@ -568,7 +568,7 @@ CREATE TABLE IF NOT EXISTS `item` ( `extid` varchar(255) NOT NULL DEFAULT '' COMMENT '', `post-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Post type (personal note, bookmark, ...)', `global` boolean NOT NULL DEFAULT '0' COMMENT '', - `private` boolean NOT NULL DEFAULT '0' COMMENT 'distribution is restricted', + `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'distribution is restricted', `visible` boolean NOT NULL DEFAULT '0' COMMENT '', `moderated` boolean NOT NULL DEFAULT '0' COMMENT '', `deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been deleted', @@ -1057,19 +1057,18 @@ CREATE TABLE IF NOT EXISTS `profile_check` ( -- TABLE profile_field -- CREATE TABLE IF NOT EXISTS `profile_field` ( - `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'sequential ID', - `uid` mediumint(8) unsigned NOT NULL DEFAULT 0 COMMENT 'Owner user id', - `psid` int(10) unsigned DEFAULT NULL COMMENT 'ID of the permission set of this profile field - 0 = public', - `name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Name of the field', - `value` text COMMENT 'Value of the field', - `order` mediumint(8) unsigned NOT NULL DEFAULT 1 COMMENT 'Field ordering per user', - `label` varchar(255) NOT NULL DEFAULT '' COMMENT 'Label of the field', - `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', - `edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', - PRIMARY KEY (`id`), - KEY `uid` (`uid`), - KEY `psid` (`psid`), - KEY `order` (`order`) + `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', + `uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner user id', + `order` mediumint unsigned NOT NULL DEFAULT 1 COMMENT 'Field ordering per user', + `psid` int unsigned COMMENT 'ID of the permission set of this profile field - 0 = public', + `label` varchar(255) NOT NULL DEFAULT '' COMMENT 'Label of the field', + `value` text COMMENT 'Value of the field', + `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'creation time', + `edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'last edit time', + PRIMARY KEY(`id`), + INDEX `uid` (`uid`), + INDEX `order` (`order`), + INDEX `psid` (`psid`) ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Custom profile fields'; -- @@ -1179,7 +1178,7 @@ CREATE TABLE IF NOT EXISTS `thread` ( `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `changed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `wall` boolean NOT NULL DEFAULT '0' COMMENT '', - `private` boolean NOT NULL DEFAULT '0' COMMENT '', + `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', `pubmail` boolean NOT NULL DEFAULT '0' COMMENT '', `moderated` boolean NOT NULL DEFAULT '0' COMMENT '', `visible` boolean NOT NULL DEFAULT '0' COMMENT '', From dc9ea15bb16c71bcb7720a6716b1d2e64adc0c9e Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 20:48:26 +0000 Subject: [PATCH 7/9] Updated field description --- database.sql | 4 ++-- static/dbstructure.config.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/database.sql b/database.sql index 6fa67d743e..04a35634e0 100644 --- a/database.sql +++ b/database.sql @@ -568,7 +568,7 @@ CREATE TABLE IF NOT EXISTS `item` ( `extid` varchar(255) NOT NULL DEFAULT '' COMMENT '', `post-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Post type (personal note, bookmark, ...)', `global` boolean NOT NULL DEFAULT '0' COMMENT '', - `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'distribution is restricted', + `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted', `visible` boolean NOT NULL DEFAULT '0' COMMENT '', `moderated` boolean NOT NULL DEFAULT '0' COMMENT '', `deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been deleted', @@ -1178,7 +1178,7 @@ CREATE TABLE IF NOT EXISTS `thread` ( `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `changed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `wall` boolean NOT NULL DEFAULT '0' COMMENT '', - `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', + `private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted', `pubmail` boolean NOT NULL DEFAULT '0' COMMENT '', `moderated` boolean NOT NULL DEFAULT '0' COMMENT '', `visible` boolean NOT NULL DEFAULT '0' COMMENT '', diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index ad9d27fc98..5b2e3bc091 100755 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -647,7 +647,7 @@ return [ "extid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""], "post-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Post type (personal note, bookmark, ...)"], "global" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], - "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "distribution is restricted"], + "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "0=public, 1=private, 2=unlisted"], "visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "moderated" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "item has been deleted"], @@ -1294,7 +1294,7 @@ return [ "received" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "changed" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""], "wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], - "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], + "private" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "0=public, 1=private, 2=unlisted"], "pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "moderated" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "visible" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], From a19151fa6ac05a10ea70354142865a16121be6bf Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 2 Mar 2020 22:35:25 +0000 Subject: [PATCH 8/9] Improved description, support for "unlisted" via DFRN as well. --- mod/settings.php | 2 +- src/Protocol/DFRN.php | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mod/settings.php b/mod/settings.php index dd3303892b..47505bd726 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -840,7 +840,7 @@ function settings_content(App $a) ]); $unlisted = Renderer::replaceMacros($opt_tpl, [ - '$field' => ['unlisted', DI::l10n()->t('Should public posts be unlisted?'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community page or in search results, nor will they be transported to relay servers.')], + '$field' => ['unlisted', DI::l10n()->t('Should public posts be unlisted?'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community page or in search results, nor will they be transported to relay servers. However they can appear on public feeds from other servers.')], ]); $blockwall = Renderer::replaceMacros($opt_tpl, [ diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 078deff7a3..4c88db1d94 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1052,6 +1052,7 @@ class DFRN if ($item['private']) { // Friendica versions prior to 2020.3 can't handle "unlisted" properly. So we can only transmit public and private XML::addElement($doc, $entry, "dfrn:private", ($item['private'] == Item::PRIVATE ? Item::PRIVATE : Item::PUBLIC)); + XML::addElement($doc, $entry, "dfrn:unlisted", $item['private'] == Item::UNLISTED); } if ($item['extid']) { @@ -2405,6 +2406,11 @@ class DFRN $item["private"] = XML::getFirstNodeValue($xpath, "dfrn:private/text()", $entry); + $unlisted = XML::getFirstNodeValue($xpath, "dfrn:unlisted/text()", $entry); + if (!empty($unlisted) && ($item['private'] != Item::PRIVATE)) { + $item['private'] = Item::UNLISTED; + } + $item["extid"] = XML::getFirstNodeValue($xpath, "dfrn:extid/text()", $entry); if (XML::getFirstNodeValue($xpath, "dfrn:bookmark/text()", $entry) == "true") { From 55f43be92a8f85cf7477b038af53a98cbc138ae6 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 3 Mar 2020 04:03:37 +0000 Subject: [PATCH 9/9] Changed setting description --- mod/settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/settings.php b/mod/settings.php index 47505bd726..9a73b83e6b 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -840,7 +840,7 @@ function settings_content(App $a) ]); $unlisted = Renderer::replaceMacros($opt_tpl, [ - '$field' => ['unlisted', DI::l10n()->t('Should public posts be unlisted?'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community page or in search results, nor will they be transported to relay servers. However they can appear on public feeds from other servers.')], + '$field' => ['unlisted', DI::l10n()->t('Make public posts unlisted'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community pages or in search results, nor be sent to relay servers. However they can still appear on public feeds on remote servers.')], ]); $blockwall = Renderer::replaceMacros($opt_tpl, [