X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FItem.php;h=1d8742926db6944ddba0600927b55e59ba37ff1e;hb=4889e8248926193b2f5e72060dbf809ee5aca3e6;hp=6d55fd56658b53d3c795243eeefb8ca368453204;hpb=aa6472061b0c321ff3c840ee35417dd713dec987;p=friendica.git diff --git a/src/Model/Item.php b/src/Model/Item.php index 6d55fd5665..1d8742926d 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1,6 +1,6 @@ trim($fields['raw-body'] ?? $fields['body'])]; - + // Remove all media attachments from the body and store them in the post-media table + // @todo On shared postings (Diaspora style and commented reshare) don't fetch content from the shared part $content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']); $content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']); } @@ -241,7 +253,7 @@ class Item if ($item['uid'] == $uid) { self::markForDeletionById($item['id'], PRIORITY_HIGH); } elseif ($item['uid'] != 0) { - Logger::log('Wrong ownership. Not deleting item ' . $item['id']); + Logger::notice('Wrong ownership. Not deleting item', ['id' => $item['id']]); } } DBA::close($items); @@ -501,7 +513,7 @@ class Item public static function isValid(array $item) { // When there is no content then we don't post it - if ($item['body'] . $item['title'] == '') { + if (($item['body'] . $item['title'] == '') && !Post\Media::existsByURIId($item['uri-id'])) { Logger::notice('No body, no title.'); return false; } @@ -744,6 +756,10 @@ class Item return 0; } + if (!isset($item['post-type'])) { + $item['post-type'] = empty($item['title']) ? self::PT_NOTE : self::PT_ARTICLE; + } + $item['wall'] = intval($item['wall'] ?? 0); $item['extid'] = trim($item['extid'] ?? ''); $item['author-name'] = trim($item['author-name'] ?? ''); @@ -762,7 +778,6 @@ class Item $item['coord'] = trim($item['coord'] ?? ''); $item['visible'] = (isset($item['visible']) ? intval($item['visible']) : 1); $item['deleted'] = 0; - $item['post-type'] = ($item['post-type'] ?? '') ?: self::PT_ARTICLE; $item['verb'] = trim($item['verb'] ?? ''); $item['object-type'] = trim($item['object-type'] ?? ''); $item['object'] = trim($item['object'] ?? ''); @@ -810,7 +825,7 @@ class Item $actor = ($item['gravity'] == GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id']; if (!$item['origin'] && ($item['uid'] != 0) && Contact::isSharing($actor, $item['uid'])) { - $item['post-type'] = self::PT_FOLLOWER; + $item['post-reason'] = self::PR_FOLLOWER; } // Ensure that there is an avatar cache @@ -945,6 +960,8 @@ class Item self::setOwnerforResharedItem($item); } + Post\Media::insertFromAttachmentData($item['uri-id'], $item['body']); + // Remove all media attachments from the body and store them in the post-media table $item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']); $item['raw-body'] = self::setHashtags($item['raw-body']); @@ -1064,7 +1081,7 @@ class Item Hook::callAll('post_local_end', $posted_item); } else { Hook::callAll('post_remote_end', $posted_item); - } + } } if ($posted_item['gravity'] === GRAVITY_PARENT) { @@ -1089,7 +1106,7 @@ class Item if ($transmit) { // Don't relay participation messages - if (($posted_item['verb'] == Activity::FOLLOW) && + if (($posted_item['verb'] == Activity::FOLLOW) && (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid)))) { Logger::info('Participation messages will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'verb' => $posted_item['verb']]); $transmit = false; @@ -1115,7 +1132,7 @@ class Item */ private static function setOwnerforResharedItem(array $item) { - $parent = Post::selectFirst(['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-type'], + $parent = Post::selectFirst(['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'], ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); if (!DBA::isResult($parent)) { Logger::error('Parent not found', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); @@ -1135,7 +1152,7 @@ class Item } if ($author['contact-type'] != Contact::TYPE_COMMUNITY) { - if ($parent['post-type'] == self::PT_ANNOUNCEMENT) { + if ($parent['post-reason'] == self::PR_ANNOUNCEMENT) { Logger::info('The parent is already marked as announced: quit', ['causer' => $parent['causer-id'], 'owner' => $parent['owner-id'], 'author' => $parent['author-id'], 'uid' => $item['uid']]); return; } @@ -1144,8 +1161,8 @@ class Item Logger::info('The resharer is no forum: quit', ['resharer' => $item['author-id'], 'owner' => $parent['owner-id'], 'author' => $parent['author-id'], 'uid' => $item['uid']]); return; } - self::update(['post-type' => self::PT_ANNOUNCEMENT, 'causer-id' => $item['author-id']], ['id' => $parent['id']]); - Logger::info('Set announcement post-type', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); + self::update(['post-reason' => self::PR_ANNOUNCEMENT, 'causer-id' => $item['author-id']], ['id' => $parent['id']]); + Logger::info('Set announcement post-reason', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); return; } @@ -1169,7 +1186,7 @@ class Item if (Contact::isSharing($item['author-id'], $uid)) { $fields = []; } else { - $fields = ['post-type' => self::PT_TAG]; + $fields = ['post-reason' => self::PR_TAG]; } $stored = self::storeForUserByUriId($item['uri-id'], $uid, $fields); @@ -1289,7 +1306,7 @@ class Item return 0; } - $item['post-type'] = self::PT_STORED; + $item['post-reason'] = self::PR_STORED; $item = array_merge($item, $fields); @@ -1416,7 +1433,7 @@ class Item unset($item['starred']); unset($item['postopts']); unset($item['inform']); - unset($item['post-type']); + unset($item['post-reason']); if ($item['uri-id'] == $item['parent-uri-id']) { $item['contact-id'] = $item['owner-id']; } else { @@ -1479,7 +1496,7 @@ class Item unset($item['starred']); unset($item['postopts']); unset($item['inform']); - unset($item['post-type']); + unset($item['post-reason']); $item['contact-id'] = Contact::getIdForURL($item['author-link']); $public_shadow = self::insert($item, false, true); @@ -1637,7 +1654,7 @@ class Item // or it had been done by a "regular" contact. if (!empty($arr['wall'])) { $condition = ['id' => $arr['contact-id']]; - } else { + } else { $condition = ['id' => $arr['contact-id'], 'self' => false]; } DBA::update('contact', ['failed' => false, 'success_update' => $arr['received'], 'last-item' => $arr['received']], $condition); @@ -1690,7 +1707,7 @@ class Item return ("[bookmark=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/bookmark]"); }, $body); - $body = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism", + $body = preg_replace_callback("/\[attachment (.*?)\](.*?)\[\/attachment\]/ism", function ($match) { return ("[attachment " . str_replace("#", "#", $match[1]) . "]" . $match[2] . "[/attachment]"); }, $body); @@ -1772,7 +1789,7 @@ class Item } } } - + if (!$mention) { if (($community_page || $prvgroup) && !$item['wall'] && !$item['origin'] && ($item['gravity'] == GRAVITY_PARENT)) { @@ -2157,7 +2174,7 @@ class Item // Only expire posts, not photos and photo comments - if (!$expire_photos && strlen($item['resource-id'])) { + if (!$expire_photos && !empty($item['resource-id'])) { continue; } elseif (!$expire_starred && intval($item['starred'])) { continue; @@ -2177,9 +2194,15 @@ class Item public static function firstPostDate($uid, $wall = false) { - $condition = ['gravity' => GRAVITY_PARENT, 'uid' => $uid, 'wall' => $wall, 'deleted' => false, 'visible' => true]; + $user = User::getById($uid, ['register_date']); + if (empty($user)) { + return false; + } + + $condition = ["`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?", + $uid, $wall, $user['register_date']]; $params = ['order' => ['received' => false]]; - $thread = Post::selectFirst(['received'], $condition, $params); + $thread = Post::selectFirstThread(['received'], $condition, $params); if (DBA::isResult($thread)) { $postdate = substr(DateTimeFormat::local($thread['received']), 0, 10); return $postdate; @@ -2426,10 +2449,10 @@ class Item /** * Get a permission SQL string for the given user - * - * @param int $owner_id - * @param string $table - * @return string + * + * @param int $owner_id + * @param string $table + * @return string */ public static function getPermissionsSQLByUserId(int $owner_id, string $table = '') { @@ -2475,22 +2498,23 @@ class Item /** * get translated item type * - * @param $item + * @param array $item + * @param \Friendica\Core\L10n $l10n * @return string */ - public static function postType($item) + public static function postType(array $item, \Friendica\Core\L10n $l10n) { if (!empty($item['event-id'])) { - return DI::l10n()->t('event'); + return $l10n->t('event'); } elseif (!empty($item['resource-id'])) { - return DI::l10n()->t('photo'); + return $l10n->t('photo'); } elseif ($item['gravity'] == GRAVITY_ACTIVITY) { - return DI::l10n()->t('activity'); + return $l10n->t('activity'); } elseif ($item['gravity'] == GRAVITY_COMMENT) { - return DI::l10n()->t('comment'); + return $l10n->t('comment'); } - return DI::l10n()->t('post'); + return $l10n->t('post'); } /** @@ -2616,7 +2640,10 @@ class Item unset($hook_data); } + $body = $item['body'] ?? ''; + $item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", '', $item['body']); self::putInCache($item); + $item['body'] = $body; $s = $item["rendered-html"]; $hook_data = [ @@ -2636,71 +2663,276 @@ class Item return $s; } - $as = ''; - $vhead = false; - foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) { - $mime = $attachment['mimetype']; + $shared = BBCode::fetchShareAttributes($item['body']); + if (!empty($shared['guid'])) { + $shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]); + $shared_uri_id = $shared_item['uri-id'] ?? 0; + $shared_links = [strtolower($shared_item['plink'] ?? '')]; + $attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']); + $s = self::addVisualAttachments($attachments, $item, $s, true); + $s = self::addLinkAttachment($attachments, $body, $s, true, []); + $s = self::addNonVisualAttachments($attachments, $item, $s, true); + $shared_links = array_merge($shared_links, array_column($attachments['visual'], 'url')); + $shared_links = array_merge($shared_links, array_column($attachments['link'], 'url')); + $shared_links = array_merge($shared_links, array_column($attachments['additional'], 'url')); + $body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body); + } else { + $shared_uri_id = 0; + $shared_links = []; + } + + $attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'], $shared_links); + $s = self::addVisualAttachments($attachments, $item, $s, false); + $s = self::addLinkAttachment($attachments, $body, $s, false, $shared_links); + $s = self::addNonVisualAttachments($attachments, $item, $s, false); + + // Map. + if (strpos($s, '
') !== false && !empty($item['coord'])) { + $x = Map::byCoordinates(trim($item['coord'])); + if ($x) { + $s = preg_replace('/\
/', '$0' . $x, $s); + } + } + + // Replace friendica image url size with theme preference. + if (!empty($a->theme_info['item_image_size'])) { + $ps = $a->theme_info['item_image_size']; + $s = preg_replace('|(]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|', "$1-" . $ps, $s); + } + + $s = HTML::applyContentFilter($s, $filter_reasons); + + $hook_data = ['item' => $item, 'html' => $s]; + Hook::callAll('prepare_body_final', $hook_data); + + return $hook_data['html']; + } + + /** + * Check if the body contains a link + * + * @param string $body + * @param string $url + * @return bool + */ + public static function containsLink(string $body, string $url) + { + if (strpos($body, $url)) { + return true; + } + foreach ([0, 1, 2] as $size) { + if (preg_match('#/photo/.*-' . $size . '\.#ism', $url) && + strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url)) { + return true; + } + } + return false; + } + + /** + * Add visual attachments to the content + * + * @param array $attachments + * @param array $item + * @param string $content + * @return string modified content + */ + private static function addVisualAttachments(array $attachments, array $item, string $content, bool $shared) + { + $stamp1 = microtime(true); + $leading = ''; + $trailing = ''; + + // @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty. + foreach ($attachments['visual'] as $attachment) { + if (self::containsLink($item['body'], $attachment['url'])) { + continue; + } $author = ['uid' => 0, 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link']]; $the_url = Contact::magicLinkByContact($author, $attachment['url']); - if (strpos($mime, 'video') !== false) { - if (!$vhead) { - $vhead = true; - DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('videos_head.tpl')); - } + if (!empty($attachment['preview'])) { + $preview_url = Proxy::proxifyUrl(Contact::magicLinkByContact($author, $attachment['preview'])); + } else { + $preview_url = ''; + } - $as .= Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [ + if (($attachment['filetype'] == 'video')) { + /// @todo Move the template to /content as well + $media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [ '$video' => [ - 'id' => $item['author-id'], - 'title' => DI::l10n()->t('View Video'), + 'id' => $attachment['id'], + 'src' => $the_url, + 'name' => $attachment['name'] ?: $attachment['url'], + 'preview' => $preview_url, + 'mime' => $attachment['mimetype'], + ], + ]); + if ($item['post-type'] == Item::PT_VIDEO) { + $leading .= $media; + } else { + $trailing .= $media; + } + } elseif ($attachment['filetype'] == 'audio') { + $media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/audio.tpl'), [ + '$audio' => [ + 'id' => $attachment['id'], 'src' => $the_url, - 'mime' => $mime, + 'name' => $attachment['name'] ?: $attachment['url'], + 'mime' => $attachment['mimetype'], + ], + ]); + if ($item['post-type'] == Item::PT_AUDIO) { + $leading .= $media; + } else { + $trailing .= $media; + } + } elseif ($attachment['filetype'] == 'image') { + if (empty($preview_url) && (max($attachment['width'], $attachment['height']) > 600)) { + $preview_url = Proxy::proxifyUrl($the_url, false, ($attachment['width'] > $attachment['height']) ? Proxy::SIZE_MEDIUM : Proxy::SIZE_LARGE); + } + $media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image.tpl'), [ + '$image' => [ + 'src' => Proxy::proxifyUrl($the_url), + 'preview' => $preview_url, + 'attachment' => $attachment, ], ]); + // On Diaspora posts the attached pictures are leading + if ($item['network'] == Protocol::DIASPORA) { + $leading .= $media; + } else { + $trailing .= $media; + } } + } - $filetype = strtolower(substr($mime, 0, strpos($mime, '/'))); - if ($filetype) { - $filesubtype = strtolower(substr($mime, strpos($mime, '/') + 1)); - $filesubtype = str_replace('.', '-', $filesubtype); - } else { - $filetype = 'unkn'; - $filesubtype = 'unkn'; + if ($shared) { + $content = str_replace(BBCode::TOP_ANCHOR, '
' . $leading . '
' . BBCode::TOP_ANCHOR, $content); + $content = str_replace(BBCode::BOTTOM_ANCHOR, '
' . $trailing . '
' . BBCode::BOTTOM_ANCHOR, $content); + } else { + if ($leading != '') { + $content = '
' . $leading . '
' . $content; } - $title = Strings::escapeHtml(trim(($attachment['description'] ?? '') ?: $attachment['url'])); - $title .= ' ' . ($attachment['size'] ?? 0) . ' ' . DI::l10n()->t('bytes'); - - $icon = '
'; - $as .= '' . $icon . ''; + if ($trailing != '') { + $content .= '
' . $trailing . '
'; + } } - if ($as != '') { - $s .= '
'.$as.'
'; + DI::profiler()->saveTimestamp($stamp1, 'rendering'); + return $content; + } + + /** + * Add link attachment to the content + * + * @param array $attachments + * @param string $body + * @param string $content + * @param bool $shared + * @param array $ignore_links A list of URLs to ignore + * @return string modified content + */ + private static function addLinkAttachment(array $attachments, string $body, string $content, bool $shared, array $ignore_links) + { + $stamp1 = microtime(true); + // @ToDo Check only for audio and video + $preview = empty($attachments['visual']); + + if (!empty($attachments['link'])) { + foreach ($attachments['link'] as $link) { + $found = false; + foreach ($ignore_links as $ignore_link) { + if (Strings::compareLink($link['url'], $ignore_link)) { + $found = true; + } + } + if (!$found) { + $attachment = $link; + } + } } - // Map. - if (strpos($s, '
') !== false && !empty($item['coord'])) { - $x = Map::byCoordinates(trim($item['coord'])); - if ($x) { - $s = preg_replace('/\
/', '$0' . $x, $s); + if (!empty($attachment)) { + $data = [ + 'after' => '', + 'author_name' => $attachment['author-name'] ?? '', + 'author_url' => $attachment['author-url'] ?? '', + 'description' => $attachment['description'] ?? '', + 'image' => '', + 'preview' => '', + 'provider_name' => $attachment['publisher-name'] ?? '', + 'provider_url' => $attachment['publisher-url'] ?? '', + 'text' => '', + 'title' => $attachment['name'] ?? '', + 'type' => 'link', + 'url' => $attachment['url']]; + + if ($preview) { + if ($attachment['preview-width'] >= 500) { + $data['image'] = $attachment['preview'] ?? ''; + } else { + $data['preview'] = $attachment['preview'] ?? ''; + } } + } elseif (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $body, $match)) { + $data = BBCode::getAttachmentData($match[1]); } + DI::profiler()->saveTimestamp($stamp1, 'rendering'); - // Replace friendica image url size with theme preference. - if (!empty($a->theme_info['item_image_size'])) { - $ps = $a->theme_info['item_image_size']; - $s = preg_replace('|(]+src="[^"]+/photo/[0-9a-f]+)-[0-9]|', "$1-" . $ps, $s); + if (isset($data['url']) && !in_array($data['url'], $ignore_links)) { + // @todo Use a template + $rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data); + if ($shared) { + return str_replace(BBCode::BOTTOM_ANCHOR, BBCode::BOTTOM_ANCHOR . $rendered, $content); + } else { + return $content . $rendered; + } } + return $content; + } - $s = HTML::applyContentFilter($s, $filter_reasons); + /** + * Add non visual attachments to the content + * + * @param array $attachments + * @param array $item + * @param string $content + * @return string modified content + */ + private static function addNonVisualAttachments(array $attachments, array $item, string $content) + { + $stamp1 = microtime(true); + $trailing = ''; + foreach ($attachments['additional'] as $attachment) { + if (strpos($item['body'], $attachment['url'])) { + continue; + } - $hook_data = ['item' => $item, 'html' => $s]; - Hook::callAll('prepare_body_final', $hook_data); + $author = ['uid' => 0, 'id' => $item['author-id'], + 'network' => $item['author-network'], 'url' => $item['author-link']]; + $the_url = Contact::magicLinkByContact($author, $attachment['url']); - return $hook_data['html']; + $title = Strings::escapeHtml(trim(($attachment['description'] ?? '') ?: $attachment['url'])); + + if (!empty($attachment['size'])) { + $title .= ' ' . $attachment['size'] . ' ' . DI::l10n()->t('bytes'); + } + + /// @todo Use a template + $icon = '
'; + $trailing .= '' . $icon . ''; + } + + if ($trailing != '') { + $content .= '
' . $trailing . '
'; + } + + DI::profiler()->saveTimestamp($stamp1, 'rendering'); + return $content; } /** @@ -2791,8 +3023,8 @@ class Item } /** - * Return the URI for a link to the post - * + * Return the URI for a link to the post + * * @param string $uri URI or link to post * * @return string URI