X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FItem.php;h=1d8742926db6944ddba0600927b55e59ba37ff1e;hb=4889e8248926193b2f5e72060dbf809ee5aca3e6;hp=6114457d64bf362256c4452e9c3feceba49aa006;hpb=ce6ad1aa73885196ce5393a910d1b57774a940d4;p=friendica.git diff --git a/src/Model/Item.php b/src/Model/Item.php index 6114457d64..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']); } @@ -212,7 +207,8 @@ class Item DBA::close($items); foreach ($notify_items as $notify_item) { - Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $notify_item); + $post = Post::selectFirst(['uri-id', 'uid'], ['id' => $notify_item]); + Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, (int)$post['uri-id'], (int)$post['uid']); } return $rows; @@ -257,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); @@ -334,14 +330,6 @@ class Item Post\DeliveryData::delete($item['uri-id']); - // When the permission set will be used in photo and events as well, - // this query here needs to be extended. - // @todo Currently deactivated. We need the permission set in the deletion process. - // This is a reminder to add the removal somewhere else. - //if (!empty($item['psid']) && !self::exists(['psid' => $item['psid'], 'deleted' => false])) { - // DBA::delete('permissionset', ['id' => $item['psid']], ['cascade' => false]); - //} - // If it's the parent of a comment thread, kill all the kids if ($item['gravity'] == GRAVITY_PARENT) { self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority); @@ -354,7 +342,7 @@ class Item // send the notification upstream/downstream if ($priority) { - Worker::add(['priority' => $priority, 'dont_fork' => true], "Notifier", Delivery::DELETION, intval($item['id'])); + Worker::add(['priority' => $priority, 'dont_fork' => true], "Notifier", Delivery::DELETION, (int)$item['uri-id'], (int)$item['uid']); } } elseif ($item['uid'] != 0) { Post\User::update($item['uri-id'], $item['uid'], ['hidden' => true]); @@ -525,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; } @@ -768,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'] ?? ''); @@ -786,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'] ?? ''); @@ -834,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 @@ -882,6 +873,8 @@ class Item $item['wall'] = $toplevel_parent['wall']; } else { $item['wall'] = false; + // Participations are technical messages, so they are set to "seen" automatically + $item['unseen'] = false; } /* @@ -959,10 +952,16 @@ class Item $item['deny_gid'] ); + if (!empty($item['extid'])) { + $item['external-id'] = ItemURI::getIdByURI($item['extid']); + } + if ($item['verb'] == Activity::ANNOUNCE) { 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']); @@ -988,6 +987,10 @@ class Item unset($item['event-id']); } + if (empty($item['causer-id'])) { + unset($item['causer-id']); + } + Post::insert($item['uri-id'], $item); if ($item['gravity'] == GRAVITY_PARENT) { @@ -1026,122 +1029,95 @@ class Item return 0; } - $id = Post\User::insert($item['uri-id'], $item['uid'], $item); - if (!$id) { + $post_user_id = Post\User::insert($item['uri-id'], $item['uid'], $item); + if (!$post_user_id) { Logger::notice('Post-User is already inserted - aborting', ['uid' => $item['uid'], 'uri-id' => $item['uri-id']]); return 0; } if ($item['gravity'] == GRAVITY_PARENT) { - $item['post-user-id'] = $id; + $item['post-user-id'] = $post_user_id; Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item); } - // Remove all fields that aren't part of the item table - $table_fields = DBStructure::getFieldsForTable('item', $item); - - // We remove all legacy fields that now are stored in other tables - foreach (self::LEGACY_FIELDLIST as $field) { - unset($table_fields[$field]); - } - - $result = DBA::insert('item', $table_fields); - - // When the item was successfully stored we fetch the ID of the item. - $current_post = DBA::lastInsertId(); + Logger::notice('created item', ['post-id' => $post_user_id, 'uid' => $item['uid'], 'network' => $item['network'], 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); - if (empty($current_post) || !DBA::isResult($result)) { + $posted_item = Post::selectFirst(self::ITEM_FIELDLIST, ['post-user-id' => $post_user_id]); + if (!DBA::isResult($posted_item)) { // On failure store the data into a spool file so that the "SpoolPost" worker can try again later. - Logger::warning('Could not store item. it will be spooled', ['result' => $result, 'id' => $current_post]); + Logger::warning('Could not store item. it will be spooled', ['id' => $post_user_id]); self::spool($orig_item); return 0; } - Logger::notice('created item', ['id' => $current_post, 'uid' => $item['uid'], 'network' => $item['network'], 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); - - if (!$parent_id || ($item['gravity'] === GRAVITY_PARENT)) { - $parent_id = $current_post; - } - - // Set parent id - DBA::update('item', ['parent' => $parent_id], ['id' => $current_post]); - - $item['id'] = $current_post; - $item['parent'] = $parent_id; - // update the commented timestamp on the parent if (DI::config()->get('system', 'like_no_comment')) { // Update when it is a comment - $update_commented = in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]); + $update_commented = in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]); } else { // Update when it isn't a follow or tag verb - $update_commented = !in_array($item['verb'], [Activity::FOLLOW, Activity::TAG]); + $update_commented = !in_array($posted_item['verb'], [Activity::FOLLOW, Activity::TAG]); } if ($update_commented) { - Post::update(['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]); + $fields = ['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()]; } else { - Post::update(['changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]); + $fields = ['changed' => DateTimeFormat::utcNow()]; } + Post::update($fields, ['uri-id' => $posted_item['parent-uri-id'], 'uid' => $posted_item['uid']]); + // In that function we check if this is a forum post. Additionally we delete the item under certain circumstances - if (self::tagDeliver($item['uid'], $current_post)) { + if (self::tagDeliver($posted_item['uid'], $post_user_id)) { // Get the user information for the logging $user = User::getById($uid); - Logger::notice('Item had been deleted', ['id' => $current_post, 'user' => $uid, 'account-type' => $user['account-type']]); + Logger::notice('Item had been deleted', ['id' => $post_user_id, 'user' => $uid, 'account-type' => $user['account-type']]); return 0; } if (!$dontcache) { - $posted_item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $current_post]); - if (DBA::isResult($posted_item)) { - if ($notify) { - Hook::callAll('post_local_end', $posted_item); - } else { - Hook::callAll('post_remote_end', $posted_item); - } + if ($notify) { + Hook::callAll('post_local_end', $posted_item); } else { - Logger::log('new item not found in DB, id ' . $current_post); + Hook::callAll('post_remote_end', $posted_item); } } - if ($item['gravity'] === GRAVITY_PARENT) { - self::addShadow($current_post); + if ($posted_item['gravity'] === GRAVITY_PARENT) { + self::addShadow($post_user_id); } else { - self::addShadowPost($current_post); + self::addShadowPost($post_user_id); } - self::updateContact($item); + self::updateContact($posted_item); - Post\UserNotification::setNotification($item['uri-id'], $item['uid']); + Post\UserNotification::setNotification($posted_item['uri-id'], $posted_item['uid']); - check_user_notification($item['uri-id'], $item['uid']); - //check_user_notification($current_post); + check_user_notification($posted_item['uri-id'], $posted_item['uid']); // Distribute items to users who subscribed to their tags - self::distributeByTags($item); + self::distributeByTags($posted_item); // Automatically reshare the item if the "remote_self" option is selected - self::autoReshare($item); + self::autoReshare($posted_item); - $transmit = $notify || ($item['visible'] && ($parent_origin || $item['origin'])); + $transmit = $notify || ($posted_item['visible'] && ($parent_origin || $posted_item['origin'])); if ($transmit) { - $transmit_item = Post::selectFirst(['verb', 'origin'], ['id' => $item['id']]); // Don't relay participation messages - if (($transmit_item['verb'] == Activity::FOLLOW) && - (!$transmit_item['origin'] || ($item['author-id'] != Contact::getPublicIdByUserId($uid)))) { - Logger::info('Participation messages will not be relayed', ['item' => $item['id'], 'uri' => $item['uri'], 'verb' => $transmit_item['verb']]); + 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; } } if ($transmit) { - Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, $current_post); + Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']); } - return $current_post; + return $post_user_id; } /** @@ -1156,14 +1132,14 @@ 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']]); return; } - $author = Contact::selectFirst(['url', 'contact-type'], ['id' => $item['author-id']]); + $author = Contact::selectFirst(['url', 'contact-type', 'network'], ['id' => $item['author-id']]); if (!DBA::isResult($author)) { Logger::error('Author not found', ['id' => $item['author-id']]); return; @@ -1171,12 +1147,12 @@ class Item $cid = Contact::getIdForURL($author['url'], $item['uid']); if (empty($cid) || !Contact::isSharing($cid, $item['uid'])) { - Logger::info('The resharer is not a following contact: quit', ['resharer' => $author['url'], 'uid' => $item['uid']]); + Logger::info('The resharer is not a following contact: quit', ['resharer' => $author['url'], 'uid' => $item['uid'], 'cid' => $cid]); return; } 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; } @@ -1185,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; } @@ -1210,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); @@ -1227,9 +1203,10 @@ class Item */ public static function distribute($itemid, $signed_text = '') { - $condition = ["`id` IN (SELECT `parent` FROM `post-view` WHERE `id` = ?)", $itemid]; + $condition = ["`id` IN (SELECT `parent` FROM `post-user-view` WHERE `id` = ?)", $itemid]; $parent = Post::selectFirst(['owner-id'], $condition); if (!DBA::isResult($parent)) { + Logger::warning('Item not found', ['condition' => $condition]); return; } @@ -1239,6 +1216,7 @@ class Item 'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED]]; $item = Post::selectFirst(self::ITEM_FIELDLIST, $condition); if (!DBA::isResult($item)) { + Logger::warning('Item not found', ['condition' => $condition]); return; } @@ -1328,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 +1394,7 @@ class Item private static function addShadow($itemid) { $fields = ['uid', 'private', 'visible', 'deleted', 'network', 'uri-id']; - $condition = ['id' => $itemid, 'parent' => [0, $itemid]]; + $condition = ['id' => $itemid, 'gravity' => GRAVITY_PARENT]; $item = Post::selectFirst($fields, $condition); if (!DBA::isResult($item)) { @@ -1455,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 { @@ -1518,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); @@ -1542,6 +1520,10 @@ class Item */ private static function getLanguage(array $item) { + if (!empty($item['language'])) { + return $item['language']; + } + if (!in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) || empty($item['body'])) { return ''; } @@ -1672,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); @@ -1725,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); @@ -1807,12 +1789,11 @@ class Item } } } - + if (!$mention) { if (($community_page || $prvgroup) && !$item['wall'] && !$item['origin'] && ($item['gravity'] == GRAVITY_PARENT)) { - Logger::info('Delete private group/communiy top-level item without mention', ['id' => $item_id, 'guid'=> $item['guid']]); - DBA::delete('item', ['uri-id' => $item['uri-id'], 'uid' => $item['uid']]); + Logger::info('Delete private group/communiy top-level item without mention', ['id' => $item['id'], 'guid'=> $item['guid']]); Post\User::delete(['uri-id' => $item['uri-id'], 'uid' => $item['uid']]); return true; } @@ -1860,11 +1841,11 @@ class Item $fields = ['wall' => true, 'origin' => true, 'forum_mode' => $forum_mode, 'contact-id' => $self['id'], 'owner-id' => $owner_id, 'private' => $private, 'psid' => $psid]; - self::update($fields, ['id' => $item_id]); + self::update($fields, ['id' => $item['id']]); - Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, $item_id); + Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, (int)$item['uri-id'], (int)$item['uid']); - self::performActivity($item_id, 'announce', $uid); + self::performActivity($item['id'], 'announce', $uid); return false; } @@ -2193,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; @@ -2213,11 +2194,18 @@ 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)) { - return substr(DateTimeFormat::local($thread['received']), 0, 10); + $postdate = substr(DateTimeFormat::local($thread['received']), 0, 10); + return $postdate; } return false; } @@ -2461,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 = '') { @@ -2510,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'); } /** @@ -2534,12 +2523,11 @@ class Item * Body is preserved to avoid side-effects as we modify it just-in-time for spoilers and private image links * * @param array $item - * @param bool $update * * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @todo Remove reference, simply return "rendered-html" and "rendered-hash" */ - public static function putInCache(&$item, $update = false) + public static function putInCache(&$item) { // Save original body to prevent addons to modify it $body = $item['body']; @@ -2563,17 +2551,8 @@ class Item $item['rendered-hash'] = $hook_data['rendered-hash']; unset($hook_data); - // Force an update if the generated values differ from the existing ones - if ($rendered_hash != $item['rendered-hash']) { - $update = true; - } - - // Only compare the HTML when we forcefully ignore the cache - if (DI::config()->get('system', 'ignore_cache') && ($rendered_html != $item['rendered-html'])) { - $update = true; - } - - if ($update && !empty($item['id'])) { + // Update if the generated values differ from the existing ones + if ((($rendered_hash != $item['rendered-hash']) || ($rendered_html != $item['rendered-html'])) && !empty($item['id'])) { self::update( [ 'rendered-html' => $item['rendered-html'], @@ -2661,15 +2640,10 @@ class Item unset($hook_data); } - // Update the cached values if there is no "zrl=..." on the links. - $update = (!Session::isAuthenticated() && ($item["uid"] == 0)); - - // Or update it if the current viewer is the intented viewer. - if (($item["uid"] == local_user()) && ($item["uid"] != 0)) { - $update = true; - } - - self::putInCache($item, $update); + $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 = [ @@ -2689,48 +2663,28 @@ 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']; - - $the_url = Contact::magicLinkById($item['author-id'], $attachment['url']); - - if (strpos($mime, 'video') !== false) { - if (!$vhead) { - $vhead = true; - DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('videos_head.tpl')); - } - - $as .= Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [ - '$video' => [ - 'id' => $item['author-id'], - 'title' => DI::l10n()->t('View Video'), - 'src' => $the_url, - 'mime' => $mime, - ], - ]); - } - - $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'; - } - - $title = Strings::escapeHtml(trim(($attachment['description'] ?? '') ?: $attachment['url'])); - $title .= ' ' . ($attachment['size'] ?? 0) . ' ' . DI::l10n()->t('bytes'); - - $icon = '
'; - $as .= '' . $icon . ''; + $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 = []; } - if ($as != '') { - $s .= '
'.$as.'
'; - } + $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'])) { @@ -2754,6 +2708,233 @@ class Item 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 (!empty($attachment['preview'])) { + $preview_url = Proxy::proxifyUrl(Contact::magicLinkByContact($author, $attachment['preview'])); + } else { + $preview_url = ''; + } + + if (($attachment['filetype'] == 'video')) { + /// @todo Move the template to /content as well + $media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [ + '$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, + '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; + } + } + } + + 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; + } + + if ($trailing != '') { + $content .= '
' . $trailing . '
'; + } + } + + 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; + } + } + } + + 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'); + + 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; + } + + /** + * 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; + } + + $author = ['uid' => 0, 'id' => $item['author-id'], + 'network' => $item['author-network'], 'url' => $item['author-link']]; + $the_url = Contact::magicLinkByContact($author, $attachment['url']); + + $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; + } + /** * get private link for item * @@ -2842,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