]> git.mxchange.org Git - friendica.git/blob - src/Content/Item.php
The "share" handling is improved
[friendica.git] / src / Content / Item.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2022, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Content;
23
24 use Friendica\Content\Text\BBCode;
25 use Friendica\Content\Text\HTML;
26 use Friendica\Core\Hook;
27 use Friendica\Core\L10n;
28 use Friendica\Core\Logger;
29 use Friendica\Core\Protocol;
30 use Friendica\Core\Session;
31 use Friendica\Core\System;
32 use Friendica\Database\DBA;
33 use Friendica\Model\Contact;
34 use Friendica\Model\Group;
35 use Friendica\Model\Item as ItemModel;
36 use Friendica\Model\Photo;
37 use Friendica\Model\Tag;
38 use Friendica\Model\Post;
39 use Friendica\Protocol\Activity;
40 use Friendica\Protocol\Diaspora;
41 use Friendica\Util\Profiler;
42 use Friendica\Util\Proxy;
43 use Friendica\Util\XML;
44
45 /**
46  * A content helper class for displaying items
47  */
48 class Item
49 {
50         /** @var Activity */
51         private $activity;
52         /** @var L10n */
53         private $l10n;
54         /** @var Profiler */
55         private $profiler;
56
57         public function __construct(Profiler $profiler, Activity $activity, L10n $l10n)
58         {
59                 $this->profiler = $profiler;
60                 $this->activity = $activity;
61                 $this->l10n   = $l10n;
62         }
63
64         /**
65          * Return array with details for categories and folders for an item
66          *
67          * @param array $item
68          * @param int   $uid
69          * @return [array, array]
70          *
71          * [
72          *      [ // categories array
73          *          {
74          *               'name': 'category name',
75          *               'removeurl': 'url to remove this category',
76          *               'first': 'is the first in this array? true/false',
77          *               'last': 'is the last in this array? true/false',
78          *           } ,
79          *           ....
80          *       ],
81          *       [ //folders array
82          *                      {
83          *               'name': 'folder name',
84          *               'removeurl': 'url to remove this folder',
85          *               'first': 'is the first in this array? true/false',
86          *               'last': 'is the last in this array? true/false',
87          *           } ,
88          *           ....
89          *       ]
90          *  ]
91          */
92         public function determineCategoriesTerms(array $item, int $uid = 0): array
93         {
94                 $categories = [];
95                 $folders = [];
96                 $first = true;
97
98                 $uid = $item['uid'] ?: $uid;
99
100                 if (empty($item['has-categories'])) {
101                         return [$categories, $folders];
102                 }
103
104                 foreach (Post\Category::getArrayByURIId($item['uri-id'], $uid, Post\Category::CATEGORY) as $savedFolderName) {
105                         if (!empty($item['author-link'])) {
106                                 $url = $item['author-link'] . "?category=" . rawurlencode($savedFolderName);
107                         } else {
108                                 $url = '#';
109                         }
110                         $categories[] = [
111                                 'name' => $savedFolderName,
112                                 'url' => $url,
113                                 'removeurl' => Session::getLocalUser() == $uid ? 'filerm/' . $item['id'] . '?cat=' . rawurlencode($savedFolderName) : '',
114                                 'first' => $first,
115                                 'last' => false
116                         ];
117                         $first = false;
118                 }
119
120                 if (count($categories)) {
121                         $categories[count($categories) - 1]['last'] = true;
122                 }
123
124                 if (Session::getLocalUser() == $uid) {
125                         foreach (Post\Category::getArrayByURIId($item['uri-id'], $uid, Post\Category::FILE) as $savedFolderName) {
126                                 $folders[] = [
127                                         'name' => $savedFolderName,
128                                         'url' => "#",
129                                         'removeurl' => Session::getLocalUser() == $uid ? 'filerm/' . $item['id'] . '?term=' . rawurlencode($savedFolderName) : '',
130                                         'first' => $first,
131                                         'last' => false
132                                 ];
133                                 $first = false;
134                         }
135                 }
136
137                 if (count($folders)) {
138                         $folders[count($folders) - 1]['last'] = true;
139                 }
140
141                 return [$categories, $folders];
142         }
143
144         /**
145          * This function removes the tag $tag from the text $body and replaces it with
146          * the appropriate link.
147          *
148          * @param string $body        the text to replace the tag in
149          * @param int    $profile_uid the user id to replace the tag for (0 = anyone)
150          * @param string $tag         the tag to replace
151          * @param string $network     The network of the post
152          *
153          * @return array|bool ['replaced' => $replaced, 'contact' => $contact] or "false" on if already replaced
154          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
155          * @throws \ImagickException
156          */
157         public static function replaceTag(string &$body, int $profile_uid, string $tag, string $network = '')
158         {
159                 $replaced = false;
160
161                 //is it a person tag?
162                 if (Tag::isType($tag, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION)) {
163                         $tag_type = substr($tag, 0, 1);
164                         //is it already replaced?
165                         if (strpos($tag, '[url=')) {
166                                 return $replaced;
167                         }
168
169                         //get the person's name
170                         $name = substr($tag, 1);
171
172                         // Sometimes the tag detection doesn't seem to work right
173                         // This is some workaround
174                         $nameparts = explode(' ', $name);
175                         $name = $nameparts[0];
176
177                         // Try to detect the contact in various ways
178                         if (strpos($name, 'http://') || strpos($name, '@')) {
179                                 $contact = Contact::getByURLForUser($name, $profile_uid);
180                         } else {
181                                 $contact = false;
182                                 $fields = ['id', 'url', 'nick', 'name', 'alias', 'network', 'forum', 'prv'];
183
184                                 if (strrpos($name, '+')) {
185                                         // Is it in format @nick+number?
186                                         $tagcid = intval(substr($name, strrpos($name, '+') + 1));
187                                         $contact = DBA::selectFirst('contact', $fields, ['id' => $tagcid, 'uid' => $profile_uid]);
188                                 }
189
190                                 // select someone by nick in the current network
191                                 if (!DBA::isResult($contact) && ($network != '')) {
192                                         $condition = ["`nick` = ? AND `network` = ? AND `uid` = ?",
193                                                 $name, $network, $profile_uid];
194                                         $contact = DBA::selectFirst('contact', $fields, $condition);
195                                 }
196
197                                 // select someone by attag in the current network
198                                 if (!DBA::isResult($contact) && ($network != '')) {
199                                         $condition = ["`attag` = ? AND `network` = ? AND `uid` = ?",
200                                                 $name, $network, $profile_uid];
201                                         $contact = DBA::selectFirst('contact', $fields, $condition);
202                                 }
203
204                                 //select someone by name in the current network
205                                 if (!DBA::isResult($contact) && ($network != '')) {
206                                         $condition = ['name' => $name, 'network' => $network, 'uid' => $profile_uid];
207                                         $contact = DBA::selectFirst('contact', $fields, $condition);
208                                 }
209
210                                 // select someone by nick in any network
211                                 if (!DBA::isResult($contact)) {
212                                         $condition = ["`nick` = ? AND `uid` = ?", $name, $profile_uid];
213                                         $contact = DBA::selectFirst('contact', $fields, $condition);
214                                 }
215
216                                 // select someone by attag in any network
217                                 if (!DBA::isResult($contact)) {
218                                         $condition = ["`attag` = ? AND `uid` = ?", $name, $profile_uid];
219                                         $contact = DBA::selectFirst('contact', $fields, $condition);
220                                 }
221
222                                 // select someone by name in any network
223                                 if (!DBA::isResult($contact)) {
224                                         $condition = ['name' => $name, 'uid' => $profile_uid];
225                                         $contact = DBA::selectFirst('contact', $fields, $condition);
226                                 }
227                         }
228
229                         // Check if $contact has been successfully loaded
230                         if (DBA::isResult($contact)) {
231                                 $profile = $contact['url'];
232                                 $newname = ($contact['name'] ?? '') ?: $contact['nick'];
233                         }
234
235                         //if there is an url for this persons profile
236                         if (isset($profile) && ($newname != '')) {
237                                 $replaced = true;
238                                 // create profile link
239                                 $profile = str_replace(',', '%2c', $profile);
240                                 $newtag = $tag_type.'[url=' . $profile . ']' . $newname . '[/url]';
241                                 $body = str_replace($tag_type . $name, $newtag, $body);
242                         }
243                 }
244
245                 return ['replaced' => $replaced, 'contact' => $contact];
246         }
247
248         /**
249          * Render actions localized
250          *
251          * @param array $item
252          * @return void
253          * @throws ImagickException
254          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
255          */
256         public function localize(array &$item)
257         {
258                 $this->profiler->startRecording('rendering');
259                 /// @todo The following functionality needs to be cleaned up.
260                 if (!empty($item['verb'])) {
261                         $xmlhead = '<?xml version="1.0" encoding="UTF-8" ?>';
262
263                         if ($this->activity->match($item['verb'], Activity::TAG)) {
264                                 $fields = ['author-id', 'author-link', 'author-name', 'author-network',
265                                         'verb', 'object-type', 'resource-id', 'body', 'plink'];
266                                 $obj = Post::selectFirst($fields, ['uri' => $item['parent-uri']]);
267                                 if (!DBA::isResult($obj)) {
268                                         $this->profiler->stopRecording();
269                                         return;
270                                 }
271
272                                 $author_arr = [
273                                         'uid' => 0,
274                                         'id' => $item['author-id'],
275                                         'network' => $item['author-network'],
276                                         'url' => $item['author-link'],
277                                 ];
278                                 $author  = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $item['author-name'] . '[/url]';
279
280                                 $author_arr = [
281                                         'uid' => 0,
282                                         'id' => $obj['author-id'],
283                                         'network' => $obj['author-network'],
284                                         'url' => $obj['author-link'],
285                                 ];
286                                 $objauthor  = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $obj['author-name'] . '[/url]';
287
288                                 switch ($obj['verb']) {
289                                         case Activity::POST:
290                                                 switch ($obj['object-type']) {
291                                                         case Activity\ObjectType::EVENT:
292                                                                 $post_type = $this->l10n->t('event');
293                                                                 break;
294                                                         default:
295                                                                 $post_type = $this->l10n->t('status');
296                                                 }
297                                                 break;
298
299                                         default:
300                                                 if ($obj['resource-id']) {
301                                                         $post_type = $this->l10n->t('photo');
302                                                         $m=[]; preg_match("/\[url=([^]]*)\]/", $obj['body'], $m);
303                                                         $rr['plink'] = $m[1];
304                                                 } else {
305                                                         $post_type = $this->l10n->t('status');
306                                                 }
307                                                 // Let's break everthing ... ;-)
308                                                 break;
309                                 }
310                                 $plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]';
311
312                                 $parsedobj = XML::parseString($xmlhead . $item['object']);
313
314                                 $tag = sprintf('#[url=%s]%s[/url]', $parsedobj->id, $parsedobj->content);
315                                 $item['body'] = $this->l10n->t('%1$s tagged %2$s\'s %3$s with %4$s', $author, $objauthor, $plink, $tag);
316                         }
317                 }
318
319                 $this->profiler->stopRecording();
320         }
321
322         /**
323          * Renders photo menu based on item
324          *
325          * @param array $item
326          * @param string $formSecurityToken
327          * @return string
328          */
329         public function photoMenu(array $item, string $formSecurityToken): string
330         {
331                 $this->profiler->startRecording('rendering');
332                 $sub_link = $contact_url = $pm_url = $status_link = '';
333                 $photos_link = $posts_link = $block_link = $ignore_link = '';
334
335                 if (Session::getLocalUser() && Session::getLocalUser() == $item['uid'] && $item['gravity'] == ItemModel::GRAVITY_PARENT && !$item['self'] && !$item['mention']) {
336                         $sub_link = 'javascript:doFollowThread(' . $item['id'] . '); return false;';
337                 }
338
339                 $author = [
340                         'uid' => 0,
341                         'id' => $item['author-id'],
342                         'network' => $item['author-network'],
343                         'url' => $item['author-link'],
344                 ];
345                 $profile_link = Contact::magicLinkByContact($author, $item['author-link']);
346                 $sparkle = (strpos($profile_link, 'redir/') === 0);
347
348                 $cid = 0;
349                 $pcid = $item['author-id'];
350                 $network = '';
351                 $rel = 0;
352                 $condition = ['uid' => Session::getLocalUser(), 'uri-id' => $item['author-uri-id']];
353                 $contact = DBA::selectFirst('contact', ['id', 'network', 'rel'], $condition);
354                 if (DBA::isResult($contact)) {
355                         $cid = $contact['id'];
356                         $network = $contact['network'];
357                         $rel = $contact['rel'];
358                 }
359
360                 if ($sparkle) {
361                         $status_link = $profile_link . '/status';
362                         $photos_link = str_replace('/profile/', '/photos/', $profile_link);
363                         $profile_link = $profile_link . '/profile';
364                 }
365
366                 if (!empty($pcid)) {
367                         $contact_url = 'contact/' . $pcid;
368                         $posts_link  = $contact_url . '/posts';
369                         $block_link  = $item['self'] ? '' : $contact_url . '/block?t=' . $formSecurityToken;
370                         $ignore_link = $item['self'] ? '' : $contact_url . '/ignore?t=' . $formSecurityToken;
371                 }
372
373                 if ($cid && !$item['self']) {
374                         $contact_url = 'contact/' . $cid;
375                         $posts_link  = $contact_url . '/posts';
376
377                         if (in_array($network, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA])) {
378                                 $pm_url = 'message/new/' . $cid;
379                         }
380                 }
381
382                 if (Session::getLocalUser()) {
383                         $menu = [
384                                 $this->l10n->t('Follow Thread') => $sub_link,
385                                 $this->l10n->t('View Status') => $status_link,
386                                 $this->l10n->t('View Profile') => $profile_link,
387                                 $this->l10n->t('View Photos') => $photos_link,
388                                 $this->l10n->t('Network Posts') => $posts_link,
389                                 $this->l10n->t('View Contact') => $contact_url,
390                                 $this->l10n->t('Send PM') => $pm_url,
391                                 $this->l10n->t('Block') => $block_link,
392                                 $this->l10n->t('Ignore') => $ignore_link
393                         ];
394
395                         if (!empty($item['language'])) {
396                                 $menu[$this->l10n->t('Languages')] = 'javascript:alert(\'' . ItemModel::getLanguageMessage($item) . '\');';
397                         }
398
399                         if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
400                                 in_array($item['network'], Protocol::FEDERATED)) {
401                                 $menu[$this->l10n->t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']) . '&auto=1';
402                         }
403                 } else {
404                         $menu = [$this->l10n->t('View Profile') => $item['author-link']];
405                 }
406
407                 $args = ['item' => $item, 'menu' => $menu];
408
409                 Hook::callAll('item_photo_menu', $args);
410
411                 $menu = $args['menu'];
412
413                 $o = '';
414                 foreach ($menu as $k => $v) {
415                         if (strpos($v, 'javascript:') === 0) {
416                                 $v = substr($v, 11);
417                                 $o .= '<li role="menuitem"><a onclick="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
418                         } elseif ($v) {
419                                 $o .= '<li role="menuitem"><a href="' . $v . '">' . $k . '</a></li>' . PHP_EOL;
420                         }
421                 }
422                 $this->profiler->stopRecording();
423                 return $o;
424         }
425
426         /**
427          * Checks if the activity is visible to current user
428          *
429          * @param array $item Activity item
430          * @return bool Whether the item is visible to the user
431          */
432         public function isVisibleActivity(array $item): bool
433         {
434                 // Empty verb or hidden?
435                 if (empty($item['verb']) || $this->activity->isHidden($item['verb'])) {
436                         return false;
437                 }
438
439                 // Check conditions
440                 return (!($this->activity->match($item['verb'], Activity::FOLLOW) &&
441                         $item['object-type'] === Activity\ObjectType::NOTE &&
442                         empty($item['self']) &&
443                         $item['uid'] == Session::getLocalUser())
444                 );
445         }
446
447         public function expandTags(array $item, bool $setPermissions = false): array
448         {
449                 // Look for any tags and linkify them
450                 $item['inform'] = '';
451                 $private_forum  = false;
452                 $private_id     = null;
453                 $only_to_forum  = false;
454                 $forum_contact  = [];
455                 $receivers      = [];
456
457                 // Convert mentions in the body to a unified format
458                 $item['body'] = BBCode::setMentions($item['body'], $item['uid'], $item['network']);
459
460                 // Search for forum mentions
461                 foreach (Tag::getFromBody($item['body'], Tag::TAG_CHARACTER[Tag::MENTION] . Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]) as $tag) {
462                         $contact = Contact::getByURLForUser($tag[2], $item['uid']);
463                         if (empty($contact)) {
464                                 continue;
465                         }
466
467                         $receivers[] = $contact['id'];
468
469                         if (!empty($item['inform'])) {
470                                 $item['inform'] .= ',';
471                         }
472                         $item['inform'] .= 'cid:' . $contact['id'];
473
474                         if (($item['gravity'] == ItemModel::GRAVITY_COMMENT) || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) {
475                                 continue;
476                         }
477
478                         if (!empty($contact['prv']) || ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION])) {
479                                 $private_forum = $contact['prv'];
480                                 $only_to_forum = ($tag[1] == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]);
481                                 $private_id = $contact['id'];
482                                 $forum_contact = $contact;
483                                 Logger::info('Private forum or exclusive mention', ['url' => $tag[2], 'mention' => $tag[1]]);
484                         } elseif ($item['allow_cid'] == '<' . $contact['id'] . '>') {
485                                 $private_forum = false;
486                                 $only_to_forum = true;
487                                 $private_id = $contact['id'];
488                                 $forum_contact = $contact;
489                                 Logger::info('Public forum', ['url' => $tag[2], 'mention' => $tag[1]]);
490                         } else {
491                                 Logger::info('Post with forum mention will not be converted to a forum post', ['url' => $tag[2], 'mention' => $tag[1]]);
492                         }
493                 }
494                 Logger::info('Got inform', ['inform' => $item['inform']]);
495
496                 if (($item['gravity'] == ItemModel::GRAVITY_PARENT) && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
497                         // we tagged a forum in a top level post. Now we change the post
498                         $item['private'] = $private_forum ? ItemModel::PRIVATE : ItemModel::UNLISTED;
499
500                         if ($only_to_forum) {
501                                 $item['postopts'] = '';
502                         }
503
504                         $item['deny_cid'] = '';
505                         $item['deny_gid'] = '';
506
507                         if ($private_forum) {
508                                 $item['allow_cid'] = '<' . $private_id . '>';
509                                 $item['allow_gid'] = '<' . Group::getIdForForum($forum_contact['id']) . '>';
510                         } else {
511                                 $item['allow_cid'] = '';
512                                 $item['allow_gid'] = '';
513                         }
514                 } elseif ($setPermissions && ($item['gravity'] == ItemModel::GRAVITY_PARENT)) {
515                         if (empty($receivers)) {
516                                 // For security reasons direct posts without any receiver will be posts to yourself
517                                 $self = Contact::selectFirst(['id'], ['uid' => $item['uid'], 'self' => true]);
518                                 $receivers[] = $self['id'];
519                         }
520
521                         $item['private']   = ItemModel::PRIVATE;
522                         $item['allow_cid'] = '';
523                         $item['allow_gid'] = '';
524                         $item['deny_cid']  = '';
525                         $item['deny_gid']  = '';
526
527                         foreach ($receivers as $receiver) {
528                                 $item['allow_cid'] .= '<' . $receiver . '>';
529                         }
530                 }
531                 return $item;
532         }
533
534         public function getAuthorAvatar(array $item): string
535         {
536                 if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
537                         $author_avatar  = $item['contact-id'];
538                         $author_updated = '';
539                         $author_thumb   = $item['contact-avatar'];
540                 } else {
541                         $author_avatar  = $item['author-id'];
542                         $author_updated = $item['author-updated'];
543                         $author_thumb   = $item['author-avatar'];
544                 }
545
546
547                 if (empty($author_thumb) || Photo::isPhotoURI($author_thumb)) {
548                         $author_thumb = Contact::getAvatarUrlForId($author_avatar, Proxy::SIZE_THUMB, $author_updated);
549                 }
550
551                 return $author_thumb;
552         }
553
554         public function getOwnerAvatar(array $item): string
555         {
556                 if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
557                         $owner_avatar  = $item['contact-id'];
558                         $owner_updated = '';
559                         $owner_thumb   = $item['contact-avatar'];
560                 } else {
561                         $owner_avatar   = $item['owner-id'];
562                         $owner_updated  = $item['owner-updated'];
563                         $owner_thumb    = $item['owner-avatar'];
564                 }
565
566                 if (empty($owner_thumb) || Photo::isPhotoURI($owner_thumb)) {
567                         $owner_thumb = Contact::getAvatarUrlForId($owner_avatar, Proxy::SIZE_THUMB, $owner_updated);
568                 }
569
570                 return $owner_thumb;
571         }
572
573         /**
574          * Add a share block for the given url
575          *
576          * @param string $url
577          * @param integer $uid
578          * @param bool $add_media
579          * @return string
580          */
581         public function createSharedPostByUrl(string $url, int $uid = 0, bool $add_media = false): string
582         {
583                 if (!empty($uid)) {
584                         $id = ItemModel::searchByLink($url, $uid);
585                 }
586
587                 if (empty($id)) {
588                         $id = ItemModel::fetchByLink($url);
589                 }
590
591                 if (!$id) {
592                         Logger::notice('Post could not be fetched.', ['url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]);
593                         return '';
594                 }
595
596                 Logger::debug('Fetched shared post', ['id' => $id, 'url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]);
597
598                 $shared_item = Post::selectFirst(['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'], ['id' => $id]);
599                 if (!DBA::isResult($shared_item)) {
600                         Logger::warning('Post does not exist.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
601                         return '';
602                 }
603
604                 return $this->createSharedBlockByArray($shared_item, $add_media);
605         }
606
607         /**
608          * Add a share block for the given uri-id
609          *
610          * @param integer $UriId
611          * @param integer $uid
612          * @param bool $add_media
613          * @return string
614          */
615         public function createSharedPostByUriId(int $UriId, int $uid = 0, bool $add_media = false): string
616         {
617                 $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'];
618                 $shared_item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [ItemModel::PUBLIC, ItemModel::UNLISTED]]);
619                 if (!DBA::isResult($shared_item)) {
620                         Logger::notice('Post does not exist.', ['uri-id' => $UriId, 'uid' => $uid]);
621                         return '';
622                 }
623
624                 return $this->createSharedBlockByArray($shared_item, $add_media);
625         }
626
627         /**
628          * Add a share block for the given guid
629          *
630          * @param string $guid
631          * @param integer $uid
632          * @param bool $add_media
633          * @return string
634          */
635         public function createSharedPostByGuid(string $guid, int $uid = 0, string $host = '', bool $add_media = false): string
636         {
637                 $fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'];
638                 $shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ItemModel::PUBLIC, ItemModel::UNLISTED]]);
639
640                 if (!DBA::isResult($shared_item) && !empty($host) && Diaspora::storeByGuid($guid, $host, true)) {
641                         Logger::debug('Fetched post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
642                         $shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ItemModel::PUBLIC, ItemModel::UNLISTED]]);
643                 } elseif (DBA::isResult($shared_item)) {
644                         Logger::debug('Found existing post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
645                 }
646
647                 if (!DBA::isResult($shared_item)) {
648                         Logger::notice('Post does not exist.', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
649                         return '';
650                 }
651
652                 return $this->createSharedBlockByArray($shared_item, $add_media);
653         }
654
655         /**
656          * Add a share block for the given item array
657          *
658          * @param array $item
659          * @param bool $add_media
660          * @return string
661          */
662         public function createSharedBlockByArray(array $item, bool $add_media = false): string
663         {
664                 if ($item['network'] == Protocol::FEED) {
665                         return PageInfo::getFooterFromUrl($item['plink']);
666                 } elseif (!in_array($item['network'] ?? '', Protocol::FEDERATED)) {
667                         $item['guid'] = '';
668                         $item['uri']  = '';
669                         $item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
670                 } elseif ($add_media) {
671                         $item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
672                 }
673
674                 $shared_content = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid'], $item['uri']);
675
676                 if (!empty($item['title'])) {
677                         $shared_content .= '[h3]' . $item['title'] . "[/h3]\n";
678                 }
679
680                 $shared = ItemModel::getShareArray($item);
681
682                 // If it is a reshared post then reformat it to avoid display problems with two share elements
683                 if (!empty($shared)) {
684                         if (!empty($shared['guid']) && ($encaspulated_share = self::createSharedPostByGuid($shared['guid'], 0, '', $add_media))) {
685                                 $item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $encaspulated_share, $item['body']);
686                         }
687
688                         $item['body'] = HTML::toBBCode(BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::ACTIVITYPUB));
689                 }
690
691                 $shared_content .= $item['body'] . '[/share]';
692
693                 return $shared_content;
694         }
695 }