/** * @brief * * @param array $sender * @param array $arr * @param array $deliveries * @param boolean $relay * @param boolean $public (optional) default false * @param boolean $request (optional) default false * @return array */ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $request = false) { $result = array(); $result['site'] = z_root(); // We've validated the sender. Now make sure that the sender is the owner or author if (!$public) { if ($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) { logger("process_delivery: sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); return; } } foreach ($deliveries as $d) { $local_public = $public; $DR = new DReport(z_root(), $sender['hash'], $d['hash'], $arr['mid']); $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash'])); if (!$r) { $DR->update('recipient not found'); $result[] = $DR->get(); continue; } $channel = $r[0]; $DR->addto_recipient($channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>'); /** * @FIXME: Somehow we need to block normal message delivery from our clones, as the delivered * message doesn't have ACL information in it as the cloned copy does. That copy * will normally arrive first via sync delivery, but this isn't guaranteed. * There's a chance the current delivery could take place before the cloned copy arrives * hence the item could have the wrong ACL and *could* be used in subsequent deliveries or * access checks. So far all attempts at identifying this situation precisely * have caused issues with delivery of relayed comments. */ // if(($d['hash'] === $sender['hash']) && ($sender['url'] !== z_root()) && (! $relay)) { // $DR->update('self delivery ignored'); // $result[] = $DR->get(); // continue; // } // allow public postings to the sys channel regardless of permissions, but not // for comments travelling upstream. Wait and catch them on the way down. // They may have been blocked by the owner. if (intval($channel['channel_system']) && !$arr['item_private'] && !$relay) { $local_public = true; $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", dbesc($sender['hash'])); // don't import sys channel posts from selfcensored authors if ($r && intval($r[0]['xchan_selfcensored'])) { $local_public = false; continue; } } $tag_delivery = tgroup_check($channel['channel_id'], $arr); $perm = 'send_stream'; if ($arr['mid'] !== $arr['parent_mid'] && $relay) { $perm = 'post_comments'; } // This is our own post, possibly coming from a channel clone if ($arr['owner_xchan'] == $d['hash']) { $arr['item_wall'] = 1; } else { $arr['item_wall'] = 0; } if (!perm_is_allowed($channel['channel_id'], $sender['hash'], $perm) && !$tag_delivery && !$local_public) { logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); $DR->update('permission denied'); $result[] = $DR->get(); continue; } if ($arr['mid'] != $arr['parent_mid']) { // check source route. // We are only going to accept comments from this sender if the comment has the same route as the top-level-post, // this is so that permissions mismatches between senders apply to the entire conversation // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise // processing it is pointless. $r = q("select route, id from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id'])); if (!$r) { $DR->update('comment parent not found'); $result[] = $DR->get(); // We don't seem to have a copy of this conversation or at least the parent // - so request a copy of the entire conversation to date. // Don't do this if it's a relay post as we're the ones who are supposed to // have the copy and we don't want the request to loop. // Also don't do this if this comment came from a conversation request packet. // It's possible that comments are allowed but posting isn't and that could // cause a conversation fetch loop. We can detect these packets since they are // delivered via a 'notify' packet type that has a message_id element in the // initial zot packet (just like the corresponding 'request' packet type which // makes the request). // We'll also check the send_stream permission - because if it isn't allowed, // the top level post is unlikely to be imported and // this is just an exercise in futility. if (!$relay && !$request && !$local_public && perm_is_allowed($channel['channel_id'], $sender['hash'], 'send_stream')) { proc_run('php', 'include/notifier.php', 'request', $channel['channel_id'], $sender['hash'], $arr['parent_mid']); } continue; } if ($relay) { // reset the route in case it travelled a great distance upstream // use our parent's route so when we go back downstream we'll match // with whatever route our parent has. $arr['route'] = $r[0]['route']; } else { // going downstream check that we have the same upstream provider that // sent it to us originally. Ignore it if it came from another source // (with potentially different permissions). // only compare the last hop since it could have arrived at the last location any number of ways. // Always accept empty routes and firehose items (route contains 'undefined') . $existing_route = explode(',', $r[0]['route']); $routes = count($existing_route); if ($routes) { $last_hop = array_pop($existing_route); $last_prior_route = implode(',', $existing_route); } else { $last_hop = ''; $last_prior_route = ''; } if (in_array('undefined', $existing_route) || $last_hop == 'undefined' || $sender['hash'] == 'undefined') { $last_hop = ''; } $current_route = ($arr['route'] ? $arr['route'] . ',' : '') . $sender['hash']; if ($last_hop && $last_hop != $sender['hash']) { logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG); logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG); $DR->update('comment route mismatch'); $result[] = $DR->get(); continue; } // we'll add sender['hash'] onto this when we deliver it. $last_prior_route now has the previously stored route // *except* for the sender['hash'] which would've been the last hop before it got to us. $arr['route'] = $last_prior_route; } } $ab = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", intval($channel['channel_id']), dbesc($arr['owner_xchan'])); $abook = $ab ? $ab[0] : null; if (intval($arr['item_deleted'])) { // remove_community_tag is a no-op if this isn't a community tag activity remove_community_tag($sender, $arr, $channel['channel_id']); // set these just in case we need to store a fresh copy of the deleted post. // This could happen if the delete got here before the original post did. $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; $item_id = delete_imported_item($sender, $arr, $channel['channel_id'], $relay); $DR->update($item_id ? 'deleted' : 'delete_failed'); $result[] = $DR->get(); if ($relay && $item_id) { logger('process_delivery: invoking relay'); proc_run('php', 'include/notifier.php', 'relay', intval($item_id)); $DR->update('relayed'); $result[] = $DR->get(); } continue; } $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($arr['mid']), intval($channel['channel_id'])); if ($r) { // We already have this post. $item_id = $r[0]['id']; if (intval($r[0]['item_deleted'])) { // It was deleted locally. $DR->update('update ignored'); $result[] = $DR->get(); continue; } elseif ($arr['edited'] > $r[0]['edited']) { $arr['id'] = $r[0]['id']; $arr['uid'] = $channel['channel_id']; if ($arr['mid'] == $arr['parent_mid'] && !post_is_importable($arr, $abook)) { $DR->update('update ignored'); $result[] = $DR->get(); } else { update_imported_item($sender, $arr, $r[0], $channel['channel_id']); $DR->update('updated'); $result[] = $DR->get(); if (!$relay) { add_source_route($item_id, $sender['hash']); } } } else { $DR->update('update ignored'); $result[] = $DR->get(); // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit), // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful. if (!intval($r[0]['item_origin'])) { continue; } } } else { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; // if it's a sourced post, call the post_local hooks as if it were // posted locally so that crosspost connectors will be triggered. if (check_item_source($arr['uid'], $arr)) { call_hooks('post_local', $arr); } $item_id = 0; if ($arr['mid'] == $arr['parent_mid'] && !post_is_importable($arr, $abook)) { $DR->update('post ignored'); $result[] = $DR->get(); } else { $item_result = item_store($arr); if ($item_result['success']) { $item_id = $item_result['item_id']; $parr = array('item_id' => $item_id, 'item' => $arr, 'sender' => $sender, 'channel' => $channel); call_hooks('activity_received', $parr); // don't add a source route if it's a relay or later recipients will get a route mismatch if (!$relay) { add_source_route($item_id, $sender['hash']); } } $DR->update($item_id ? 'posted' : 'storage failed: ' . $item_result['message']); $result[] = $DR->get(); } } if ($relay && $item_id) { logger('process_delivery: invoking relay'); proc_run('php', 'include/notifier.php', 'relay', intval($item_id)); $DR->addto_update('relayed'); $result[] = $DR->get(); } } if (!$deliveries) { $result[] = array('', 'no recipients', '', $arr['mid']); } logger('process_delivery: local results: ' . print_r($result, true), LOGGER_DEBUG); return $result; }
/** * @brief Process atom feed and update anything/everything we might need to update. * * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or * might not) try and subscribe to it. * $datedir sorts in reverse order * * @param array $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param $contact * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. * @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been * imported prior to its children being seen in the stream unless we are certain * of how the feed is arranged/ordered. * * With $pass = 1, we only pull parent items out of the stream. * * With $pass = 2, we only pull children (comments/likes). * * So running this twice, first with pass 1 and then with pass 2 will do the right * thing regardless of feed ordering. This won't be adequate in a fully-threaded * model where comments can have sub-threads. That would require some massive sorting * to get all the feed items into a mostly linear ordering, and might still require * recursion. */ function consume_feed($xml, $importer, &$contact, $pass = 0) { require_once 'library/simplepie/simplepie.inc'; if (!strlen($xml)) { logger('consume_feed: empty input'); return; } $feed = new SimplePie(); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('consume_feed: Error parsing XML: ' . $feed->error()); } $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries) && $pass != 2) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $mid = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted && is_array($contact)) { $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1", dbesc(base64url_encode($mid)), dbesc($contact['xchan_hash']), intval($importer['channel_id'])); if ($r) { $item = $r[0]; if (!intval($item['item_deleted'])) { logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . base64url_decode($item['mid']), LOGGER_DEBUG); drop_item($item['id'], false); } } } } } // Now process the feed if ($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG); $items = $feed->get_items(); foreach ($items as $item) { $is_reply = false; $item_id = base64url_encode($item->get_id()); logger('consume_feed: processing ' . $item_id, LOGGER_DEBUG); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_mid = base64url_encode($rawthread[0]['attribs']['']['ref']); } if ($is_reply) { if ($pass == 1) { continue; } // Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $parent_mid; $datarray['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if (is_array($contact)) { if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } } if (!x($author, 'author_name') || !x($author, 'author_link')) { logger('consume_feed: no author information! ' . print_r($author, true)); continue; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $item_id; $datarray['uid'] = $importer['channel_id']; if (!link_compare($author['owner_link'], $contact['xchan_url'])) { logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); $author['owner_name'] = $contact['name']; $author['owner_link'] = $contact['url']; $author['owner_avatar'] = $contact['thumb']; } if (!post_is_importable($datarray, $contact)) { continue; } logger('consume_feed: author ' . print_r($author, true), LOGGER_DEBUG); logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } } } }
function diaspora_reshare($importer, $xml, $msg) { logger('diaspora_reshare: init: ' . print_r($xml, true), LOGGER_DATA); $a = get_app(); $guid = notags(unxmlify($xml['guid'])); $diaspora_handle = notags(diaspora_get_author($xml)); if ($diaspora_handle != $msg['author']) { logger('Potential forgery. Message handle is not the same as envelope sender.'); return 202; } $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { return; } $search_guid = strlen($guid) == 64 ? $guid . '%' : $guid; $r = q("SELECT id FROM item WHERE uid = %d AND mid like '%s' LIMIT 1", intval($importer['channel_id']), dbesc($search_guid)); if ($r) { logger('diaspora_reshare: message exists: ' . $guid); return; } $orig_author = notags(diaspora_get_root_author($xml)); $orig_guid = notags(unxmlify($xml['root_guid'])); $source_url = 'https://' . substr($orig_author, strpos($orig_author, '@') + 1) . '/p/' . $orig_guid . '.xml'; $orig_url = 'https://' . substr($orig_author, strpos($orig_author, '@') + 1) . '/posts/' . $orig_guid; $source_xml = get_diaspora_reshare_xml($source_url); if ($source_xml['status_message']) { $body = diaspora2bb($source_xml['status_message']['raw_message']); $orig_author = diaspora_get_author($source_xml['status_message']); $orig_guid = notags(unxmlify($source_xml['status_message']['guid'])); // Checking for embedded pictures if ($source_xml['status_message']['photo']['remote_photo_path'] && $source_xml['status_message']['photo']['remote_photo_name']) { $remote_photo_path = notags(unxmlify($source_xml['status_message']['photo']['remote_photo_path'])); $remote_photo_name = notags(unxmlify($source_xml['status_message']['photo']['remote_photo_name'])); $body = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n" . $body; logger('diaspora_reshare: embedded picture link found: ' . $body, LOGGER_DEBUG); } $body = scale_external_images($body); // Add OEmbed and other information to the body // $body = add_page_info_to_body($body, false, true); } else { // Maybe it is a reshare of a photo that will be delivered at a later time (testing) logger('diaspora_reshare: no reshare content found: ' . print_r($source_xml, true)); $body = ""; //return; } $maxlen = get_max_import_size(); if ($maxlen && mb_strlen($body) > $maxlen) { $body = mb_substr($body, 0, $maxlen, 'UTF-8'); logger('message length exceeds max_import_size: truncated'); } $person = find_diaspora_person_by_handle($orig_author); if ($person) { $orig_author_name = $person['xchan_name']; $orig_author_link = $person['xchan_url']; $orig_author_photo = $person['xchan_photo_m']; } $created = unxmlify($xml['created_at']); $private = unxmlify($xml['public']) == 'false' ? 1 : 0; $datarray = array(); // Look for tags and linkify them $results = linkify_tags(get_app(), $body, $importer['channel_id'], true); $datarray['term'] = array(); if ($results) { foreach ($results as $result) { $success = $result['success']; if ($success['replaced']) { $datarray['term'][] = array('uid' => $importer['channel_id'], 'ttype' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url']); } } } $cnt = preg_match_all('/@\\[url=(.*?)\\](.*?)\\[\\/url\\]/ism', $body, $matches, PREG_SET_ORDER); if ($cnt) { foreach ($matches as $mtch) { $datarray['term'][] = array('uid' => $importer['channel_id'], 'ttype' => TERM_MENTION, 'otype' => TERM_OBJ_POST, 'term' => $mtch[2], 'url' => $mtch[1]); } } $cnt = preg_match_all('/@\\[zrl=(.*?)\\](.*?)\\[\\/zrl\\]/ism', $body, $matches, PREG_SET_ORDER); if ($cnt) { foreach ($matches as $mtch) { // don't include plustags in the term $term = substr($mtch[2], -1, 1) === '+' ? substr($mtch[2], 0, -1) : $mtch[2]; $datarray['term'][] = array('uid' => $importer['channel_id'], 'ttype' => TERM_MENTION, 'otype' => TERM_OBJ_POST, 'term' => $term, 'url' => $mtch[1]); } } $newbody = "[share author='" . urlencode($orig_author_name) . "' profile='" . $orig_author_link . "' avatar='" . $orig_author_photo . "' link='" . $orig_url . "' posted='" . datetime_convert('UTC', 'UTC', unxmlify($source_xml['status_message']['created_at'])) . "' message_id='" . unxmlify($source_xml['status_message']['guid']) . "']" . $body . "[/share]"; $plink = service_plink($contact, $guid); $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; $datarray['mid'] = $datarray['parent_mid'] = $guid; $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC', 'UTC', $created); $datarray['item_private'] = $private; $datarray['plink'] = $plink; $datarray['owner_xchan'] = $contact['xchan_hash']; $datarray['author_xchan'] = $contact['xchan_hash']; $datarray['body'] = $newbody; $datarray['app'] = 'Diaspora'; $tgroup = tgroup_check($importer['channel_id'], $datarray); if (!$importer['system'] && !perm_is_allowed($importer['channel_id'], $contact['xchan_hash'], 'send_stream') && !$tgroup) { logger('diaspora_reshare: Ignoring this author.'); return 202; } if (!post_is_importable($datarray, $contact)) { logger('diaspora_reshare: filtering this author.'); return 202; } $result = item_store($datarray); if ($result['success']) { sync_an_item($importer['channel_id'], $result['item_id']); } return; }
/** * @brief Process atom feed and update anything/everything we might need to update. * * @param array $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param $contact * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. * @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been * imported prior to its children being seen in the stream unless we are certain * of how the feed is arranged/ordered. * * With $pass = 1, we only pull parent items out of the stream. * * With $pass = 2, we only pull children (comments/likes). * * So running this twice, first with pass 1 and then with pass 2 will do the right * thing regardless of feed ordering. This won't be adequate in a fully-threaded * model where comments can have sub-threads. That would require some massive sorting * to get all the feed items into a mostly linear ordering, and might still require * recursion. */ function consume_feed($xml, $importer, &$contact, $pass = 0) { require_once 'library/simplepie/simplepie.inc'; if (!strlen($xml)) { logger('consume_feed: empty input'); return; } $sys_expire = intval(get_config('system', 'default_expire_days')); $chn_expire = intval($importer['channel_expire_days']); $expire_days = $sys_expire; if ($chn_expire != 0 && $chn_expire < $sys_expire) { $expire_days = $chn_expire; } // logger('expire_days: ' . $expire_days); $feed = new SimplePie(); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('consume_feed: Error parsing XML: ' . $feed->error()); } $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries) && $pass != 2) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $mid = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted && is_array($contact)) { $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1", dbesc(base64url_encode($mid)), dbesc($contact['xchan_hash']), intval($importer['channel_id'])); if ($r) { $item = $r[0]; if (!intval($item['item_deleted'])) { logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . base64url_decode($item['mid']), LOGGER_DEBUG); drop_item($item['id'], false); } } } } } // Now process the feed if ($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG); $items = $feed->get_items(); foreach ($items as $item) { $is_reply = false; $item_id = base64url_encode($item->get_id()); logger('consume_feed: processing ' . $item_id, LOGGER_DEBUG); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_mid = base64url_encode($rawthread[0]['attribs']['']['ref']); } if ($is_reply) { if ($pass == 1) { continue; } // Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $parent_mid; $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (is_array($contact)) { if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } } if (!x($author, 'author_name') || !x($author, 'author_link')) { logger('consume_feed: no author information! ' . print_r($author, true)); continue; } $datarray['author_xchan'] = ''; if (activity_match($datarray['verb'], ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) { $cb = array('item' => $datarray, 'channel' => $importer, 'xchan' => null, 'author' => $author, 'caught' => false); call_hooks('follow_from_feed', $cb); if ($cb['caught']) { if ($cb['return_code']) { http_status_exit($cb['return_code']); } continue; } } if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; if (array_key_exists('created', $datarray) && $datarray['created'] != NULL_DATE && $expire_days) { $t1 = $datarray['created']; $t2 = datetime_convert('UTC', 'UTC', 'now - ' . $expire_days . 'days'); if ($t1 < $t2) { logger('feed content older than expiration. Ignoring.', LOGGER_DEBUG, LOG_INFO); continue; } } $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $item_id; $datarray['uid'] = $importer['channel_id']; $datarray['aid'] = $importer['channel_account_id']; if (!link_compare($author['owner_link'], $contact['xchan_url'])) { logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); $author['owner_name'] = $contact['name']; $author['owner_link'] = $contact['url']; $author['owner_avatar'] = $contact['thumb']; } if (!post_is_importable($datarray, $contact)) { continue; } logger('consume_feed: author ' . print_r($author, true), LOGGER_DEBUG); logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } } } }
function diaspora_post($importer, $xml, $msg) { $a = get_app(); $guid = notags(unxmlify($xml->guid)); $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); $app = notags(xmlify($xml->provider_display_name)); if ($diaspora_handle != $msg['author']) { logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); return 202; } $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { return; } if (!$app) { if (strstr($contact['xchan_network'], 'friendica')) { $app = 'Friendica'; } else { $app = 'Diaspora'; } } $search_guid = strlen($guid) == 64 ? $guid . '%' : $guid; $r = q("SELECT id FROM item WHERE uid = %d AND mid like '%s' LIMIT 1", intval($importer['channel_id']), dbesc($search_guid)); if ($r) { // check dates if post editing is implemented logger('diaspora_post: message exists: ' . $guid); return; } $created = unxmlify($xml->created_at); $private = unxmlify($xml->public) == 'false' ? 1 : 0; $body = diaspora2bb($xml->raw_message); if ($xml->photo) { $body = '[img]' . $xml->photo->remote_photo_path . $xml->photo->remote_photo_name . '[/img]' . "\n\n" . $body; $body = scale_external_images($body); } $maxlen = get_max_import_size(); if ($maxlen && mb_strlen($body) > $maxlen) { $body = mb_substr($body, 0, $maxlen, 'UTF-8'); logger('message length exceeds max_import_size: truncated'); } //WTF? FIXME // Add OEmbed and other information to the body // $body = add_page_info_to_body($body, false, true); $datarray = array(); // Look for tags and linkify them $results = linkify_tags(get_app(), $body, $importer['channel_id'], true); $datarray['term'] = array(); if ($results) { foreach ($results as $result) { $success = $result['success']; if ($success['replaced']) { $datarray['term'][] = array('uid' => $importer['channel_id'], 'type' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url']); } } } $cnt = preg_match_all('/@\\[url=(.*?)\\](.*?)\\[\\/url\\]/ism', $body, $matches, PREG_SET_ORDER); if ($cnt) { foreach ($matches as $mtch) { $datarray['term'][] = array('uid' => $importer['channel_id'], 'type' => TERM_MENTION, 'otype' => TERM_OBJ_POST, 'term' => $mtch[2], 'url' => $mtch[1]); } } $cnt = preg_match_all('/@\\[zrl=(.*?)\\](.*?)\\[\\/zrl\\]/ism', $body, $matches, PREG_SET_ORDER); if ($cnt) { foreach ($matches as $mtch) { // don't include plustags in the term $term = substr($mtch[2], -1, 1) === '+' ? substr($mtch[2], 0, -1) : $mtch[2]; $datarray['term'][] = array('uid' => $importer['channel_id'], 'type' => TERM_MENTION, 'otype' => TERM_OBJ_POST, 'term' => $term, 'url' => $mtch[1]); } } $plink = service_plink($contact, $guid); $datarray['uid'] = $importer['channel_id']; $datarray['verb'] = ACTIVITY_POST; $datarray['mid'] = $datarray['parent_mid'] = $guid; $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC', 'UTC', $created); $datarray['item_private'] = $private; $datarray['plink'] = $plink; $datarray['author_xchan'] = $contact['xchan_hash']; $datarray['owner_xchan'] = $contact['xchan_hash']; $datarray['body'] = $body; $datarray['app'] = $app; $datarray['item_flags'] = ITEM_THREAD_TOP; $datarray['item_unseen'] = 1; $tgroup = tgroup_check($importer['channel_id'], $datarray); if (!$importer['system'] && !perm_is_allowed($importer['channel_id'], $contact['xchan_hash'], 'send_stream') && !$tgroup) { logger('diaspora_post: Ignoring this author.'); return 202; } if (!post_is_importable($datarray, $contact)) { logger('diaspora_post: filtering this author.'); return 202; } $result = item_store($datarray); return; }