<?php
/**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
-use Friendica\Core\Session;
+use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Theme;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Model\Verb;
+use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Object\Post as PostObject;
use Friendica\Object\Thread;
use Friendica\Protocol\Activity;
+use Friendica\User\Settings\Entity\UserGServer;
+use Friendica\User\Settings\Repository;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
class Conversation
{
+ const MODE_COMMUNITY = 'community';
+ const MODE_CONTACTS = 'contacts';
+ const MODE_CONTACT_POSTS = 'contact-posts';
+ const MODE_DISPLAY = 'display';
+ const MODE_FILED = 'filed';
+ const MODE_NETWORK = 'network';
+ const MODE_NOTES = 'notes';
+ const MODE_SEARCH = 'search';
+ const MODE_PROFILE = 'profile';
+
/** @var Activity */
private $activity;
/** @var L10n */
private $page;
/** @var App\Mode */
private $mode;
+ /** @var IHandleUserSessions */
+ private $session;
+ /** @var Repository\UserGServer */
+ private $userGServer;
- public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app)
+ public function __construct(Repository\UserGServer $userGServer, LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
{
- $this->activity = $activity;
- $this->item = $item;
- $this->config = $config;
- $this->mode = $mode;
- $this->baseURL = $baseURL;
- $this->profiler = $profiler;
- $this->logger = $logger;
- $this->l10n = $l10n;
- $this->args = $args;
- $this->pConfig = $pConfig;
- $this->page = $page;
- $this->app = $app;
+ $this->activity = $activity;
+ $this->item = $item;
+ $this->config = $config;
+ $this->mode = $mode;
+ $this->baseURL = $baseURL;
+ $this->profiler = $profiler;
+ $this->logger = $logger;
+ $this->l10n = $l10n;
+ $this->args = $args;
+ $this->pConfig = $pConfig;
+ $this->page = $page;
+ $this->app = $app;
+ $this->session = $session;
+ $this->userGServer = $userGServer;
}
/**
'uid' => 0,
'id' => $activity['author-id'],
'network' => $activity['author-network'],
- 'url' => $activity['author-link']
+ 'url' => $activity['author-link'],
+ 'alias' => $activity['author-alias'],
];
$url = Contact::magicLinkByContact($author);
- if (strpos($url, 'redir/') === 0) {
+ if (strpos($url, 'contact/redir/') === 0) {
$sparkle = ' class="sparkle" ';
}
continue;
}
- if (public_contact() == $activity['author-id']) {
+ if ($this->session->getPublicContactId() == $activity['author-id']) {
$conv_responses[$mode][$activity['thr-parent-id']]['self'] = 1;
}
}
}
+ /**
+ * Returns the liker phrase based on a list of likers
+ *
+ * @param string $verb the activity verb
+ * @param array $likers a list of likers
+ *
+ * @return string the liker phrase
+ *
+ * @throws InternalServerErrorException in case either the verb is invalid or the list of likers is empty
+ */
+ private function getLikerPhrase(string $verb, array $likers): string
+ {
+ $total = count($likers);
+
+ if ($total === 0) {
+ throw new InternalServerErrorException(sprintf('There has to be at least one Liker for verb "%s"', $verb));
+ } else if ($total === 1) {
+ $likerString = $likers[0];
+ } else {
+ if ($total < $this->config->get('system', 'max_likers')) {
+ $likerString = implode(', ', array_slice($likers, 0, -1));
+ $likerString .= ' ' . $this->l10n->t('and') . ' ' . $likers[count($likers) - 1];
+ } else {
+ $likerString = implode(', ', array_slice($likers, 0, $this->config->get('system', 'max_likers') - 1));
+ $likerString .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
+ }
+ }
+
+ switch ($verb) {
+ case 'like':
+ return $this->l10n->tt('%2$s likes this.', '%2$s like this.', $total, $likerString);
+ case 'dislike':
+ return $this->l10n->tt('%2$s doesn\'t like this.', '%2$s don\'t like this.', $total, $likerString);
+ case 'attendyes':
+ return $this->l10n->tt('%2$s attends.', '%2$s attend.', $total, $likerString);
+ case 'attendno':
+ return $this->l10n->tt('%2$s doesn\'t attend.', '%2$s don\'t attend.', $total, $likerString);
+ case 'attendmaybe':
+ return $this->l10n->tt('%2$s attends maybe.', '%2$s attend maybe.', $total, $likerString);
+ case 'announce':
+ return $this->l10n->tt('%2$s reshared this.', '%2$s reshared this.', $total, $likerString);
+ default:
+ throw new InternalServerErrorException(sprintf('Unknown verb "%s"', $verb));
+ }
+ }
+
/**
* Format the activity text for an item/photo/video
*
public function formatActivity(array $links, string $verb, int $id): string
{
$this->profiler->startRecording('rendering');
- $o = '';
$expanded = '';
- $phrase = '';
- $total = count($links);
- if ($total == 1) {
- $likers = $links[0];
-
- // Phrase if there is only one liker. In other cases it will be uses for the expanded
- // list which show all likers
- switch ($verb) {
- case 'like':
- $phrase = $this->l10n->t('%s likes this.', $likers);
- break;
- case 'dislike':
- $phrase = $this->l10n->t('%s doesn\'t like this.', $likers);
- break;
- case 'attendyes':
- $phrase = $this->l10n->t('%s attends.', $likers);
- break;
- case 'attendno':
- $phrase = $this->l10n->t('%s doesn\'t attend.', $likers);
- break;
- case 'attendmaybe':
- $phrase = $this->l10n->t('%s attends maybe.', $likers);
- break;
- case 'announce':
- $phrase = $this->l10n->t('%s reshared this.', $likers);
- break;
- }
- } elseif ($total > 1) {
- if ($total < $this->config->get('system', 'max_likers')) {
- $likers = implode(', ', array_slice($links, 0, -1));
- $likers .= ' ' . $this->l10n->t('and') . ' ' . $links[count($links) - 1];
- } else {
- $likers = implode(', ', array_slice($links, 0, $this->config->get('system', 'max_likers') - 1));
- $likers .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
- }
+ $phrase = $this->getLikerPhrase($verb, $links);
+ $total = count($links);
- $spanatts = "class=\"fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+ if ($total > 1) {
+ $spanatts = "class=\"btn btn-link fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+ $explikers = $phrase;
- $explikers = '';
switch ($verb) {
case 'like':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> like this', $spanatts, $total);
- $explikers = $this->l10n->t('%s like this.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> likes this', '<button type="button" %2$s>%1$d people</button> like this', $total, $spanatts);
break;
case 'dislike':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> don\'t like this', $spanatts, $total);
- $explikers = $this->l10n->t('%s don\'t like this.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t like this', '<button type="button" %2$s>%1$d peiple</button> don\'t like this', $total, $spanatts);
break;
case 'attendyes':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> attend', $spanatts, $total);
- $explikers = $this->l10n->t('%s attend.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts);
break;
case 'attendno':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> don\'t attend', $spanatts, $total);
- $explikers = $this->l10n->t('%s don\'t attend.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t attend', '<button type="button" %2$s>%1$d people</button> don\'t attend', $total, $spanatts);
break;
case 'attendmaybe':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> attend maybe', $spanatts, $total);
- $explikers = $this->l10n->t('%s attend maybe.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends maybe', '<button type="button" %2$s>%1$d people</button> attend maybe', $total, $spanatts);
break;
case 'announce':
- $phrase = $this->l10n->t('<span %1$s>%2$d people</span> reshared this', $spanatts, $total);
- $explikers = $this->l10n->t('%s reshared this.', $likers);
+ $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> reshared this', '<button type="button" %2$s>%1$d people</button> reshared this', $total, $spanatts);
break;
}
- $expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
+ $expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . '</p>';
}
- $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
+ $output = Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
'$phrase' => $phrase,
'$type' => $verb,
'$id' => $id
]);
- $o .= $expanded;
+ $output .= $expanded;
$this->profiler->stopRecording();
- return $o;
+ return $output;
}
public function statusEditor(array $x = [], int $notes_cid = 0, bool $popup = false): string
$x['bang'] = $x['bang'] ?? '';
$x['visitor'] = $x['visitor'] ?? 'block';
$x['is_owner'] = $x['is_owner'] ?? true;
- $x['profile_uid'] = $x['profile_uid'] ?? local_user();
+ $x['profile_uid'] = $x['profile_uid'] ?? $this->session->getLocalUserId();
$geotag = !empty($x['allow_location']) ? Renderer::replaceMacros(Renderer::getMarkupTemplate('jot_geotag.tpl'), []) : '';
$tpl = Renderer::getMarkupTemplate('jot-header.tpl');
$this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$newpost' => 'true',
- '$baseurl' => $this->baseURL->get(true),
+ '$baseurl' => $this->baseURL,
'$geotag' => $geotag,
'$nickname' => $x['nickname'],
'$ispublic' => $this->l10n->t('Visible to <strong>everybody</strong>'),
$created_at = '';
}
- $tpl = Renderer::getMarkupTemplate("jot.tpl");
+ $tpl = Renderer::getMarkupTemplate('jot.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$new_post' => $this->l10n->t('New Post'),
'$editalic' => $this->l10n->t('Italic'),
'$eduline' => $this->l10n->t('Underline'),
'$edquote' => $this->l10n->t('Quote'),
+ '$edemojis' => $this->l10n->t('Add emojis'),
+ '$contentwarn' => $this->l10n->t('Content Warning'),
'$edcode' => $this->l10n->t('Code'),
'$edimg' => $this->l10n->t('Image'),
'$edurl' => $this->l10n->t('Link'),
'$title' => $x['title'] ?? '',
'$placeholdertitle' => $this->l10n->t('Set title'),
'$category' => $x['category'] ?? '',
- '$placeholdercategory' => Feature::isEnabled(local_user(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
+ '$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
'$scheduled_at' => Temporal::getDateTimeField(
new \DateTime(),
new \DateTime('now + 6 months'),
'$posttype' => $notes_cid ? ItemModel::PT_PERSONAL_NOTE : ItemModel::PT_ARTICLE,
'$content' => $x['content'] ?? '',
'$post_id' => $x['post_id'] ?? '',
- '$baseurl' => $this->baseURL->get(true),
+ '$baseurl' => $this->baseURL,
'$defloc' => $x['default_location'],
'$visitor' => $x['visitor'],
'$pvisit' => $notes_cid ? 'none' : $x['visitor'],
'$message' => $this->l10n->t('Message'),
'$browser' => $this->l10n->t('Browser'),
- '$compose_link_title' => $this->l10n->t('Open Compose page'),
+ '$compose_link_title' => $this->l10n->t('Open Compose page'),
+ '$always_open_compose' => $this->pConfig->get($this->session->getLocalUserId(), 'frio', 'always_open_compose', false),
]);
* The $mode parameter decides between the various renderings and also
* figures out how to determine page owner and other contextual items
* that are based on unique features of the calling module.
- * @param array $items
- * @param string $mode
- * @param $update @TODO Which type?
- * @param bool $preview
- * @param string $order
+ * @param array $items An array of Posts
+ * @param string $mode One of self::MODE_*
+ * @param bool $update Asynchronous update rendering
+ * @param bool $preview Post preview (no actual database record)
+ * @param string $order Either "received" or "commented"
* @param int $uid
* @return string
* @throws ImagickException
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public function create(array $items, string $mode, $update, bool $preview = false, string $order = 'commented', int $uid = 0): string
+ public function render(array $items, string $mode, bool $update = false, bool $preview = false, string $order = 'commented', int $uid = 0): string
{
$this->profiler->startRecording('rendering');
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
- $ssl_state = (local_user() ? true : false);
-
$live_update_div = '';
- $blocklist = $this->getBlocklist();
+ $userGservers = $this->userGServer->listIgnoredByUser($this->session->getLocalUserId());
- $previewing = (($preview) ? ' preview ' : '');
+ $ignoredGsids = array_map(function (UserGServer $userGServer) {
+ return $userGServer->gsid;
+ }, $userGservers->getArrayCopy());
- if ($mode === 'network') {
- $items = $this->addChildren($items, false, $order, $uid, $mode);
+ if ($mode === self::MODE_NETWORK) {
+ $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) {
/*
* The special div is needed for liveUpdate to kick in for this page.
. "'; </script>\r\n";
}
- } elseif ($mode === 'profile') {
- $items = $this->addChildren($items, false, $order, $uid, $mode);
+ } elseif ($mode === self::MODE_PROFILE) {
+ $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) {
$tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts';
. "; var netargs = '?f='; </script>\r\n";
}
}
- } elseif ($mode === 'notes') {
- $items = $this->addChildren($items, false, $order, local_user(), $mode);
+ } elseif ($mode === self::MODE_NOTES) {
+ $items = $this->addChildren($items, false, $order, $this->session->getLocalUserId(), $mode);
if (!$update) {
$live_update_div = '<div id="live-notes"></div>' . "\r\n"
- . "<script> var profile_uid = " . local_user()
- . "; var netargs = '/?f='; </script>\r\n";
+ . "<script> var profile_uid = " . $this->session->getLocalUserId()
+ . "; var netargs = '?f='; </script>\r\n";
}
- } elseif ($mode === 'display') {
- $items = $this->addChildren($items, false, $order, $uid, $mode);
+ } elseif ($mode === self::MODE_DISPLAY) {
+ $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) {
$live_update_div = '<div id="live-display"></div>' . "\r\n"
- . "<script> var profile_uid = " . Session::get('uid', 0) . ";"
+ . "<script> var profile_uid = " . ($this->session->getLocalUserId() ?: 0) . ";"
. "</script>";
}
- } elseif ($mode === 'community') {
- $items = $this->addChildren($items, true, $order, $uid, $mode);
+ } elseif ($mode === self::MODE_COMMUNITY) {
+ $items = $this->addChildren($items, true, $order, $uid, $mode, $ignoredGsids);
if (!$update) {
$live_update_div = '<div id="live-community"></div>' . "\r\n"
. (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '')
. "'; </script>\r\n";
}
- } elseif ($mode === 'contacts') {
- $items = $this->addChildren($items, false, $order, $uid, $mode);
+ } elseif ($mode === self::MODE_CONTACTS) {
+ $items = $this->addChildren($items, false, $order, $uid, $mode, $ignoredGsids);
if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
- ."/?f='; </script>\r\n";
+ . "?f='; </script>\r\n";
}
- } elseif ($mode === 'search') {
+ } elseif ($mode === self::MODE_SEARCH) {
$live_update_div = '<div id="live-search"></div>' . "\r\n";
}
- $page_dropping = ((local_user() && local_user() == $uid) ? true : false);
+ $page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid && $mode != self::MODE_SEARCH;
if (!$update) {
$_SESSION['return_path'] = $this->args->getQueryString();
$items = $cb['items'];
- $conv_responses = [
- 'like' => [],
- 'dislike' => [],
- 'attendyes' => [],
- 'attendno' => [],
- 'attendmaybe' => [],
- 'announce' => [],
- ];
+ $formSecurityToken = BaseModule::getFormSecurityToken('contact_action');
+
+ $threads = $this->getThreadList($items, $mode, $preview, $page_dropping, $formSecurityToken);
- if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
- unset($conv_responses['dislike']);
+ if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
+ $page_template = Renderer::getMarkupTemplate('conversation.tpl');
+ } else {
+ $page_template = Renderer::getMarkupTemplate('threaded_conversation.tpl');
}
- // array with html for each thread (parent+comments)
- $threads = [];
- $threadsid = -1;
+ $o = Renderer::replaceMacros($page_template, [
+ '$baseurl' => $this->baseURL,
+ '$return_path' => $this->args->getQueryString(),
+ '$live_update' => $live_update_div,
+ '$remove' => $this->l10n->t('remove'),
+ '$mode' => $mode,
+ '$update' => $update,
+ '$threads' => $threads,
+ '$dropping' => ($page_dropping ? $this->l10n->t('Delete Selected Items') : false),
+ ]);
- $page_template = Renderer::getMarkupTemplate("conversation.tpl");
- $formSecurityToken = BaseModule::getFormSecurityToken('contact_action');
+ $this->profiler->stopRecording();
+ return $o;
+ }
- if (!empty($items)) {
- if (in_array($mode, ['community', 'contacts', 'profile'])) {
+ /**
+ * @param array $items
+ * @param string $mode One of self::MODE_*
+ * @param bool $preview
+ * @param bool $pagedrop Whether to enable the user to select the thread for deletion
+ * @param string $formSecurityToken A 'contact_action' form security token
+ * @return array
+ * @throws InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public function getThreadList(array $items, string $mode, bool $preview, bool $pagedrop, string $formSecurityToken): array
+ {
+ if (!$items) {
+ return [];
+ }
+
+ if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
+ $threads = $this->getContextLessThreadList($items, $mode, $preview, $pagedrop, $formSecurityToken);
+ } else {
+ $conv_responses = [
+ 'like' => [],
+ 'dislike' => [],
+ 'attendyes' => [],
+ 'attendno' => [],
+ 'attendmaybe' => [],
+ 'announce' => [],
+ ];
+
+ if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
+ unset($conv_responses['dislike']);
+ }
+
+ if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_CONTACTS, self::MODE_PROFILE])) {
$writable = true;
} else {
$writable = $items[0]['writable'] || ($items[0]['uid'] == 0) && in_array($items[0]['network'], Protocol::FEDERATED);
}
- if (!local_user()) {
+ if (!$this->session->getLocalUserId()) {
$writable = false;
}
- if (in_array($mode, ['filed', 'search', 'contact-posts'])) {
-
- /*
- * "New Item View" on network page or search page results
- * - just loop through the items and format them minimally for display
- */
-
- $tpl = 'search_item.tpl';
-
- $uriids = [];
-
- foreach ($items as $item) {
- if (in_array($item['uri-id'], $uriids)) {
- continue;
- }
-
- $uriids[] = $item['uri-id'];
-
- if (!$this->item->isVisibleActivity($item)) {
- continue;
- }
-
- if (in_array($item['author-id'], $blocklist)) {
- continue;
- }
-
- $threadsid++;
-
- // prevent private email from leaking.
- if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
- continue;
- }
-
- $profile_name = $item['author-name'];
- if (!empty($item['author-link']) && empty($item['author-name'])) {
- $profile_name = $item['author-link'];
- }
-
- $tags = Tag::populateFromItem($item);
+ // Normal View
+ $conv = new Thread($mode, $preview, $writable);
- $author = ['uid' => 0, 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link']];
- $profile_link = Contact::magicLinkByContact($author);
-
- $sparkle = '';
- if (strpos($profile_link, 'redir/') === 0) {
- $sparkle = ' sparkle';
- }
-
- $locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
- Hook::callAll('render_location', $locate);
- $location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
-
- $this->item->localize($item);
- if ($mode === 'filed') {
- $dropping = true;
- } else {
- $dropping = false;
- }
-
- $drop = [
- 'dropping' => $dropping,
- 'pagedrop' => $page_dropping,
- 'select' => $this->l10n->t('Select'),
- 'delete' => $this->l10n->t('Delete'),
- ];
-
- $likebuttons = [
- 'like' => null,
- 'dislike' => null,
- 'share' => null,
- 'announce' => null,
- ];
-
- if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
- unset($likebuttons['dislike']);
- }
-
- $body_html = ItemModel::prepareBody($item, true, $preview);
-
- [$categories, $folders] = $this->item->determineCategoriesTerms($item, local_user());
-
- if (!empty($item['title'])) {
- $title = $item['title'];
- } elseif (!empty($item['content-warning']) && $this->pConfig->get(local_user(), 'system', 'disable_cw', false)) {
- $title = ucfirst($item['content-warning']);
- } else {
- $title = '';
- }
-
- if (!empty($item['featured'])) {
- $pinned = $this->l10n->t('Pinned item');
- } else {
- $pinned = '';
- }
-
- $tmp_item = [
- 'template' => $tpl,
- 'id' => ($preview ? 'P0' : $item['id']),
- 'guid' => ($preview ? 'Q0' : $item['guid']),
- 'commented' => $item['commented'],
- 'received' => $item['received'],
- 'created_date' => $item['created'],
- 'uriid' => $item['uri-id'],
- 'network' => $item['network'],
- 'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
- 'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
- 'linktitle' => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
- 'profile_url' => $profile_link,
- 'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
- 'name' => $profile_name,
- 'sparkle' => $sparkle,
- 'lock' => false,
- 'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
- 'title' => $title,
- 'body_html' => $body_html,
- 'tags' => $tags['tags'],
- 'hashtags' => $tags['hashtags'],
- 'mentions' => $tags['mentions'],
- 'implicit_mentions' => $tags['implicit_mentions'],
- 'txt_cats' => $this->l10n->t('Categories:'),
- 'txt_folders' => $this->l10n->t('Filed under:'),
- 'has_cats' => ((count($categories)) ? 'true' : ''),
- 'has_folders' => ((count($folders)) ? 'true' : ''),
- 'categories' => $categories,
- 'folders' => $folders,
- 'text' => strip_tags($body_html),
- 'localtime' => DateTimeFormat::local($item['created'], 'r'),
- 'utc' => DateTimeFormat::utc($item['created'], 'c'),
- 'ago' => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
- 'location_html' => $location_html,
- 'indent' => '',
- 'owner_name' => '',
- 'owner_url' => '',
- 'owner_photo' => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
- 'plink' => ItemModel::getPlink($item),
- 'edpost' => false,
- 'pinned' => $pinned,
- 'isstarred' => 'unstarred',
- 'star' => false,
- 'drop' => $drop,
- 'vote' => $likebuttons,
- 'like_html' => '',
- 'dislike_html ' => '',
- 'comment_html' => '',
- 'conv' => ($preview ? '' : ['href' => 'display/' . $item['guid'], 'title' => $this->l10n->t('View in context')]),
- 'previewing' => $previewing,
- 'wait' => $this->l10n->t('Please wait'),
- 'thread_level' => 1,
- ];
-
- $arr = ['item' => $item, 'output' => $tmp_item];
- Hook::callAll('display_item', $arr);
-
- $threads[$threadsid]['id'] = $item['id'];
- $threads[$threadsid]['network'] = $item['network'];
- $threads[$threadsid]['items'] = [$arr['output']];
+ /*
+ * get all the topmost parents
+ * this shouldn't be needed, as we should have only them in our array
+ * But for now, this array respects the old style, just in case
+ */
+ foreach ($items as $item) {
+ if (in_array($item['author-id'], $this->getBlocklist())) {
+ continue;
}
- } else {
- // Normal View
- $page_template = Renderer::getMarkupTemplate("threaded_conversation.tpl");
-
- $conv = new Thread($mode, $preview, $writable);
-
- /*
- * get all the topmost parents
- * this shouldn't be needed, as we should have only them in our array
- * But for now, this array respects the old style, just in case
- */
- foreach ($items as $item) {
- if (in_array($item['author-id'], $blocklist)) {
- continue;
- }
- // Can we put this after the visibility check?
- $this->builtinActivityPuller($item, $conv_responses);
+ // Can we put this after the visibility check?
+ $this->builtinActivityPuller($item, $conv_responses);
- // Only add what is visible
- if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
- continue;
- }
+ // Only add what is visible
+ if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
+ continue;
+ }
- if (!$this->item->isVisibleActivity($item)) {
- continue;
- }
+ if (!$this->item->isVisibleActivity($item)) {
+ continue;
+ }
- /// @todo Check if this call is needed or not
- $arr = ['item' => $item];
- Hook::callAll('display_item', $arr);
+ /// @todo Check if this call is needed or not
+ $arr = ['item' => $item];
+ Hook::callAll('display_item', $arr);
- $item['pagedrop'] = $page_dropping;
+ $item['pagedrop'] = $pagedrop;
- if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
- $item_object = new PostObject($item);
- $conv->addParent($item_object);
- }
+ if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
+ $item_object = new PostObject($item);
+ $conv->addParent($item_object);
}
+ }
- $threads = $conv->getTemplateData($conv_responses, $formSecurityToken);
- if (!$threads) {
- $this->logger->info('[ERROR] conversation : Failed to get template data.');
- $threads = [];
- }
+ $threads = $conv->getTemplateData($conv_responses, $formSecurityToken);
+ if (!$threads) {
+ $this->logger->info('[ERROR] conversation : Failed to get template data.');
+ $threads = [];
}
}
- $o = Renderer::replaceMacros($page_template, [
- '$baseurl' => $this->baseURL->get($ssl_state),
- '$return_path' => $this->args->getQueryString(),
- '$live_update' => $live_update_div,
- '$remove' => $this->l10n->t('remove'),
- '$mode' => $mode,
- '$update' => $update,
- '$threads' => $threads,
- '$dropping' => ($page_dropping ? $this->l10n->t('Delete Selected Items') : false),
- ]);
-
- $this->profiler->stopRecording();
- return $o;
+ return $threads;
}
private function getBlocklist(): array
{
- if (!local_user()) {
+ if (!$this->session->getLocalUserId()) {
return [];
}
- $str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get(local_user(), 'system', 'blocked'));
+ $str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get($this->session->getLocalUserId(), 'system', 'blocked') ?? '');
if (empty($str_blocked)) {
return [];
}
$row['causer-avatar'] = $contact['thumb'];
$row['causer-name'] = $contact['name'];
} elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
- ($row['author-id'] == $activity['causer-id'])) {
+ ($row['author-id'] == $activity['causer-id'])
+ ) {
return $row;
}
}
case ItemModel::PR_BCC:
$row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'bcc')];
break;
+ case ItemModel::PR_AUDIENCE:
+ $row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'audience')];
+ break;
case ItemModel::PR_FOLLOWER:
$row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('You are following %s.', $row['causer-name'] ?: $row['author-name'])];
break;
$row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to one or more tags in this post.')];
break;
case ItemModel::PR_ANNOUNCEMENT:
- if (!empty($row['causer-id']) && $this->pConfig->get(local_user(), 'system', 'display_resharer')) {
+ if (!empty($row['causer-id']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'display_resharer')) {
$row['owner-id'] = $row['causer-id'];
$row['owner-link'] = $row['causer-link'];
$row['owner-avatar'] = $row['causer-avatar'];
}
if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
- $causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']];
+ $causer = [
+ 'uid' => 0,
+ 'id' => $row['causer-id'],
+ 'network' => $row['causer-network'],
+ 'url' => $row['causer-link'],
+ 'alias' => $row['causer-alias'],
+ ];
- $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>');
+ $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="' . htmlentities(Contact::magicLinkByContact($causer)) . '">' . htmlentities($row['causer-name']) . '</a>');
}
$row['direction'] = ['direction' => 3, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Reshared') : $this->l10n->t('Reshared by %s <%s>', $row['causer-name'], $row['causer-link']))];
break;
*
* @param array $parents Parent items
* @param bool $block_authors
- * @param bool $order
+ * @param string $order Either "received" or "commented"
* @param int $uid
- * @param string $mode
+ * @param string $mode One of self::MODE_*
+ * @param array $ignoredGsids List of ids of servers ignored by the user
* @return array items with parents and comments
- * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ * @throws InternalServerErrorException
*/
- private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode): array
+ private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode, array $ignoredGsids = []): array
{
$this->profiler->startRecording('rendering');
if (count($parents) > 1) {
$condition['author-hidden'] = false;
}
- $condition = DBA::mergeConditions($condition,
- ["`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW)]);
+ if ($this->config->get('system', 'emoji_activities')) {
+ $emojis = $this->getEmojis($uriids);
+ $condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]);
+ }
+
+ $condition = DBA::mergeConditions(
+ $condition,
+ ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]
+ );
+
+ $condition = DBA::mergeConditions($condition, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]);
+
+ $condition = DBA::mergeConditions(
+ $condition,
+ [
+ "`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked`
+ AND ((NOT `contact-pending` AND (`contact-rel` IN (?, ?))) OR `self` OR `contact-uid` = ?)",
+ Contact::SHARING, Contact::FRIEND, 0
+ ]
+ );
$thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
$params = ['order' => ['uri-id' => true, 'uid' => true]];
- $thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
+ $thread_items = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
- $items = [];
+ $items = [];
+ $quote_uri_ids = [];
+ $authors = [];
while ($row = Post::fetch($thread_items)) {
if (!empty($items[$row['uri-id']]) && ($row['uid'] == 0)) {
continue;
}
- if (($mode != 'contacts') && !$row['origin']) {
+ if (in_array($row['author-gsid'], $ignoredGsids)
+ || in_array($row['owner-gsid'], $ignoredGsids)
+ || in_array($row['causer-gsid'], $ignoredGsids)
+ ) {
+ continue;
+ }
+
+ if (($mode != self::MODE_CONTACTS) && !$row['origin']) {
$row['featured'] = false;
}
}
}
+ $authors[] = $row['author-id'];
+ $authors[] = $row['owner-id'];
+
+ if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT])) {
+ $quote_uri_ids[$row['uri-id']] = [
+ 'uri-id' => $row['uri-id'],
+ 'uri' => $row['uri'],
+ 'parent-uri-id' => $row['parent-uri-id'],
+ 'parent-uri' => $row['parent-uri'],
+ ];
+ }
+
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []);
}
DBA::close($thread_items);
+ $quotes = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), ['quote-uri-id' => array_column($quote_uri_ids, 'uri-id'), 'body' => '', 'uid' => 0]);
+ while ($quote = Post::fetch($quotes)) {
+ $row = $quote;
+
+ $row['uid'] = $uid;
+ $row['verb'] = $row['body'] = $row['raw-body'] = Activity::ANNOUNCE;
+ $row['gravity'] = ItemModel::GRAVITY_ACTIVITY;
+ $row['object-type'] = Activity\ObjectType::NOTE;
+ $row['parent-uri'] = $quote_uri_ids[$quote['quote-uri-id']]['parent-uri'];
+ $row['parent-uri-id'] = $quote_uri_ids[$quote['quote-uri-id']]['parent-uri-id'];
+ $row['thr-parent'] = $quote_uri_ids[$quote['quote-uri-id']]['uri'];
+ $row['thr-parent-id'] = $quote_uri_ids[$quote['quote-uri-id']]['uri-id'];
+
+ $authors[] = $row['author-id'];
+ $authors[] = $row['owner-id'];
+
+ $items[$row['uri-id']] = $this->addRowInformation($row, [], []);
+ }
+ DBA::close($quotes);
+
+ $authors = array_unique($authors);
+
+ $blocks = [];
+ $ignores = [];
+ $collapses = [];
+ if (!empty($authors)) {
+ $usercontacts = DBA::select('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $uid, 'cid' => $authors]);
+ while ($usercontact = DBA::fetch($usercontacts)) {
+ if ($usercontact['blocked']) {
+ $blocks[] = $usercontact['cid'];
+ }
+ if ($usercontact['ignored']) {
+ $ignores[] = $usercontact['cid'];
+ }
+ if ($usercontact['collapsed']) {
+ $collapses[] = $usercontact['cid'];
+ }
+ }
+ DBA::close($usercontacts);
+ }
+
+ foreach ($items as $key => $row) {
+ $items[$key]['emojis'] = $emojis[$key] ?? [];
+
+ $always_display = in_array($mode, [self::MODE_CONTACTS, self::MODE_CONTACT_POSTS]);
+
+ $items[$key]['user-blocked-author'] = !$always_display && in_array($row['author-id'], $blocks);
+ $items[$key]['user-ignored-author'] = !$always_display && in_array($row['author-id'], $ignores);
+ $items[$key]['user-blocked-owner'] = !$always_display && in_array($row['owner-id'], $blocks);
+ $items[$key]['user-ignored-owner'] = !$always_display && in_array($row['owner-id'], $ignores);
+ $items[$key]['user-collapsed-author'] = !$always_display && in_array($row['author-id'], $collapses);
+ $items[$key]['user-collapsed-owner'] = !$always_display && in_array($row['owner-id'], $collapses);
+
+ if (
+ in_array($mode, [self::MODE_COMMUNITY, self::MODE_NETWORK]) &&
+ (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))
+ ) {
+ unset($items[$key]);
+ }
+ }
+
$items = $this->convSort($items, $order);
$this->profiler->stopRecording();
return $items;
}
+ /**
+ * Fetch emoji reaction from the conversation
+ *
+ * @param array $uriids
+ * @return array
+ */
+ private function getEmojis(array $uriids): array
+ {
+ $activity_emoji = [
+ Activity::LIKE => '👍',
+ Activity::DISLIKE => '👎',
+ Activity::ATTEND => '✔️',
+ Activity::ATTENDMAYBE => '❓',
+ Activity::ATTENDNO => '❌',
+ Activity::ANNOUNCE => '♻',
+ Activity::VIEW => '📺',
+ ];
+
+ $index_list = array_values($activity_emoji);
+ $verbs = array_merge(array_keys($activity_emoji), [Activity::EMOJIREACT]);
+
+ $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]);
+ $separator = chr(255) . chr(255) . chr(255);
+
+ $sql = "SELECT `thr-parent-id`, `body`, `verb`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '" . $separator . "' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`";
+
+ $emojis = [];
+
+ $rows = DBA::p($sql, $condition);
+ while ($row = DBA::fetch($rows)) {
+ $row['verb'] = $row['body'] ? Activity::EMOJIREACT : $row['verb'];
+ $emoji = $row['body'] ?: $activity_emoji[$row['verb']];
+ if (!isset($index_list[$emoji])) {
+ $index_list[] = $emoji;
+ }
+ $index = array_search($emoji, $index_list);
+
+ $emojis[$row['thr-parent-id']][$index]['emoji'] = $emoji;
+ $emojis[$row['thr-parent-id']][$index]['verb'] = $row['verb'];
+ $emojis[$row['thr-parent-id']][$index]['total'] = ($emojis[$row['thr-parent-id']][$index]['total'] ?? 0) + $row['total'];
+ $emojis[$row['thr-parent-id']][$index]['title'] = array_unique(array_merge($emojis[$row['thr-parent-id']][$index]['title'] ?? [], explode($separator, $row['title'])));
+ }
+ DBA::close($rows);
+
+ return $emojis;
+ }
+
/**
* Plucks the children of the given parent from a given item list.
*
// Searches the post item in the children
$j = 0;
while ($child['children'][$j]['verb'] !== Activity::POST && $j < count($child['children'])) {
- $j ++;
+ $j++;
}
$moved_item = $child['children'][$j];
* items and add them as children of their top-level post.
*/
foreach ($parents as $i => $parent) {
- $parents[$i]['children'] = array_merge($this->getItemChildren($item_array, $parent, true),
- $this->getItemChildren($item_array, $parent, false));
+ $parents[$i]['children'] = array_merge(
+ $this->getItemChildren($item_array, $parent, true),
+ $this->getItemChildren($item_array, $parent, false)
+ );
}
foreach ($parents as $i => $parent) {
$parents[$i]['children'] = $this->sortItemChildren($parents[$i]['children']);
}
- if (!$this->pConfig->get(local_user(), 'system', 'no_smart_threading', 0)) {
+ if (!$this->pConfig->get($this->session->getLocalUserId(), 'system', 'no_smart_threading', 0)) {
foreach ($parents as $i => $parent) {
$parents[$i] = $this->smartFlattenConversation($parent);
}
}
- /// @TODO: Stop recusrsively adding all children back to the top level (!!!)
+ /// @TODO: Stop recursively adding all children back to the top level (!!!)
/// However, this apparently ensures responses (likes, attendance) display (?!)
foreach ($parents as $parent) {
if (count($parent['children'])) {
{
return strcmp($b['created'], $a['created']);
}
+
+ /**
+ * "New Item View" on network page or search page results
+ * - just loop through the items and format them minimally for display
+ *
+ * @param array $items
+ * @param string $mode One of self::MODE_*
+ * @param bool $preview Whether the display is a preview
+ * @param bool $pagedrop Whether the user can select the threads for deletion
+ * @param string $formSecurityToken A 'contact_action' form security token
+ * @return array
+ * @throws InternalServerErrorException
+ * @throws \ImagickException
+ */
+ public function getContextLessThreadList(array $items, string $mode, bool $preview, bool $pagedrop, string $formSecurityToken): array
+ {
+ $threads = [];
+ $uriids = [];
+
+ foreach ($items as $item) {
+ if (in_array($item['uri-id'], $uriids)) {
+ continue;
+ }
+
+ $uriids[] = $item['uri-id'];
+
+ if (!$this->item->isVisibleActivity($item)) {
+ continue;
+ }
+
+ if (in_array($item['author-id'], $this->getBlocklist())) {
+ continue;
+ }
+
+ // prevent private email from leaking.
+ if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
+ continue;
+ }
+
+ $profile_name = $item['author-name'];
+ if (!empty($item['author-link']) && empty($item['author-name'])) {
+ $profile_name = $item['author-link'];
+ }
+
+ $tags = Tag::populateFromItem($item);
+
+ $author = [
+ 'uid' => 0,
+ 'id' => $item['author-id'],
+ 'network' => $item['author-network'],
+ 'url' => $item['author-link'],
+ 'alias' => $item['author-alias'],
+ ];
+ $profile_link = Contact::magicLinkByContact($author);
+
+ $sparkle = '';
+ if (strpos($profile_link, 'contact/redir/') === 0) {
+ $sparkle = ' sparkle';
+ }
+
+ $locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
+ Hook::callAll('render_location', $locate);
+ $location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
+
+ $this->item->localize($item);
+ if ($mode === self::MODE_FILED) {
+ $dropping = true;
+ } else {
+ $dropping = false;
+ }
+
+ $drop = [
+ 'dropping' => $dropping,
+ 'pagedrop' => $pagedrop,
+ 'select' => $this->l10n->t('Select'),
+ 'delete' => $this->l10n->t('Delete'),
+ ];
+
+ $likebuttons = [
+ 'like' => null,
+ 'dislike' => null,
+ 'share' => null,
+ 'announce' => null,
+ ];
+
+ if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
+ unset($likebuttons['dislike']);
+ }
+
+ $body_html = ItemModel::prepareBody($item, true, $preview);
+
+ [$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
+
+ if (!empty($item['title'])) {
+ $title = $item['title'];
+ } elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
+ $title = ucfirst($item['content-warning']);
+ } else {
+ $title = '';
+ }
+
+ if (!empty($item['featured'])) {
+ $pinned = $this->l10n->t('Pinned item');
+ } else {
+ $pinned = '';
+ }
+
+ $tmp_item = [
+ 'template' => 'search_item.tpl',
+ 'id' => ($preview ? 'P0' : $item['id']),
+ 'guid' => ($preview ? 'Q0' : $item['guid']),
+ 'commented' => $item['commented'],
+ 'received' => $item['received'],
+ 'created_date' => $item['created'],
+ 'uriid' => $item['uri-id'],
+ 'author_gsid' => $item['author-gsid'],
+ 'network' => $item['network'],
+ 'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
+ 'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
+ 'linktitle' => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
+ 'profile_url' => $profile_link,
+ 'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
+ 'name' => $profile_name,
+ 'sparkle' => $sparkle,
+ 'lock' => false,
+ 'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
+ 'title' => $title,
+ 'body_html' => $body_html,
+ 'tags' => $tags['tags'],
+ 'hashtags' => $tags['hashtags'],
+ 'mentions' => $tags['mentions'],
+ 'implicit_mentions' => $tags['implicit_mentions'],
+ 'txt_cats' => $this->l10n->t('Categories:'),
+ 'txt_folders' => $this->l10n->t('Filed under:'),
+ 'has_cats' => ((count($categories)) ? 'true' : ''),
+ 'has_folders' => ((count($folders)) ? 'true' : ''),
+ 'categories' => $categories,
+ 'folders' => $folders,
+ 'text' => strip_tags($body_html),
+ 'localtime' => DateTimeFormat::local($item['created'], 'r'),
+ 'utc' => DateTimeFormat::utc($item['created'], 'c'),
+ 'ago' => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
+ 'location_html' => $location_html,
+ 'indent' => '',
+ 'owner_name' => '',
+ 'owner_url' => '',
+ 'owner_photo' => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
+ 'plink' => ItemModel::getPlink($item),
+ 'edpost' => false,
+ 'pinned' => $pinned,
+ 'isstarred' => 'unstarred',
+ 'star' => false,
+ 'drop' => $drop,
+ 'vote' => $likebuttons,
+ 'like_html' => '',
+ 'dislike_html ' => '',
+ 'comment_html' => '',
+ 'conv' => $preview ? '' : ['href' => 'display/' . $item['guid'], 'title' => $this->l10n->t('View in context')],
+ 'previewing' => $preview ? ' preview ' : '',
+ 'wait' => $this->l10n->t('Please wait'),
+ 'thread_level' => 1,
+ ];
+
+ $arr = ['item' => $item, 'output' => $tmp_item];
+ Hook::callAll('display_item', $arr);
+
+ $threads[] = [
+ 'id' => $item['id'],
+ 'network' => $item['network'],
+ 'items' => [$arr['output']],
+ ];
+ }
+
+ return $threads;
+ }
}