Exemplo n.º 1
0
Arquivo: zot.php Projeto: 23n/hubzilla
/**
 * @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;
}
Exemplo n.º 2
0
/**
 * @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;
            }
        }
    }
}
Exemplo n.º 3
0
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;
}
Exemplo n.º 4
0
/**
 * @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;
            }
        }
    }
}
Exemplo n.º 5
0
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;
}