use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
+use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Diaspora;
use Friendica\Protocol\OStatus;
use Friendica\Util\DateTimeFormat;
'event-id', 'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-adjust', 'event-ignore', 'event-id',
- 'delivery_queue_count', 'delivery_queue_done'
+ 'delivery_queue_count', 'delivery_queue_done', 'delivery_queue_failed'
];
// Field list that is used to deliver items via the protocols
// We can always comment on posts from these networks
if (array_key_exists('writable', $row) &&
- in_array($row['internal-network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
+ in_array($row['internal-network'], Protocol::FEDERATED)) {
$row['writable'] = true;
}
}
}
+ /**
+ * @brief Select rows from the item table and returns them as an array
+ *
+ * @param array $selected Array of selected fields, empty for all
+ * @param array $condition Array of fields for condition
+ * @param array $params Array of several parameters
+ *
+ * @return array
+ * @throws \Exception
+ */
+ public static function selectToArray(array $fields = [], array $condition = [], $params = [])
+ {
+ $result = self::select($fields, $condition, $params);
+
+ if (is_bool($result)) {
+ return [];
+ }
+
+ $data = [];
+ while ($row = self::fetch($result)) {
+ $data[] = $row;
+ }
+ DBA::close($result);
+
+ return $data;
+ }
+
/**
* @brief Select rows from the item table
*
// When there is no content for the "old" item table, this will count the fetched items
$rows = DBA::affectedRows();
+ $notify_items = [];
+
while ($item = DBA::fetch($items)) {
if (!empty($item['iaid']) || (!empty($content_fields['verb']) && (self::activityToIndex($content_fields['verb']) >= 0))) {
self::updateActivity($content_fields, ['uri-id' => $item['uri-id']]);
// We only need to notfiy others when it is an original entry from us.
// Only call the notifier when the item has some content relevant change.
if ($item['origin'] && in_array('edited', array_keys($fields))) {
- Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $item['id']);
+ $notify_items[] = $item['id'];
}
}
DBA::close($items);
DBA::commit();
+
+ foreach ($notify_items as $notify_item) {
+ Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $notify_item);
+ }
+
return $rows;
}
}
// When the permission set will be used in photo and events as well,
// this query here needs to be extended.
- // Currently deactivated. We need the permission set in the deletion process.
+ // @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]);
private static function contactId($item)
{
- $contact_id = (int)$item["contact-id"];
-
- if (!empty($contact_id)) {
- return $contact_id;
- }
- Logger::log('Missing contact-id. Called by: '.System::callstack(), Logger::DEBUG);
- /*
- * First we are looking for a suitable contact that matches with the author of the post
- * This is done only for comments
- */
- if ($item['parent-uri'] != $item['uri']) {
+ if (!empty($item['contact-id']) && DBA::exists('contact', ['self' => true, 'id' => $item['contact-id']])) {
+ return $item['contact-id'];
+ } elseif (($item['gravity'] == GRAVITY_PARENT) && !empty($item['uid']) && !empty($item['contact-id']) && Contact::isSharing($item['contact-id'], $item['uid'])) {
+ return $item['contact-id'];
+ } elseif (!empty($item['uid']) && !Contact::isSharing($item['author-id'], $item['uid'])) {
+ return $item['author-id'];
+ } elseif (!empty($item['contact-id'])) {
+ return $item['contact-id'];
+ } else {
$contact_id = Contact::getIdForURL($item['author-link'], $item['uid']);
- }
-
- // If not present then maybe the owner was found
- if ($contact_id == 0) {
- $contact_id = Contact::getIdForURL($item['owner-link'], $item['uid']);
- }
-
- // Still missing? Then use the "self" contact of the current user
- if ($contact_id == 0) {
- $self = DBA::selectFirst('contact', ['id'], ['self' => true, 'uid' => $item['uid']]);
- if (DBA::isResult($self)) {
- $contact_id = $self["id"];
+ if (!empty($contact_id)) {
+ return $contact_id;
}
}
- Logger::log("Contact-id was missing for post ".$item['guid']." from user id ".$item['uid']." - now set to ".$contact_id, Logger::DEBUG);
-
- return $contact_id;
+ return $item['author-id'];
}
// This function will finally cover most of the preparation functionality in mod/item.php
*/
}
+ /**
+ * Write an item array into a spool file to be inserted later.
+ * This command is called whenever there are issues storing an item.
+ *
+ * @param array $item The item fields that are to be inserted
+ * @throws \Exception
+ */
+ private static function spool($orig_item)
+ {
+ // Now we store the data in the spool directory
+ // We use "microtime" to keep the arrival order and "mt_rand" to avoid duplicates
+ $file = 'item-' . round(microtime(true) * 10000) . '-' . mt_rand() . '.msg';
+
+ $spoolpath = get_spoolpath();
+ if ($spoolpath != "") {
+ $spool = $spoolpath . '/' . $file;
+
+ file_put_contents($spool, json_encode($orig_item));
+ Logger::warning("Item wasn't stored - Item was spooled into file", ['file' => $file]);
+ }
+ }
+
public static function insert($item, $force_parent = false, $notify = false, $dontcache = false)
{
$orig_item = $item;
* We have to check several networks since Friendica posts could be repeated
* via OStatus (maybe Diasporsa as well)
*/
- if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS, ""])) {
+ if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) {
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)",
trim($item['uri']), $item['uid'],
Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
$item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid']));
- // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
- $item["contact-id"] = self::contactId($item);
-
$default = ['url' => $item['author-link'], 'name' => $item['author-name'],
'photo' => $item['author-avatar'], 'network' => $item['network']];
unset($item['causer-id']);
unset($item['causer-link']);
+ // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
+ $item["contact-id"] = self::contactId($item);
+
if ($item['network'] == Protocol::PHANTOM) {
$item['network'] = Protocol::DFRN;
Logger::notice('Missing network, setting to {network}.', [
Logger::log('duplicated item with the same guid found. '.print_r($item,true));
return 0;
}
- } else {
+ } elseif ($item['network'] == Protocol::OSTATUS) {
// Check for an existing post with the same content. There seems to be a problem with OStatus.
$condition = ["`body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ?",
$item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid']];
unset($item['owner-name']);
unset($item['owner-avatar']);
+ $like_no_comment = Config::get('system', 'like_no_comment');
+
DBA::transaction();
$ret = DBA::insert('item', $item);
DBA::rollback();
// Store the data into a spool file so that we can try again later.
-
- // At first we restore the Diaspora signature that we removed above.
- if (isset($encoded_signature)) {
- $item['dsprsig'] = $encoded_signature;
- }
-
- // Now we store the data in the spool directory
- // We use "microtime" to keep the arrival order and "mt_rand" to avoid duplicates
- $file = 'item-'.round(microtime(true) * 10000).'-'.mt_rand().'.msg';
-
- $spoolpath = get_spoolpath();
- if ($spoolpath != "") {
- $spool = $spoolpath.'/'.$file;
-
- file_put_contents($spool, json_encode($orig_item));
- Logger::log("Item wasn't stored - Item was spooled into file ".$file, Logger::DEBUG);
- }
+ self::spool($orig_item);
return 0;
}
}
Logger::log('created item '.$current_post);
- self::updateContact($item);
if (!$parent_id || ($item['parent-uri'] === $item['uri'])) {
$parent_id = $current_post;
// update the commented timestamp on the parent
// Only update "commented" if it is really a comment
- if (($item['gravity'] != GRAVITY_ACTIVITY) || !Config::get("system", "like_no_comment")) {
+ if (($item['gravity'] != GRAVITY_ACTIVITY) || !$like_no_comment) {
DBA::update('item', ['commented' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]);
} else {
DBA::update('item', ['changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]);
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $diaspora_signed_text], true);
}
- self::tagDeliver($item['uid'], $current_post);
-
- /*
- * current post can be deleted if is for a community page and no mention are
- * in it.
- */
- if (!$dontcache) {
- $posted_item = self::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);
- }
- } else {
- Logger::log('new item not found in DB, id ' . $current_post);
- }
- }
-
if ($item['parent-uri'] === $item['uri']) {
self::addThread($current_post);
} else {
self::updateThread($parent_id);
}
- ItemDeliveryData::insert($current_post, $delivery_data);
+ if (!empty($item['origin']) || !empty($item['wall']) || !empty($delivery_data['postopts']) || !empty($delivery_data['inform'])) {
+ ItemDeliveryData::insert($current_post, $delivery_data);
+ }
DBA::commit();
Term::insertFromFileFieldByItemId($current_post, $files);
}
+ // 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)) {
+ // 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']]);
+ return 0;
+ }
+
+ if (!$dontcache) {
+ $posted_item = self::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);
+ }
+ } else {
+ Logger::log('new item not found in DB, id ' . $current_post);
+ }
+ }
+
if ($item['parent-uri'] === $item['uri']) {
self::addShadow($current_post);
} else {
self::addShadowPost($current_post);
}
+ self::updateContact($item);
+
check_user_notification($current_post);
if ($notify || ($item['visible'] && ((!empty($parent) && $parent['origin']) || $item['origin']))) {
// Only distribute public items from native networks
$condition = ['id' => $itemid, 'uid' => 0,
- 'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""],
+ 'network' => array_merge(Protocol::FEDERATED ,['']),
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
$item = self::selectFirst(self::ITEM_FIELDLIST, $condition);
if (!DBA::isResult($item)) {
}
// is it an entry from a connector? Only add an entry for natively connected networks
- if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ""])) {
+ if (!in_array($item["network"], array_merge(Protocol::FEDERATED ,['']))) {
return;
}
*
* @param int $uid
* @param int $item_id
- * @return void true if item was deleted, else false
+ * @return boolean true if item was deleted, else false
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
$user = DBA::selectFirst('user', [], ['uid' => $uid]);
if (!DBA::isResult($user)) {
- return;
+ return false;
}
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
$item = self::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id]);
if (!DBA::isResult($item)) {
- return;
+ return false;
}
$link = Strings::normaliseLink(System::baseUrl() . '/profile/' . $user['nickname']);
DBA::delete('item', ['id' => $item_id]);
return true;
}
- return;
+ return false;
}
$arr = ['item' => $item, 'user' => $user];
Hook::callAll('tagged', $arr);
if (!$community_page && !$prvgroup) {
- return;
+ return false;
}
/*
* if the message originated elsewhere and is a top-level post
*/
if ($item['wall'] || $item['origin'] || ($item['id'] != $item['parent'])) {
- return;
+ return false;
}
// now change this copy of the post to a forum head message and deliver to all the tgroup members
$self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'], ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($self)) {
- return;
+ return false;
}
$owner_id = Contact::getIdForURL($self['url']);
self::updateThread($item_id);
Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, $item_id);
+
+ return false;
}
public static function isRemoteSelf($contact, &$datarray)
$replace = true;
}
} elseif ($item) {
- if (self::samePermissions($item, $photo)) {
+ if (self::samePermissions($uid, $item, $photo)) {
$replace = true;
}
}
!empty($obj['deny_cid']) || !empty($obj['deny_gid']);
}
- private static function samePermissions($obj1, $obj2)
+ private static function samePermissions($uid, $obj1, $obj2)
{
// first part is easy. Check that these are exactly the same.
if (($obj1['allow_cid'] == $obj2['allow_cid'])
return ($recipients1 == $recipients2);
}
- // returns an array of contact-ids that are allowed to see this object
- public static function enumeratePermissions($obj)
+ /**
+ * Returns an array of contact-ids that are allowed to see this object
+ *
+ * @param array $obj Item array with at least uid, allow_cid, allow_gid, deny_cid and deny_gid
+ * @param bool $check_dead Prunes unavailable contacts from the result
+ * @return array
+ * @throws \Exception
+ */
+ public static function enumeratePermissions(array $obj, bool $check_dead = false)
{
$allow_people = expand_acl($obj['allow_cid']);
- $allow_groups = Group::expand(expand_acl($obj['allow_gid']));
+ $allow_groups = Group::expand($obj['uid'], expand_acl($obj['allow_gid']), $check_dead);
$deny_people = expand_acl($obj['deny_cid']);
- $deny_groups = Group::expand(expand_acl($obj['deny_gid']));
+ $deny_groups = Group::expand($obj['uid'], expand_acl($obj['deny_gid']), $check_dead);
$recipients = array_unique(array_merge($allow_people, $allow_groups));
$deny = array_unique(array_merge($deny_people, $deny_groups));
$recipients = array_diff($recipients, $deny);
if ($network != "") {
$condition[0] .= " AND `network` = ?";
$condition[] = $network;
-
- /*
- * There is an index "uid_network_received" but not "uid_network_created"
- * This avoids the creation of another index just for one purpose.
- * And it doesn't really matter wether to look at "received" or "created"
- */
- $condition[0] .= " AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY";
- $condition[] = $days;
- } else {
- $condition[0] .= " AND `created` < UTC_TIMESTAMP() - INTERVAL ? DAY";
- $condition[] = $days;
}
+ $condition[0] .= " AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY";
+ $condition[] = $days;
+
$items = self::select(['file', 'resource-id', 'starred', 'type', 'id', 'post-type'], $condition);
if (!DBA::isResult($items)) {
public static function firstPostDate($uid, $wall = false)
{
$condition = ['uid' => $uid, 'wall' => $wall, 'deleted' => false, 'visible' => true, 'moderated' => false];
- $params = ['order' => ['created' => false]];
- $thread = DBA::selectFirst('thread', ['created'], $condition, $params);
+ $params = ['order' => ['received' => false]];
+ $thread = DBA::selectFirst('thread', ['received'], $condition, $params);
if (DBA::isResult($thread)) {
- return substr(DateTimeFormat::local($thread['created']), 0, 10);
+ return substr(DateTimeFormat::local($thread['received']), 0, 10);
}
return false;
}
return Contact::isForum($item['contact-id']);
}
+
+ /**
+ * Search item id for given URI or plink
+ *
+ * @param string $uri
+ * @param integer $uid
+ *
+ * @return integer item id
+ */
+ public static function searchByLink($uri, $uid = 0)
+ {
+ $ssl_uri = str_replace('http://', 'https://', $uri);
+ $uris = [$uri, $ssl_uri, Strings::normaliseLink($uri)];
+
+ $item = DBA::selectFirst('item', ['id'], ['uri' => $uris, 'uid' => $uid]);
+ if (DBA::isResult($item)) {
+ return $item['id'];
+ }
+
+ $itemcontent = DBA::selectFirst('item-content', ['uri-id'], ['plink' => $uris]);
+ if (!DBA::isResult($itemcontent)) {
+ return 0;
+ }
+
+ $itemuri = DBA::selectFirst('item-uri', ['uri'], ['id' => $itemcontent['uri-id']]);
+ if (!DBA::isResult($itemuri)) {
+ return 0;
+ }
+
+ $item = DBA::selectFirst('item', ['id'], ['uri' => $itemuri['uri'], 'uid' => $uid]);
+ if (DBA::isResult($item)) {
+ return $item['id'];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Fetches item for given URI or plink
+ *
+ * @param string $uri
+ * @param integer $uid
+ *
+ * @return integer item id
+ */
+ public static function fetchByLink($uri, $uid = 0)
+ {
+ $item_id = self::searchByLink($uri, $uid);
+ if (!empty($item_id)) {
+ return $item_id;
+ }
+
+ if (ActivityPub\Processor::fetchMissingActivity($uri)) {
+ $item_id = self::searchByLink($uri, $uid);
+ } else {
+ $item_id = Diaspora::fetchByURL($uri);
+ }
+
+ if (!empty($item_id)) {
+ return $item_id;
+ }
+
+ return 0;
+ }
}