X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=src%2FModel%2FItem.php;h=1d8742926db6944ddba0600927b55e59ba37ff1e;hb=4889e8248926193b2f5e72060dbf809ee5aca3e6;hp=5041b7ef5b6544c28cc44cdacf0a03a2207fd632;hpb=0ef5bf29d411bc71aaf11954dde0f345117b7b00;p=friendica.git diff --git a/src/Model/Item.php b/src/Model/Item.php index 5041b7ef5b..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']); } @@ -208,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; @@ -247,12 +247,13 @@ class Item while ($item = Post::fetch($items)) { if (in_array($item['uid'], [$uid, 0])) { Post\User::update($item['uri-id'], $uid, ['hidden' => true], true); + Post\ThreadUser::update($item['uri-id'], $uid, ['hidden' => true], true); } 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); @@ -319,10 +320,9 @@ class Item // Set the item to "deleted" $item_fields = ['deleted' => true, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()]; - DBA::update('item', $item_fields, ['id' => $item['id']]); + Post::update($item_fields, ['id' => $item['id']]); Post\Category::storeTextByURIId($item['uri-id'], $item['uid'], ''); - self::deleteThread($item['id'], $item['parent-uri-id']); if (!Post::exists(["`uri-id` = ? AND `uid` != 0 AND NOT `deleted`", $item['uri-id']])) { self::markForDeletion(['uri-id' => $item['uri-id'], 'uid' => 0, 'deleted' => false], $priority); @@ -330,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); @@ -350,10 +342,11 @@ 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]); + Post\ThreadUser::update($item['uri-id'], $item['uid'], ['hidden' => true]); } Logger::info('Item has been marked for deletion.', ['id' => $item_id]); @@ -520,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; } @@ -763,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'] ?? ''); @@ -781,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'] ?? ''); @@ -829,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 @@ -877,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; } /* @@ -899,8 +897,8 @@ class Item // If its a post that originated here then tag the thread as "mention" if ($item['origin'] && $item['uid']) { - DBA::update('thread', ['mention' => true], ['iid' => $parent_id]); - Logger::info('tagged thread as mention', ['parent' => $parent_id, 'uid' => $item['uid']]); + DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]); + Logger::info('tagged thread as mention', ['parent' => $parent_id, 'parent-uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]); } // Update the contact relations @@ -918,7 +916,7 @@ class Item $item["global"] = true; // Set the global flag on all items if this was a global item entry - DBA::update('item', ['global' => true], ['uri-id' => $item['uri-id']]); + Post::update(['global' => true], ['uri-id' => $item['uri-id']]); } else { $item['global'] = Post::exists(['uid' => 0, 'uri-id' => $item['uri-id']]); } @@ -954,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']); @@ -979,6 +983,20 @@ class Item Post\Media::insertFromAttachment($item['uri-id'], $item['attach']); } + if (empty($item['event-id'])) { + unset($item['event-id']); + } + + if (empty($item['causer-id'])) { + unset($item['causer-id']); + } + + Post::insert($item['uri-id'], $item); + + if ($item['gravity'] == GRAVITY_PARENT) { + Post\Thread::insert($item['uri-id'], $item); + } + if (!in_array($item['verb'], self::ACTIVITIES)) { Post\Content::insert($item['uri-id'], $item); } @@ -1005,133 +1023,101 @@ class Item Tag::storeFromBody($item['uri-id'], $item['body']); } - $id = Post\User::insert($item['uri-id'], $item['uid'], $item); - if (!$id) { - Logger::notice('Post-User is already inserted - aborting', ['uid' => $item['uid'], 'uri-id' => $item['uri-id']]); - return 0; - } - - if ($item['gravity'] == GRAVITY_PARENT) { - Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item); - } - - $condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], 'network' => $item['network']]; + $condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid']]; if (Post::exists($condition)) { Logger::notice('Item is already inserted - aborting', $condition); return 0; } - // 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]); + $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; } - $result = DBA::insert('item', $table_fields); + if ($item['gravity'] == GRAVITY_PARENT) { + $item['post-user-id'] = $post_user_id; + Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item); + } - // 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) { - DBA::update('item', ['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]); + $fields = ['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()]; } else { - DBA::update('item', ['changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]); + $fields = ['changed' => DateTimeFormat::utcNow()]; } - if ($item['gravity'] === GRAVITY_PARENT) { - self::addThread($current_post); - } else { - self::updateThread($parent_id); - } + 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; } /** @@ -1146,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; @@ -1161,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; } @@ -1175,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; } @@ -1200,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); @@ -1217,18 +1203,20 @@ 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; } // Only distribute public items from native networks $condition = ['id' => $itemid, 'uid' => 0, 'network' => array_merge(Protocol::FEDERATED ,['']), - 'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => [self::PUBLIC, self::UNLISTED]]; + '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; } @@ -1318,7 +1306,7 @@ class Item return 0; } - $item['post-type'] = self::PT_STORED; + $item['post-reason'] = self::PR_STORED; $item = array_merge($item, $fields); @@ -1405,8 +1393,8 @@ class Item */ private static function addShadow($itemid) { - $fields = ['uid', 'private', 'moderated', 'visible', 'deleted', 'network', 'uri-id']; - $condition = ['id' => $itemid, 'parent' => [0, $itemid]]; + $fields = ['uid', 'private', 'visible', 'deleted', 'network', 'uri-id']; + $condition = ['id' => $itemid, 'gravity' => GRAVITY_PARENT]; $item = Post::selectFirst($fields, $condition); if (!DBA::isResult($item)) { @@ -1419,7 +1407,7 @@ class Item } // Is it a visible public post? - if (!$item["visible"] || $item["deleted"] || $item["moderated"] || ($item["private"] == self::PRIVATE)) { + if (!$item["visible"] || $item["deleted"] || ($item["private"] == self::PRIVATE)) { return; } @@ -1445,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 { @@ -1508,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); @@ -1532,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 ''; } @@ -1662,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); @@ -1715,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); @@ -1797,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; } @@ -1850,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; } @@ -2154,7 +2145,7 @@ class Item $condition[0] .= " AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY"; $condition[] = $days; - $items = Post::select(['resource-id', 'starred', 'type', 'id', 'post-type', 'uid', 'uri-id'], $condition); + $items = Post::select(['resource-id', 'starred', 'id', 'post-type', 'uid', 'uri-id'], $condition); if (!DBA::isResult($items)) { return; @@ -2183,13 +2174,13 @@ 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; - } elseif (!$expire_notes && (($item['type'] == 'note') || ($item['post-type'] == self::PT_PERSONAL_NOTE))) { + } elseif (!$expire_notes && ($item['post-type'] == self::PT_PERSONAL_NOTE)) { continue; - } elseif (!$expire_items && ($item['type'] != 'note') && ($item['post-type'] != self::PT_PERSONAL_NOTE)) { + } elseif (!$expire_items && ($item['post-type'] != self::PT_PERSONAL_NOTE)) { continue; } @@ -2203,11 +2194,18 @@ class Item public static function firstPostDate($uid, $wall = false) { - $condition = ['uid' => $uid, 'wall' => $wall, 'deleted' => false, 'visible' => true, 'moderated' => false]; + $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 = DBA::selectFirst('thread', ['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; } @@ -2418,75 +2416,6 @@ class Item return true; } - private static function addThread($itemid, $onlyshadow = false) - { - $fields = ['uid', 'created', 'edited', 'commented', 'received', 'changed', 'wall', 'private', 'pubmail', - 'moderated', 'visible', 'starred', 'contact-id', 'post-type', 'uri-id', - 'deleted', 'origin', 'forum_mode', 'mention', 'network', 'author-id', 'owner-id']; - $condition = ["`id` = ? AND (`parent` = ? OR `parent` = 0)", $itemid, $itemid]; - $item = Post::selectFirst($fields, $condition); - - if (!DBA::isResult($item)) { - return; - } - - $item['iid'] = $itemid; - - if (!$onlyshadow) { - $result = DBA::replace('thread', $item); - - Logger::info('Add thread', ['item' => $itemid, 'result' => $result]); - } - } - - private static function updateThread($itemid, $setmention = false) - { - $fields = ['uid', 'guid', 'created', 'edited', 'commented', 'received', 'changed', 'post-type', - 'wall', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'contact-id', 'uri-id', - 'deleted', 'origin', 'forum_mode', 'network', 'author-id', 'owner-id']; - - $item = Post::selectFirst($fields, ['id' => $itemid, 'gravity' => GRAVITY_PARENT]); - if (!DBA::isResult($item)) { - return; - } - - if ($setmention) { - $item["mention"] = 1; - } - - $fields = []; - - foreach ($item as $field => $data) { - if (!in_array($field, ["guid"])) { - $fields[$field] = $data; - } - } - - $result = DBA::update('thread', $fields, ['iid' => $itemid]); - - Logger::info('Update thread', ['item' => $itemid, 'guid' => $item["guid"], 'result' => $result]); - } - - private static function deleteThread($itemid, $uri_id) - { - $item = DBA::selectFirst('thread', ['uid'], ['iid' => $itemid]); - if (!DBA::isResult($item)) { - Logger::info('No thread found', ['id' => $itemid]); - return; - } - - $result = DBA::delete('thread', ['iid' => $itemid], ['cascade' => false]); - - Logger::info('Deleted thread', ['item' => $itemid, 'result' => $result]); - - $condition = ["`uri-id` = ? AND NOT `deleted` AND NOT (`uid` IN (?, 0))", $uri_id, $item["uid"]]; - if (!Post::exists($condition)) { - DBA::delete('item', ['uri-id' => $uri_id, 'uid' => 0]); - Post\User::delete(['uri-id' => $uri_id, 'uid' => 0]); - Logger::debug('Deleted shadow item', ['id' => $itemid, 'uri-id' => $uri_id]); - } - } - /** * Fetch the SQL condition for the given user id * @@ -2520,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 = '') { @@ -2569,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'); } /** @@ -2593,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']; @@ -2622,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'], @@ -2720,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 = [ @@ -2748,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'])) { @@ -2813,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 * @@ -2901,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