diff --git a/mod/display.php b/mod/display.php deleted file mode 100644 index 62e180874..000000000 --- a/mod/display.php +++ /dev/null @@ -1,368 +0,0 @@ -. - * - */ - -use Friendica\App; -use Friendica\Content\Text\BBCode; -use Friendica\Content\Widget; -use Friendica\Core\Logger; -use Friendica\Core\Renderer; -use Friendica\Core\System; -use Friendica\Database\DBA; -use Friendica\DI; -use Friendica\Model\Contact; -use Friendica\Model\Item; -use Friendica\Model\Post; -use Friendica\Model\User; -use Friendica\Module\ActivityPub\Objects; -use Friendica\Module\Response; -use Friendica\Network\HTTPException; -use Friendica\Protocol\ActivityPub; -use Friendica\Protocol\DFRN; -use Friendica\Protocol\Diaspora; -use Friendica\Util\DateTimeFormat; - -function display_init(App $a) -{ - if (ActivityPub::isRequest()) { - (new Objects(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), $_SERVER, ['guid' => DI::args()->getArgv()[1] ?? null]))->run(); - } - - if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) { - return; - } - - $nick = ((DI::args()->getArgc() > 1) ? DI::args()->getArgv()[1] : ''); - - $item = null; - $item_user = DI::userSession()->getLocalUserId(); - - $fields = ['uri-id', 'parent-uri-id', 'author-id', 'author-link', 'body', 'uid', 'guid', 'gravity']; - - // If there is only one parameter, then check if this parameter could be a guid - if (DI::args()->getArgc() == 2) { - $nick = ''; - - // Does the local user have this item? - if (DI::userSession()->getLocalUserId()) { - $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, ['guid' => DI::args()->getArgv()[1], 'uid' => DI::userSession()->getLocalUserId()]); - if (DBA::isResult($item)) { - $nick = $a->getLoggedInUserNickname(); - } - } - - // Is this item private but could be visible to the remove visitor? - if (!DBA::isResult($item) && DI::userSession()->getRemoteUserId()) { - $item = Post::selectFirst($fields, ['guid' => DI::args()->getArgv()[1], 'private' => Item::PRIVATE, 'origin' => true]); - if (DBA::isResult($item)) { - if (!Contact::isFollower(DI::userSession()->getRemoteUserId(), $item['uid'])) { - $item = null; - } else { - $item_user = $item['uid']; - } - } - } - - // Is it an item with uid=0? - if (!DBA::isResult($item)) { - $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, ['guid' => DI::args()->getArgv()[1], 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]); - } - } elseif (DI::args()->getArgc() >= 3 && $nick == 'feed-item') { - $uri_id = DI::args()->getArgv()[2]; - if (substr($uri_id, -5) == '.atom') { - $uri_id = substr($uri_id, 0, -5); - } - $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, ['uri-id' => $uri_id, 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]); - } - - if (!DBA::isResult($item)) { - return; - } - - if (DI::args()->getArgc() >= 3 && $nick == 'feed-item') { - displayShowFeed($item['uri-id'], $item['uid'], DI::args()->getArgc() > 3 && DI::args()->getArgv()[3] == 'conversation.atom'); - } - - if (!empty($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'application/atom+xml')) { - Logger::debug('Directly serving XML', ['uri-id' => $item['uri-id']]); - displayShowFeed($item['uri-id'], $item['uid'], false); - } - - if ($item['gravity'] != Item::GRAVITY_PARENT) { - $parent = Post::selectFirstForUser($item_user, $fields, ['uid' => [0, $item_user], 'uri-id' => $item['parent-uri-id']], ['order' => ['uid' => true]]); - $item = $parent ?: $item; - } - - $author = display_fetchauthor($item); - - if (\Friendica\Util\Network::isLocalLink($author['url'])) { - \Friendica\Model\Profile::load(DI::app(), $author['nick'], false); - } else { - DI::page()['aside'] = Widget\VCard::getHTML($author); - } - $a->setProfileOwner($item['uid']); -} - -function display_fetchauthor($item) -{ - $shared = DI::contentItem()->getSharedPost($item, ['author-link']); - if (!empty($shared) && empty($shared['comment'])) { - $contact = Contact::getByURLForUser($shared['post']['author-link'], DI::userSession()->getLocalUserId()); - } - - if (empty($contact)) { - $contact = Contact::getById($item['author-id']); - } - - return $contact; -} - -function display_content(App $a, $update = false, $update_uid = 0) -{ - if (DI::config()->get('system','block_public') && !DI::userSession()->isAuthenticated()) { - throw new HTTPException\ForbiddenException(DI::l10n()->t('Public access denied.')); - } - - $o = ''; - - $item = null; - - $force = (bool)($_REQUEST['force'] ?? false); - - if ($update) { - $uri_id = $_REQUEST['uri_id']; - $item = Post::selectFirst(['uid', 'parent-uri-id'], ['uri-id' => $uri_id, 'uid' => [0, $update_uid]], ['order' => ['uid' => true]]); - if (!empty($item)) { - if ($item['uid'] != 0) { - $a->setProfileOwner($item['uid']); - } else { - $a->setProfileOwner($update_uid); - } - $parent_uri_id = $item['parent-uri-id']; - } - if (empty($_REQUEST['force'])) { - $browser_update = intval(DI::pConfig()->get($update_uid, 'system', 'update_interval')); - if (!empty($browser_update)) { - $update_date = date(DateTimeFormat::MYSQL, time() - ($browser_update / 500)); - if (!Post::exists(["`parent-uri-id` = ? AND `uid` IN (?, ?) AND `received` > ?", $parent_uri_id, 0, $update_uid, $update_date])) { - Logger::debug('No updated content', ['uri-id' => $uri_id, 'uid' => $update_uid, 'updated' => $update_date]); - return ''; - } else { - Logger::debug('Updated content found', ['uri-id' => $uri_id, 'uid' => $update_uid, 'updated' => $update_date]); - } - } - } else { - Logger::debug('Forced content update', ['uri-id' => $uri_id, 'uid' => $update_uid]); - } - } else { - $uri_id = ((DI::args()->getArgc() > 2) ? DI::args()->getArgv()[2] : 0); - $parent_uri_id = $uri_id; - - if (DI::args()->getArgc() == 2) { - $fields = ['uri-id', 'parent-uri-id', 'uid']; - - if (DI::userSession()->getLocalUserId()) { - $condition = ['guid' => DI::args()->getArgv()[1], 'uid' => [0, DI::userSession()->getLocalUserId()]]; - $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, $condition, ['order' => ['uid' => true]]); - if (DBA::isResult($item)) { - $uri_id = $item['uri-id']; - $parent_uri_id = $item['parent-uri-id']; - } - } - - if (($parent_uri_id == 0) && DI::userSession()->getRemoteUserId()) { - $item = Post::selectFirst($fields, ['guid' => DI::args()->getArgv()[1], 'private' => Item::PRIVATE, 'origin' => true]); - if (DBA::isResult($item) && Contact::isFollower(DI::userSession()->getRemoteUserId(), $item['uid'])) { - $uri_id = $item['uri-id']; - $parent_uri_id = $item['parent-uri-id']; - } - } - - if ($parent_uri_id == 0) { - $condition = ['private' => [Item::PUBLIC, Item::UNLISTED], 'guid' => DI::args()->getArgv()[1], 'uid' => 0]; - $item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), $fields, $condition); - if (DBA::isResult($item)) { - $uri_id = $item['uri-id']; - $parent_uri_id = $item['parent-uri-id']; - } - } - } - } - - if (empty($item)) { - throw new HTTPException\NotFoundException(DI::l10n()->t('The requested item doesn\'t exist or has been deleted.')); - } - - if (!DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'detailed_notif')) { - DI::notification()->setAllSeenForUser(DI::userSession()->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); - DI::notify()->setAllSeenForUser(DI::userSession()->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); - } - - // We are displaying an "alternate" link if that post was public. See issue 2864 - $is_public = Post::exists(['uri-id' => $uri_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/'.$uri_id.'.atom'; - $conversation = DI::baseUrl().'/display/feed-item/' . $parent_uri_id . '/conversation.atom'; - } else { - $alternate = ''; - $conversation = ''; - } - - DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('display-head.tpl'), - ['$alternate' => $alternate, - '$conversation' => $conversation]); - - $is_remote_contact = false; - $item_uid = DI::userSession()->getLocalUserId(); - $page_uid = 0; - - $parent = null; - if (!DI::userSession()->getLocalUserId() && !empty($parent_uri_id)) { - $parent = Post::selectFirst(['uid'], ['uri-id' => $parent_uri_id, 'wall' => true]); - } - - if (DBA::isResult($parent)) { - $page_uid = $page_uid ?? 0 ?: $parent['uid']; - $is_remote_contact = DI::userSession()->getRemoteContactID($page_uid); - if ($is_remote_contact) { - $item_uid = $parent['uid']; - } - } else { - $page_uid = $item['uid']; - } - - if (!empty($page_uid) && ($page_uid != DI::userSession()->getLocalUserId())) { - $page_user = User::getById($page_uid); - } - - $is_owner = DI::userSession()->getLocalUserId() && (in_array($page_uid, [DI::userSession()->getLocalUserId(), 0])); - - if (!empty($page_user['hidewall']) && !$is_owner && !$is_remote_contact) { - throw new HTTPException\ForbiddenException(DI::l10n()->t('Access to this profile has been restricted.')); - } - - // We need the editor here to be able to reshare an item. - if ($is_owner && !$update) { - $o .= DI::conversation()->statusEditor([], 0, true); - } - $sql_extra = Item::getPermissionsSQLByUserId($page_uid); - - if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $page_uid)) { - $condition = ['parent-uri-id' => $parent_uri_id, 'uid' => DI::userSession()->getLocalUserId(), 'unseen' => true]; - $unseen = Post::exists($condition); - } else { - $unseen = false; - } - - if ($update && !$unseen && !$force) { - return ''; - } - - $condition = ["`uri-id` = ? AND `uid` IN (0, ?) " . $sql_extra, $uri_id, $item_uid]; - $fields = ['parent-uri-id', 'body', 'title', 'author-name', 'author-avatar', 'plink', 'author-id', 'owner-id', 'contact-id']; - $item = Post::selectFirstForUser($page_uid, $fields, $condition); - - if (!DBA::isResult($item)) { - throw new HTTPException\NotFoundException(DI::l10n()->t('The requested item doesn\'t exist or has been deleted.')); - } - - $item['uri-id'] = $item['parent-uri-id']; - - if ($unseen) { - $condition = ['parent-uri-id' => $parent_uri_id, 'uid' => DI::userSession()->getLocalUserId(), 'unseen' => true]; - Item::update(['unseen' => false], $condition); - } - - if (!$update && DI::userSession()->getLocalUserId()) { - $o .= ""; - } - - $o .= DI::conversation()->create([$item], 'display', $update_uid, false, 'commented', $item_uid); - - // Preparing the meta header - $description = trim(BBCode::toPlaintext($item['body'])); - $title = trim(BBCode::toPlaintext($item['title'] ?? '')); - $author_name = $item['author-name']; - - $image = DI::baseUrl()->remove($item['author-avatar']); - - if ($title == '') { - $title = $author_name; - } - - // Limit the description to 160 characters - if (strlen($description) > 160) { - $description = substr($description, 0, 157) . '...'; - } - - $description = htmlspecialchars($description, ENT_COMPAT, 'UTF-8', true); // allow double encoding here - $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8', true); // allow double encoding here - $author_name = htmlspecialchars($author_name, ENT_COMPAT, 'UTF-8', true); // allow double encoding here - - $page = DI::page(); - - if (DBA::exists('contact', ['unsearchable' => true, 'id' => [$item['contact-id'], $item['author-id'], $item['owner-id']]])) { - $page['htmlhead'] .= '' . "\n"; - } - - DI::page()['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - - // Schema.org microdata - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - - // Twitter cards - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - - // Dublin Core - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - - // Open Graph - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - $page['htmlhead'] .= ''."\n"; - // article:tag - - return $o; -} - -function displayShowFeed(int $uri_id, int $uid, bool $conversation) -{ - $xml = DFRN::itemFeed($uri_id, $uid, $conversation); - if ($xml == '') { - throw new HTTPException\InternalServerErrorException(DI::l10n()->t('The feed for this item is unavailable.')); - } - - System::httpExit($xml, Response::TYPE_ATOM); -} diff --git a/mod/update_display.php b/mod/update_display.php deleted file mode 100644 index 581d5d97a..000000000 --- a/mod/update_display.php +++ /dev/null @@ -1,36 +0,0 @@ -. - * - * See update_profile.php for documentation - */ - -use Friendica\App; -use Friendica\Core\System; -use Friendica\DI; - -require_once "mod/display.php"; - -function update_display_content(App $a) -{ - $profile_uid = intval($_GET["p"]); - - $text = display_content($a, true, $profile_uid); - - System::htmlUpdateExit($text); -} diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 522de7b57..ec317824b 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -493,7 +493,7 @@ class Conversation if (!$update) { $live_update_div = '
' . "\r\n" . "\r\n"; + . "; var netargs = '?f='; \r\n"; } } elseif ($mode === 'display') { $items = $this->addChildren($items, false, $order, $uid, $mode); @@ -520,7 +520,7 @@ class Conversation if (!$update) { $live_update_div = '' . "\r\n" . "\r\n"; + ."?f='; \r\n"; } } elseif ($mode === 'search') { $live_update_div = '' . "\r\n"; diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 8239708e1..cd4e52acd 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -3476,4 +3476,17 @@ class Contact return []; } + + /** + * Checks, if contacts with the given condition exists + * + * @param array $condition + * + * @return bool + * @throws \Exception + */ + public static function exists(array $condition): bool + { + return DBA::exists('contact', $condition); + } } diff --git a/src/Module/Item/Display.php b/src/Module/Item/Display.php new file mode 100644 index 000000000..42112a3e0 --- /dev/null +++ b/src/Module/Item/Display.php @@ -0,0 +1,361 @@ +. + * + */ + +namespace Friendica\Module\Item; + +use Friendica\App; +use Friendica\BaseModule; +use Friendica\Content\Conversation; +use Friendica\Content\Item as ContentItem; +use Friendica\Content\Text\BBCode; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; +use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Model\Contact; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Model\Profile; +use Friendica\Model\User; +use Friendica\Module\Response; +use Friendica\Navigation\Notifications\Repository\Notification; +use Friendica\Navigation\Notifications\Repository\Notify; +use Friendica\Protocol\ActivityPub; +use Friendica\Util\Network; +use Friendica\Util\Profiler; +use Friendica\Network\HTTPException; +use Friendica\Content\Widget; +use Psr\Log\LoggerInterface; + +/** + * Controller to display one item and its conversation + */ +class Display extends BaseModule +{ + /** @var App\Page */ + protected $page; + /** @var IManageConfigValues */ + protected $config; + /** @var IManagePersonalConfigValues */ + protected $pConfig; + /** @var IHandleUserSessions */ + protected $session; + /** @var App */ + protected $app; + /** @var ContentItem */ + protected $contentItem; + /** @var Conversation */ + protected $conversation; + /** @var Notification */ + protected $notification; + /** @var Notify */ + protected $notify; + + public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, App $app, App\Page $page, ContentItem $contentItem, Conversation $conversation, Notification $notification, Notify $notify, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->page = $page; + $this->config = $config; + $this->pConfig = $pConfig; + $this->session = $session; + $this->app = $app; + $this->contentItem = $contentItem; + $this->conversation = $conversation; + $this->notification = $notification; + $this->notify = $notify; + } + + protected function content(array $request = []): string + { + if (ActivityPub::isRequest()) { + $this->baseUrl->redirect(str_replace('display/', 'objects/', $this->args->getQueryString())); + } + + if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) { + throw new HTTPException\UnauthorizedException($this->t('Access denied.')); + } + + $guid = $this->parameters['guid'] ?? 0; + + $item = null; + $itemUid = $this->session->getLocalUserId(); + + $fields = ['uri-id', 'parent-uri-id', 'author-id', 'author-link', 'body', 'uid', 'guid', 'gravity']; + + // Does the local user have this item? + if ($this->session->getLocalUserId()) { + $item = Post::selectFirstForUser($this->session->getLocalUserId(), $fields, [ + 'guid' => $guid, + 'uid' => $this->session->getLocalUserId() + ]); + } + + // Is this item private but could be visible to the remove visitor? + if (empty($item) && $this->session->getRemoteUserId()) { + $item = Post::selectFirst($fields, ['guid' => $guid, 'private' => Item::PRIVATE, 'origin' => true]); + if (!empty($item)) { + if (!Contact::isFollower($this->session->getRemoteUserId(), $item['uid'])) { + $item = null; + } else { + $itemUid = $item['uid']; + } + } + } + + // Is it an item with uid = 0? + if (empty($item)) { + $item = Post::selectFirstForUser($this->session->getLocalUserId(), $fields, [ + 'guid' => $guid, + 'private' => [Item::PUBLIC, Item::UNLISTED], + 'uid' => 0 + ]); + } + + if (empty($item)) { + throw new HTTPException\NotFoundException($this->t('The requested item doesn\'t exist or has been deleted.')); + } + + if ($item['gravity'] != Item::GRAVITY_PARENT) { + $parent = Post::selectFirstForUser($itemUid, $fields, [ + 'uid' => [0, $itemUid], + 'uri-id' => $item['parent-uri-id'] + ], ['order' => ['uid' => true]]); + + $item = $parent ?: $item; + } + + if (!$this->pConfig->get($this->session->getLocalUserId(), 'system', 'detailed_notif')) { + $this->notification->setAllSeenForUser($this->session->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); + $this->notify->setAllSeenForUser($this->session->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); + } + + $this->displaySidebar($item); + $this->displayHead($item['uri-id'], $item['parent-uri-id']); + + $output = ''; + + // add the uri-id to the update_display parameter + if ($this->session->getLocalUserId()) { + $output .= ""; + } + + $output .= $this->getDisplayData($item); + + return $output; + } + + /** + * Loads the content for the sidebar of the display page + * + * @param array $item The current item + * + * @return void + * @throws HTTPException\InternalServerErrorException + * @throws HTTPException\NotFoundException + * @throws \ImagickException + */ + protected function displaySidebar(array $item) + { + $shared = $this->contentItem->getSharedPost($item, ['author-link']); + if (!empty($shared) && empty($shared['comment'])) { + $author = Contact::getByURLForUser($shared['post']['author-link'], $this->session->getLocalUserId()); + } + + if (empty($contact)) { + $author = Contact::getById($item['author-id']); + } + + if (Network::isLocalLink($author['url'])) { + Profile::load($this->app, $author['nick'], false); + } else { + $this->page['aside'] = Widget\VCard::getHTML($author); + } + + $this->app->setProfileOwner($item['uid']); + } + + protected function getDisplayData(array $item, bool $update = false, int $updateUid = 0, bool $force = false): string + { + $isRemoteContact = false; + $itemUid = $this->session->getLocalUserId(); + + $parent = null; + if (!$this->session->getLocalUserId() && !empty($item['parent-uri-id'])) { + $parent = Post::selectFirst(['uid'], ['uri-id' => $item['parent-uri-id'], 'wall' => true]); + } + + if (!empty($parent)) { + $pageUid = $parent['uid']; + $isRemoteContact = $this->session->getRemoteContactID($pageUid); + if ($isRemoteContact) { + $itemUid = $parent['uid']; + } + } else { + $pageUid = $item['uid']; + } + + if (!empty($pageUid) && ($pageUid != $this->session->getLocalUserId())) { + $page_user = User::getById($pageUid, ['hidewall']); + } + + $is_owner = $this->session->getLocalUserId() && (in_array($pageUid, [$this->session->getLocalUserId(), 0])); + + if (!empty($page_user['hidewall']) && !$is_owner && !$isRemoteContact) { + throw new HTTPException\ForbiddenException($this->t('Access to this profile has been restricted.')); + } + + $sql_extra = Item::getPermissionsSQLByUserId($pageUid); + + if ($this->session->getLocalUserId() && ($this->session->getLocalUserId() == $pageUid)) { + $unseen = Post::exists([ + 'parent-uri-id' => $item['parent-uri-id'], + 'uid' => $this->session->getLocalUserId(), + 'unseen' => true + ]); + } else { + $unseen = false; + } + + if ($update && !$unseen && !$force) { + return ''; + } + + $condition = ["`uri-id` = ? AND `uid` IN (0, ?) " . $sql_extra, $item['uri-id'], $itemUid]; + $fields = [ + 'parent-uri-id', 'body', 'title', 'author-name', 'author-avatar', 'plink', 'author-id', + 'owner-id', 'contact-id' + ]; + + $item = Post::selectFirstForUser($pageUid, $fields, $condition); + + if (empty($item)) { + throw new HTTPException\NotFoundException($this->t('The requested item doesn\'t exist or has been deleted.')); + } + + $item['uri-id'] = $item['parent-uri-id']; + + if ($unseen) { + $condition = [ + 'parent-uri-id' => $item['parent-uri-id'], + 'uid' => $this->session->getLocalUserId(), + 'unseen' => true + ]; + Item::update(['unseen' => false], $condition); + } + + $this->addMetaTags($item); + + $output = ''; + + // We need the editor here to be able to reshare an item. + if ($is_owner && !$update) { + $output .= $this->conversation->statusEditor([], 0, true); + } + + $output .= $this->conversation->create([$item], 'display', $updateUid, false, 'commented', $itemUid); + + return $output; + } + + // We are displaying an "alternate" link if that post was public. See issue 2864 + protected function displayHead(string $uriId, string $parentUriId) + { + if (Post::exists(['uri-id' => $uriId, 'private' => [Item::PUBLIC, Item::UNLISTED]])) { + // For the atom feed the nickname doesn't matter at all, we only need the item id. + $this->page['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('display-head.tpl'), [ + '$alternate' => sprintf('display/feed-item/%s.atom', $uriId), + '$conversation' => sprintf('display/feed-item/%s/conversation.atom', $parentUriId) + ]); + } + } + + /** + * Adds tags to the HTML output based on an item + * + * @param array $item The item with the information for the tags + * + * @return void + * @throws \Exception + */ + protected function addMetaTags(array $item) + { + // Preparing the meta header + $description = trim(BBCode::toPlaintext($item['body'])); + $title = trim(BBCode::toPlaintext($item['title'] ?? '')); + $author_name = $item['author-name']; + + $image = $this->baseUrl->remove($item['author-avatar']); + + if ($title === '') { + $title = $author_name; + } + + // Limit the description to 160 characters + if (strlen($description) > 160) { + $description = substr($description, 0, 157) . '...'; + } + + $description = htmlspecialchars($description, ENT_COMPAT, 'UTF-8', true); // allow double encoding here + $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8', true); // allow double encoding here + $author_name = htmlspecialchars($author_name, ENT_COMPAT, 'UTF-8', true); // allow double encoding here + + $page = $this->page; + + if (Contact::exists([ + 'unsearchable' => true, 'id' => [$item['contact-id'], $item['author-id'], $item['owner-id']] + ])) { + $page['htmlhead'] .= "\n"; + } + + $page['htmlhead'] .= sprintf("\n", $author_name); + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $description); + + // Schema.org microdata + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $description); + $page['htmlhead'] .= sprintf("\n", $image); + $page['htmlhead'] .= sprintf("\n", $author_name); + + // Twitter cards + $page['htmlhead'] .= "\n"; + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $description); + $page['htmlhead'] .= sprintf("\n", $this->baseUrl, $image); + $page['htmlhead'] .= sprintf("\n", $item["plink"]); + + // Dublin Core + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $description); + + // Open Graph + $page['htmlhead'] .= "\n"; + $page['htmlhead'] .= sprintf("\n", $title); + $page['htmlhead'] .= sprintf("\n", $this->baseUrl, $image); + $page['htmlhead'] .= sprintf("\n", $item["plink"]); + $page['htmlhead'] .= sprintf("\n", $description); + $page['htmlhead'] .= sprintf("\n", $author_name); + // article:tag + } +} diff --git a/src/Module/Item/Feed.php b/src/Module/Item/Feed.php new file mode 100644 index 000000000..50447afba --- /dev/null +++ b/src/Module/Item/Feed.php @@ -0,0 +1,91 @@ +. + * + */ + +namespace Friendica\Module\Item; + +use Friendica\App; +use Friendica\BaseModule; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\L10n; +use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Core\System; +use Friendica\Model\Item; +use Friendica\Model\Post; +use Friendica\Module\Response; +use Friendica\Protocol\DFRN; +use Friendica\Util\Profiler; +use Friendica\Network\HTTPException; +use Psr\Log\LoggerInterface; + +/** + * Controller to display an item (or the whole conversation of an item) as an ATOM Feed + */ +class Feed extends BaseModule +{ + /** @var IManageConfigValues */ + protected $config; + /** @var IHandleUserSessions */ + protected $session; + + public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, IHandleUserSessions $session, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->config = $config; + $this->session = $session; + } + + protected function rawContent(array $request = []) + { + if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) { + throw new HTTPException\UnauthorizedException($this->t('Access denied.')); + } + + $uriId = $this->parameters['uri-id']; + + $item = Post::selectFirstForUser($this->session->getLocalUserId(), [ + 'uri-id', + 'parent-uri-id', + 'author-id', + 'author-link', + 'body', + 'uid', + 'guid', + 'gravity', + ], [ + 'uri-id' => $uriId, + 'private' => [Item::PUBLIC, Item::UNLISTED], + 'uid' => 0, + ]); + + if (empty($item)) { + throw new HTTPException\BadRequestException($this->t('Item not found.', ['uri-id' => $uriId])); + } + + $xml = DFRN::itemFeed($item['uri-id'], $item['uid'], ($this->parameters['mode'] ?? '') === 'conversation'); + + if (empty($xml)) { + throw new HTTPException\InternalServerErrorException($this->t('The feed for this item is unavailable.', ['uri-id' => $uriId])); + } + + System::httpExit($xml, Response::TYPE_ATOM); + } +} diff --git a/src/Module/Update/Display.php b/src/Module/Update/Display.php new file mode 100644 index 000000000..4b84cba7a --- /dev/null +++ b/src/Module/Update/Display.php @@ -0,0 +1,88 @@ +. + * + */ + +namespace Friendica\Module\Update; + +use Friendica\Model\Post; +use Friendica\Module\Item\Display as DisplayModule; +use Friendica\Util\DateTimeFormat; +use Friendica\Network\HTTPException; + +/** + * Asynchronous update class for the display + */ +class Display extends DisplayModule +{ + protected function content(array $request = []): string + { + if ($this->config->get('system', 'block_public') && !$this->session->isAuthenticated()) { + throw new HTTPException\UnauthorizedException($this->t('Access denied.')); + } + + $profileUid = $request['p'] ?? 0; + $force = $request['force'] ?? false; + $uriId = $request['uri_id'] ?? 0; + + if (empty($uriId)) { + throw new HTTPException\BadRequestException($this->t('Parameter uri_id is missing.')); + } + + $item = Post::selectFirst( + ['uid', 'parent-uri-id', 'uri-id'], + ['uri-id' => $uriId, 'uid' => [0, $profileUid]], + ['order' => ['uid' => true]] + ); + + if (empty($item)) { + throw new HTTPException\NotFoundException($this->t('The requested item doesn\'t exist or has been deleted.')); + } + + $this->app->setProfileOwner($item['uid'] ?: $profileUid); + $parentUriId = $item['parent-uri-id']; + + if (empty($force)) { + $browserUpdate = $this->pConfig->get($profileUid, 'system', 'update_interval'); + if (!empty($browserUpdate)) { + $updateDate = date(DateTimeFormat::MYSQL, time() - (intval($browserUpdate) / 500)); + if (!Post::exists([ + "`parent-uri-id` = ? AND `uid` IN (?, ?) AND `received` > ?", + $parentUriId, 0, + $profileUid, $updateDate])) { + $this->logger->debug('No updated content. Ending process', + ['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]); + return ''; + } else { + $this->logger->debug('Updated content found.', + ['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]); + } + } + } else { + $this->logger->debug('Forced content update.', ['uri-id' => $uriId, 'uid' => $profileUid]); + } + + if (!$this->pConfig->get($this->session->getLocalUserId(), 'system', 'detailed_notif')) { + $this->notification->setAllSeenForUser($this->session->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); + $this->notify->setAllSeenForUser($this->session->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); + } + + return $this->getDisplayData($item, true, $profileUid, $force); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index d5bed2756..7a86d8d03 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -398,6 +398,10 @@ return [ '/dirfind' => [Module\Search\Directory::class, [R::GET]], '/directory' => [Module\Directory::class, [R::GET]], + '/display/{guid}' => [Module\Item\Display::class, [R::GET]], + '/display/feed-item/{uri-id}[.atom]' => [Module\Item\Feed::class, [R::GET]], + '/display/feed-item/{uri-id}/{mode:conversation}[.atom]' => [Module\Item\Feed::class, [R::GET]], + '/featured/{nickname}' => [Module\ActivityPub\Featured::class, [R::GET]], '/feed' => [ @@ -638,6 +642,8 @@ return [ '/update_community[/{content}]' => [Module\Update\Community::class, [R::GET]], + '/update_display' => [Module\Update\Display::class, [R::GET]], + '/update_network' => [ '[/]' => [Module\Update\Network::class, [R::GET]], '/archive/{from:\d\d\d\d-\d\d-\d\d}[/{to:\d\d\d\d-\d\d-\d\d}]' => [Module\Update\Network::class, [R::GET]], diff --git a/view/js/main.js b/view/js/main.js index 93340dc37..e11585381 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -590,7 +590,7 @@ function liveUpdate(src) { var orgHeight = $("section").height(); - var udargs = ((netargs.length) ? '/' + netargs : ''); + var udargs = ((netargs.length) ? netargs : ''); var update_url = 'update_' + src + udargs + '&p=' + profile_uid + '&force=' + (force ? 1 : 0) + '&item=' + update_item; diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 68e4b5d3d..059e79bd8 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2022.12-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2022-11-14 17:05-0500\n" +"POT-Creation-Date: 2022-11-15 00:00+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME