use Friendica\App;
use Friendica\Content\Item as ItemHelper;
+use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
-use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Model\Conversation;
use Friendica\Model\FileTag;
use Friendica\Model\Item;
+use Friendica\Model\Notify;
use Friendica\Model\Notify\Type;
use Friendica\Model\Photo;
+use Friendica\Model\Post;
use Friendica\Model\Tag;
+use Friendica\Model\User;
use Friendica\Network\HTTPException;
use Friendica\Object\EMail\ItemCCEMail;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora;
use Friendica\Util\DateTimeFormat;
-use Friendica\Util\Security;
-use Friendica\Util\Strings;
+use Friendica\Security\Security;
use Friendica\Worker\Delivery;
-require_once __DIR__ . '/../include/items.php';
-
function item_post(App $a) {
if (!Session::isAuthenticated()) {
throw new HTTPException\ForbiddenException();
$api_source = $_REQUEST['api_source'] ?? false;
- $message_id = ((!empty($_REQUEST['message_id']) && $api_source) ? strip_tags($_REQUEST['message_id']) : '');
-
$return_path = $_REQUEST['return'] ?? '';
$preview = intval($_REQUEST['preview'] ?? 0);
}
// Is this a reply to something?
- $toplevel_item_id = intval($_REQUEST['parent'] ?? 0);
+ $parent_item_id = intval($_REQUEST['parent'] ?? 0);
$thr_parent_uri = trim($_REQUEST['parent_uri'] ?? '');
+ $parent_item = null;
$toplevel_item = null;
- $parent_user = null;
+ $toplevel_item_id = 0;
+ $toplevel_user_id = null;
$objecttype = null;
$profile_uid = ($_REQUEST['profile_uid'] ?? 0) ?: local_user();
$posttype = ($_REQUEST['post_type'] ?? '') ?: Item::PT_ARTICLE;
- if ($toplevel_item_id || $thr_parent_uri) {
- if ($toplevel_item_id) {
- $toplevel_item = Item::selectFirst([], ['id' => $toplevel_item_id]);
+ if ($parent_item_id || $thr_parent_uri) {
+ if ($parent_item_id) {
+ $parent_item = Item::selectFirst([], ['id' => $parent_item_id]);
} elseif ($thr_parent_uri) {
- $toplevel_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]);
+ $parent_item = Item::selectFirst([], ['uri' => $thr_parent_uri, 'uid' => $profile_uid]);
}
// if this isn't the top-level parent of the conversation, find it
- if (DBA::isResult($toplevel_item)) {
+ if (DBA::isResult($parent_item)) {
// The URI and the contact is taken from the direct parent which needn't to be the top parent
- $thr_parent_uri = $toplevel_item['uri'];
+ $thr_parent_uri = $parent_item['uri'];
+ $toplevel_item = $parent_item;
- if ($toplevel_item['gravity'] != GRAVITY_PARENT) {
+ if ($parent_item['gravity'] != GRAVITY_PARENT) {
$toplevel_item = Item::selectFirst([], ['id' => $toplevel_item['parent']]);
}
}
throw new HTTPException\NotFoundException(DI::l10n()->t('Unable to locate original post.'));
}
+ // When commenting on a public post then store the post for the current user
+ // This enables interaction like starring and saving into folders
+ if ($toplevel_item['uid'] == 0) {
+ $stored = Item::storeForUserByUriId($toplevel_item['uri-id'], local_user());
+ Logger::info('Public item stored for user', ['uri-id' => $toplevel_item['uri-id'], 'uid' => $uid, 'stored' => $stored]);
+ if ($stored) {
+ $toplevel_item = Item::selectFirst([], ['id' => $stored]);
+ }
+ }
+
$toplevel_item_id = $toplevel_item['id'];
- $parent_user = $toplevel_item['uid'];
+ $toplevel_user_id = $toplevel_item['uid'];
$objecttype = Activity\ObjectType::COMMENT;
}
}
// Ensure that the user id in a thread always stay the same
- if (!is_null($parent_user) && in_array($parent_user, [local_user(), 0])) {
- $profile_uid = $parent_user;
- }
-
- // Check for multiple posts with the same message id (when the post was created via API)
- if (($message_id != '') && ($profile_uid != 0)) {
- if (Item::exists(['uri' => $message_id, 'uid' => $profile_uid])) {
- Logger::info('Message already exists for user', ['uri' => $message_id, 'uid' => $profile_uid]);
- return 0;
- }
+ if (!is_null($toplevel_user_id) && in_array($toplevel_user_id, [local_user(), 0])) {
+ $profile_uid = $toplevel_user_id;
}
// Allow commenting if it is an answer to a public post
$orig_post = Item::selectFirst(Item::ITEM_FIELDLIST, ['id' => $post_id]);
}
- $user = DBA::selectFirst('user', [], ['uid' => $profile_uid]);
-
+ $user = User::getById($profile_uid, ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);
if (!DBA::isResult($user) && !$toplevel_item_id) {
return 0;
}
];
}
- $att_bbcode = add_page_info_data($attachment);
+ $att_bbcode = "\n" . PageInfo::getFooterFromData($attachment);
$body .= $att_bbcode;
}
$objecttype = $orig_post['object-type'];
$app = $orig_post['app'];
$categories = $orig_post['file'] ?? '';
- $title = Strings::escapeTags(trim($_REQUEST['title']));
+ $title = trim($_REQUEST['title'] ?? '');
$body = trim($body);
$private = $orig_post['private'];
$pubmail_enabled = $orig_post['pubmail'];
$guid = $orig_post['guid'];
$extid = $orig_post['extid'];
} else {
- $str_contact_allow = '';
- $str_group_allow = '';
- $str_contact_deny = '';
- $str_group_deny = '';
-
- if (($_REQUEST['visibility'] ?? '') !== 'public') {
- $aclFormatter = DI::aclFormatter();
- $str_contact_allow = isset($_REQUEST['contact_allow']) ? $aclFormatter->toString($_REQUEST['contact_allow']) : $user['allow_cid'] ?? '';
- $str_group_allow = isset($_REQUEST['group_allow']) ? $aclFormatter->toString($_REQUEST['group_allow']) : $user['allow_gid'] ?? '';
- $str_contact_deny = isset($_REQUEST['contact_deny']) ? $aclFormatter->toString($_REQUEST['contact_deny']) : $user['deny_cid'] ?? '';
- $str_group_deny = isset($_REQUEST['group_deny']) ? $aclFormatter->toString($_REQUEST['group_deny']) : $user['deny_gid'] ?? '';
- }
-
- $title = Strings::escapeTags(trim($_REQUEST['title'] ?? ''));
- $location = Strings::escapeTags(trim($_REQUEST['location'] ?? ''));
- $coord = Strings::escapeTags(trim($_REQUEST['coord'] ?? ''));
- $verb = Strings::escapeTags(trim($_REQUEST['verb'] ?? ''));
- $emailcc = Strings::escapeTags(trim($_REQUEST['emailcc'] ?? ''));
+ $aclFormatter = DI::aclFormatter();
+ $str_contact_allow = isset($_REQUEST['contact_allow']) ? $aclFormatter->toString($_REQUEST['contact_allow']) : $user['allow_cid'] ?? '';
+ $str_group_allow = isset($_REQUEST['group_allow']) ? $aclFormatter->toString($_REQUEST['group_allow']) : $user['allow_gid'] ?? '';
+ $str_contact_deny = isset($_REQUEST['contact_deny']) ? $aclFormatter->toString($_REQUEST['contact_deny']) : $user['deny_cid'] ?? '';
+ $str_group_deny = isset($_REQUEST['group_deny']) ? $aclFormatter->toString($_REQUEST['group_deny']) : $user['deny_gid'] ?? '';
+
+ $visibility = $_REQUEST['visibility'] ?? '';
+ if ($visibility === 'public') {
+ // The ACL selector introduced in version 2019.12 sends ACL input data even when the Public visibility is selected
+ $str_contact_allow = $str_group_allow = $str_contact_deny = $str_group_deny = '';
+ } else if ($visibility === 'custom') {
+ // Since we know from the visibility parameter the item should be private, we have to prevent the empty ACL
+ // case that would make it public. So we always append the author's contact id to the allowed contacts.
+ // See https://github.com/friendica/friendica/issues/9672
+ $str_contact_allow .= $aclFormatter->toString(Contact::getPublicIdByUserId($uid));
+ }
+
+ $title = trim($_REQUEST['title'] ?? '');
+ $location = trim($_REQUEST['location'] ?? '');
+ $coord = trim($_REQUEST['coord'] ?? '');
+ $verb = trim($_REQUEST['verb'] ?? '');
+ $emailcc = trim($_REQUEST['emailcc'] ?? '');
$body = trim($body);
- $network = Strings::escapeTags(trim(($_REQUEST['network'] ?? '') ?: Protocol::DFRN));
+ $network = trim(($_REQUEST['network'] ?? '') ?: Protocol::DFRN);
$guid = System::createUUID();
$postopts = $_REQUEST['postopts'] ?? '';
System::jsonExit(['preview' => '']);
}
- info(DI::l10n()->t('Empty post discarded.'));
+ notice(DI::l10n()->t('Empty post discarded.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
// get contact info for owner
if ($profile_uid == local_user() || $allow_comment) {
- $contact_record = $author;
+ $contact_record = $author ?: [];
} else {
- $contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]);
+ $contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]) ?: [];
}
// Look for any tags and linkify them
if (strlen($attachments)) {
$attachments .= ',';
}
- $attachments .= '[attach]href="' . DI::baseUrl() . '/attach/' . $attachment['id'] .
- '" length="' . $attachment['filesize'] . '" type="' . $attachment['filetype'] .
- '" title="' . ($attachment['filename'] ? $attachment['filename'] : '') . '"[/attach]';
+ $attachments .= Post\Media::getAttachElement(DI::baseUrl() . '/attach/' . $attachment['id'],
+ $attachment['filesize'], $attachment['filetype'], $attachment['filename'] ?? '');
}
$body = str_replace($match[1],'',$body);
}
$origin = $_REQUEST['origin'];
}
- $uri = ($message_id ? $message_id : Item::newURI($api_source ? $profile_uid : $uid, $guid));
+ $uri = Item::newURI($api_source ? $profile_uid : $uid, $guid);
// Fallback so that we alway have a parent uri
if (!$thr_parent_uri || !$toplevel_item_id) {
$datarray['gravity'] = $gravity;
$datarray['network'] = $network;
$datarray['contact-id'] = $contact_id;
- $datarray['owner-name'] = $contact_record['name'];
- $datarray['owner-link'] = $contact_record['url'];
- $datarray['owner-avatar'] = $contact_record['thumb'];
+ $datarray['owner-name'] = $contact_record['name'] ?? '';
+ $datarray['owner-link'] = $contact_record['url'] ?? '';
+ $datarray['owner-avatar'] = $contact_record['thumb'] ?? '';
$datarray['owner-id'] = Contact::getIdForURL($datarray['owner-link']);
$datarray['author-name'] = $author['name'];
$datarray['author-link'] = $author['url'];
$datarray['pubmail'] = $pubmail_enabled;
$datarray['attach'] = $attachments;
- // This is not a bug. The item store function changes 'parent-uri' to 'thr-parent' and fetches 'parent-uri' new. (We should change this)
- $datarray['parent-uri'] = $thr_parent_uri;
+ $datarray['thr-parent'] = $thr_parent_uri;
$datarray['postopts'] = $postopts;
$datarray['origin'] = $origin;
$datarray['api_source'] = $api_source;
// This field is for storing the raw conversation data
- $datarray['protocol'] = Conversation::PARCEL_DFRN;
+ $datarray['protocol'] = Conversation::PARCEL_DIRECT;
+ $datarray['direction'] = Conversation::PUSH;
- $conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $datarray['parent-uri']]);
+ $conversation = DBA::selectFirst('conversation', ['conversation-uri', 'conversation-href'], ['item-uri' => $datarray['thr-parent']]);
if (DBA::isResult($conversation)) {
if ($conversation['conversation-uri'] != '') {
$datarray['conversation-uri'] = $conversation['conversation-uri'];
// update filetags in pconfig
FileTag::updatePconfig($uid, $categories_old, $categories_new, 'category');
- info(DI::l10n()->t('Post updated.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
$post_id = Item::insert($datarray);
if (!$post_id) {
- info(DI::l10n()->t('Item wasn\'t stored.'));
+ notice(DI::l10n()->t('Item wasn\'t stored.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
if ($contact_record != $author) {
if ($toplevel_item_id) {
notification([
- 'type' => Type::COMMENT,
- 'notify_flags' => $user['notify-flags'],
- 'language' => $user['language'],
- 'to_name' => $user['username'],
- 'to_email' => $user['email'],
- 'uid' => $user['uid'],
- 'item' => $datarray,
- 'link' => DI::baseUrl().'/display/'.urlencode($datarray['guid']),
- 'source_name' => $datarray['author-name'],
- 'source_link' => $datarray['author-link'],
- 'source_photo' => $datarray['author-avatar'],
- 'verb' => Activity::POST,
- 'otype' => 'item',
- 'parent' => $toplevel_item_id,
- 'parent_uri' => $toplevel_item['uri']
+ 'type' => Type::COMMENT,
+ 'otype' => Notify\ObjectType::ITEM,
+ 'verb' => Activity::POST,
+ 'uid' => $profile_uid,
+ 'cid' => $datarray['author-id'],
+ 'item' => $datarray,
+ 'link' => DI::baseUrl() . '/display/' . urlencode($datarray['guid']),
]);
} elseif (empty($forum_contact)) {
notification([
- 'type' => Type::WALL,
- 'notify_flags' => $user['notify-flags'],
- 'language' => $user['language'],
- 'to_name' => $user['username'],
- 'to_email' => $user['email'],
- 'uid' => $user['uid'],
- 'item' => $datarray,
- 'link' => DI::baseUrl().'/display/'.urlencode($datarray['guid']),
- 'source_name' => $datarray['author-name'],
- 'source_link' => $datarray['author-link'],
- 'source_photo' => $datarray['author-avatar'],
- 'verb' => Activity::POST,
- 'otype' => 'item'
+ 'type' => Type::WALL,
+ 'otype' => Notify\ObjectType::ITEM,
+ 'verb' => Activity::POST,
+ 'uid' => $profile_uid,
+ 'cid' => $datarray['author-id'],
+ 'item' => $datarray,
+ 'link' => DI::baseUrl() . '/display/' . urlencode($datarray['guid']),
]);
}
}
}
}
- // Insert an item entry for UID=0 for global entries.
- // We now do it in the background to save some time.
- // This is important in interactive environments like the frontend or the API.
- // We don't fork a new process since this is done anyway with the following command
- Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], "CreateShadowEntry", $post_id);
-
// When we are doing some forum posting via ! we have to start the notifier manually.
// These kind of posts don't initiate the notifier call in the item class.
if ($only_to_forum) {
return $post_id;
}
- info(DI::l10n()->t('Post published.'));
item_post_return(DI::baseUrl(), $api_source, $return_path);
// NOTREACHED
}
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $id]);
if (!DBA::isResult($item)) {
- notice(DI::l10n()->t('Item not found.') . EOL);
+ notice(DI::l10n()->t('Item not found.'));
DI::baseUrl()->redirect('network');
}
}
if ((local_user() == $item['uid']) || $contact_id) {
- // Check if we should do HTML-based delete confirmation
- if (!empty($_REQUEST['confirm'])) {
- // <form> can't take arguments in its "action" parameter
- // so add any arguments as hidden inputs
- $query = explode_querystring(DI::args()->getQueryString());
- $inputs = [];
-
- foreach ($query['args'] as $arg) {
- if (strpos($arg, 'confirm=') === false) {
- $arg_parts = explode('=', $arg);
- $inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
- }
- }
-
- return Renderer::replaceMacros(Renderer::getMarkupTemplate('confirm.tpl'), [
- '$method' => 'get',
- '$message' => DI::l10n()->t('Do you really want to delete this item?'),
- '$extra_inputs' => $inputs,
- '$confirm' => DI::l10n()->t('Yes'),
- '$confirm_url' => $query['base'],
- '$confirm_name' => 'confirmed',
- '$cancel' => DI::l10n()->t('Cancel'),
- ]);
- }
- // Now check how the user responded to the confirmation query
- if (!empty($_REQUEST['canceled'])) {
- DI::baseUrl()->redirect('display/' . $item['guid']);
- }
-
- $is_comment = $item['gravity'] == GRAVITY_COMMENT;
- $parentitem = null;
if (!empty($item['parent'])) {
- $fields = ['guid'];
- $parentitem = Item::selectFirstForUser(local_user(), $fields, ['id' => $item['parent']]);
+ $parentitem = Item::selectFirstForUser(local_user(), ['guid'], ['id' => $item['parent']]);
}
// delete the item
$return_url = str_replace("update_", "", $return_url);
// Check if delete a comment
- if ($is_comment) {
+ if ($item['gravity'] == GRAVITY_COMMENT) {
// Return to parent guid
if (!empty($parentitem)) {
DI::baseUrl()->redirect('display/' . $parentitem['guid']);