Exemple #1
0
/**
 * @brief Takes an associative array of a fetched discovery packet and updates
 *   all internal data structures which need to be updated as a result.
 *
 * @param array $arr => json_decoded discovery packet
 * @param int $ud_flags
 *    Determines whether to create a directory update record if any changes occur, default is UPDATE_FLAGS_UPDATED
 *    $ud_flags = UPDATE_FLAGS_FORCED indicates a forced refresh where we unconditionally create a directory update record
 *      this typically occurs once a month for each channel as part of a scheduled ping to notify the directory
 *      that the channel still exists
 * @param array $ud_arr
 *    If set [typically by update_directory_entry()] indicates a specific update table row and more particularly
 *    contains a particular address (ud_addr) which needs to be updated in that table.
 *
 * @return associative array
 *   * \e boolean \b success boolean true or false
 *   * \e string \b message (optional) error string only if success is false
 */
function import_xchan($arr, $ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null)
{
    call_hooks('import_xchan', $arr);
    $ret = array('success' => false);
    $dirmode = intval(get_config('system', 'directory_mode'));
    $changed = false;
    $what = '';
    if (!(is_array($arr) && array_key_exists('success', $arr) && $arr['success'])) {
        logger('import_xchan: invalid data packet: ' . print_r($arr, true));
        $ret['message'] = t('Invalid data packet');
        return $ret;
    }
    if (!($arr['guid'] && $arr['guid_sig'])) {
        logger('import_xchan: no identity information provided. ' . print_r($arr, true));
        return $ret;
    }
    $xchan_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']);
    $arr['hash'] = $xchan_hash;
    $import_photos = false;
    if (!rsa_verify($arr['guid'], base64url_decode($arr['guid_sig']), $arr['key'])) {
        logger('import_xchan: Unable to verify channel signature for ' . $arr['address']);
        $ret['message'] = t('Unable to verify channel signature');
        return $ret;
    }
    logger('import_xchan: ' . $xchan_hash, LOGGER_DEBUG);
    $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($xchan_hash));
    if (!array_key_exists('connect_url', $arr)) {
        $arr['connect_url'] = '';
    }
    if (strpos($arr['address'], '/') !== false) {
        $arr['address'] = substr($arr['address'], 0, strpos($arr['address'], '/'));
    }
    if ($r) {
        if ($r[0]['xchan_photo_date'] != $arr['photo_updated']) {
            $import_photos = true;
        }
        // if we import an entry from a site that's not ours and either or both of us is off the grid - hide the entry.
        /** @TODO: check if we're the same directory realm, which would mean we are allowed to see it */
        $dirmode = get_config('system', 'directory_mode');
        if (($arr['site']['directory_mode'] === 'standalone' || $dirmode & DIRECTORY_MODE_STANDALONE) && $arr['site']['url'] != z_root()) {
            $arr['searchable'] = false;
        }
        $hidden = 1 - intval($arr['searchable']);
        $hidden_changed = $adult_changed = $deleted_changed = $pubforum_changed = 0;
        if (intval($r[0]['xchan_hidden']) != 1 - intval($arr['searchable'])) {
            $hidden_changed = 1;
        }
        if (intval($r[0]['xchan_selfcensored']) != intval($arr['adult_content'])) {
            $adult_changed = 1;
        }
        if (intval($r[0]['xchan_deleted']) != intval($arr['deleted'])) {
            $deleted_changed = 1;
        }
        if (intval($r[0]['xchan_pubforum']) != intval($arr['public_forum'])) {
            $pubforum_changed = 1;
        }
        if ($r[0]['xchan_name_date'] != $arr['name_updated'] || $r[0]['xchan_connurl'] != $arr['connections_url'] || $r[0]['xchan_addr'] != $arr['address'] || $r[0]['xchan_follow'] != $arr['follow_url'] || $r[0]['xchan_connpage'] != $arr['connect_url'] || $r[0]['xchan_url'] != $arr['url'] || $hidden_changed || $adult_changed || $deleted_changed || $pubforum_changed) {
            $rup = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', \n\t\t\t\txchan_connpage = '%s', xchan_hidden = %d, xchan_selfcensored = %d, xchan_deleted = %d, xchan_pubforum = %d, \n\t\t\t\txchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s'", dbesc($arr['name'] ? $arr['name'] : '-'), dbesc($arr['name_updated']), dbesc($arr['connections_url']), dbesc($arr['follow_url']), dbesc($arr['connect_url']), intval(1 - intval($arr['searchable'])), intval($arr['adult_content']), intval($arr['deleted']), intval($arr['public_forum']), dbesc($arr['address']), dbesc($arr['url']), dbesc($xchan_hash));
            logger('import_xchan: update: existing: ' . print_r($r[0], true), LOGGER_DATA);
            logger('import_xchan: update: new: ' . print_r($arr, true), LOGGER_DATA);
            $what .= 'xchan ';
            $changed = true;
        }
    } else {
        $import_photos = true;
        if (($arr['site']['directory_mode'] === 'standalone' || $dirmode & DIRECTORY_MODE_STANDALONE) && $arr['site']['url'] != z_root()) {
            $arr['searchable'] = false;
        }
        $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype,\n\t\t\t\txchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_hidden, xchan_selfcensored, xchan_deleted, xchan_pubforum )\n\t\t\t\tvalues ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, %d) ", dbesc($xchan_hash), dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($arr['key']), dbesc($arr['photo_mimetype']), dbesc($arr['photo']), dbesc($arr['address']), dbesc($arr['url']), dbesc($arr['connections_url']), dbesc($arr['follow_url']), dbesc($arr['connect_url']), dbesc($arr['name'] ? $arr['name'] : '-'), dbesc('zot'), dbescdate($arr['photo_updated']), dbescdate($arr['name_updated']), intval(1 - intval($arr['searchable'])), intval($arr['adult_content']), intval($arr['deleted']), intval($arr['public_forum']));
        $what .= 'new_xchan';
        $changed = true;
    }
    if ($import_photos) {
        require_once 'include/photo/photo_driver.php';
        // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections
        $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", dbesc($xchan_hash));
        if ($local) {
            $ph = z_fetch_url($arr['photo'], true);
            if ($ph['success']) {
                $hash = import_channel_photo($ph['body'], $arr['photo_mimetype'], $local[0]['channel_account_id'], $local[0]['channel_id']);
                if ($hash) {
                    // unless proven otherwise
                    $is_default_profile = 1;
                    $profile = q("select is_default from profile where aid = %d and uid = %d limit 1", intval($local[0]['channel_account_id']), intval($local[0]['channel_id']));
                    if ($profile) {
                        if (!intval($profile[0]['is_default'])) {
                            $is_default_profile = 0;
                        }
                    }
                    // If setting for the default profile, unset the profile photo flag from any other photos I own
                    if ($is_default_profile) {
                        q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d AND resource_id != '%s' AND aid = %d AND uid = %d", intval(PHOTO_NORMAL), intval(PHOTO_PROFILE), dbesc($hash), intval($local[0]['channel_account_id']), intval($local[0]['channel_id']));
                    }
                }
                // reset the names in case they got messed up when we had a bug in this function
                $photos = array(z_root() . '/photo/profile/l/' . $local[0]['channel_id'], z_root() . '/photo/profile/m/' . $local[0]['channel_id'], z_root() . '/photo/profile/s/' . $local[0]['channel_id'], $arr['photo_mimetype'], false);
            }
        } else {
            $photos = import_xchan_photo($arr['photo'], $xchan_hash);
        }
        if ($photos) {
            if ($photos[4]) {
                // importing the photo failed somehow. Leave the photo_date alone so we can try again at a later date.
                // This often happens when somebody joins the matrix with a bad cert.
                $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s'\n\t\t\t\t\twhere xchan_hash = '%s'", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($xchan_hash));
            } else {
                $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s'\n\t\t\t\t\twhere xchan_hash = '%s'", dbescdate(datetime_convert('UTC', 'UTC', $arr['photo_updated'])), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($xchan_hash));
            }
            $what .= 'photo ';
            $changed = true;
        }
    }
    // what we are missing for true hub independence is for any changes in the primary hub to
    // get reflected not only in the hublocs, but also to update the URLs and addr in the appropriate xchan
    $s = sync_locations($arr, $arr);
    if ($s) {
        if ($s['change_message']) {
            $what .= $s['change_message'];
        }
        if ($s['changed']) {
            $changed = $s['changed'];
        }
        if ($s['message']) {
            $ret['message'] .= $s['message'];
        }
    }
    // Which entries in the update table are we interested in updating?
    $address = $ud_arr && $ud_arr['ud_addr'] ? $ud_arr['ud_addr'] : $arr['address'];
    // Are we a directory server of some kind?
    $other_realm = false;
    $realm = get_directory_realm();
    if (array_key_exists('site', $arr) && array_key_exists('realm', $arr['site']) && strpos($arr['site']['realm'], $realm) === false) {
        $other_realm = true;
    }
    if ($dirmode != DIRECTORY_MODE_NORMAL) {
        // We're some kind of directory server. However we can only add directory information
        // if the entry is in the same realm (or is a sub-realm). Sub-realms are denoted by
        // including the parent realm in the name. e.g. 'RED_GLOBAL:foo' would allow an entry to
        // be in directories for the local realm (foo) and also the RED_GLOBAL realm.
        if (array_key_exists('profile', $arr) && is_array($arr['profile']) && !$other_realm) {
            $profile_changed = import_directory_profile($xchan_hash, $arr['profile'], $address, $ud_flags, 1);
            if ($profile_changed) {
                $what .= 'profile ';
                $changed = true;
            }
        } else {
            logger('import_xchan: profile not available - hiding');
            // they may have made it private
            $r = q("delete from xprof where xprof_hash = '%s'", dbesc($xchan_hash));
            $r = q("delete from xtag where xtag_hash = '%s' and xtag_flags = 0", dbesc($xchan_hash));
        }
    }
    if (array_key_exists('site', $arr) && is_array($arr['site'])) {
        $profile_changed = import_site($arr['site'], $arr['key']);
        if ($profile_changed) {
            $what .= 'site ';
            $changed = true;
        }
    }
    if ($changed || $ud_flags == UPDATE_FLAGS_FORCED) {
        $guid = random_string() . '@' . get_app()->get_hostname();
        update_modtime($xchan_hash, $guid, $address, $ud_flags);
        logger('import_xchan: changed: ' . $what, LOGGER_DEBUG);
    } elseif (!$ud_flags) {
        // nothing changed but we still need to update the updates record
        q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", intval(UPDATE_FLAGS_UPDATED), dbesc($address), intval(UPDATE_FLAGS_UPDATED));
    }
    if (!x($ret, 'message')) {
        $ret['success'] = true;
        $ret['hash'] = $xchan_hash;
    }
    logger('import_xchan: result: ' . print_r($ret, true), LOGGER_DATA);
    return $ret;
}
Exemple #2
0
/**
 * @function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED)
 *   Takes an associative array of a fetched discovery packet and updates
 *   all internal data structures which need to be updated as a result.
 * 
 * @param array $arr => json_decoded discovery packet
 * @param int $ud_flags
 *    Determines whether to create a directory update record if any changes occur, default is UPDATE_FLAGS_UPDATED
 *    $ud_flags = UPDATE_FLAGS_FORCED indicates a forced refresh where we unconditionally create a directory update record
 *      this typically occurs once a month for each channel as part of a scheduled ping to notify the directory
 *      that the channel still exists
 * @param array $ud_arr
 *    If set [typically by update_directory_entry()] indicates a specific update table row and more particularly 
 *    contains a particular address (ud_addr) which needs to be updated in that table.
 *
 * @returns array =>  'success' (boolean true or false)
 *                    'message' (optional error string only if success is false)
 */
function import_xchan($arr, $ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null)
{
    call_hooks('import_xchan', $arr);
    $ret = array('success' => false);
    $dirmode = intval(get_config('system', 'directory_mode'));
    $changed = false;
    $what = '';
    if (!(is_array($arr) && array_key_exists('success', $arr) && $arr['success'])) {
        logger('import_xchan: invalid data packet: ' . print_r($arr, true));
        $ret['message'] = t('Invalid data packet');
        return $ret;
    }
    if (!($arr['guid'] && $arr['guid_sig'])) {
        logger('import_xchan: no identity information provided. ' . print_r($arr, true));
        return $ret;
    }
    $xchan_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']);
    $import_photos = false;
    if (!rsa_verify($arr['guid'], base64url_decode($arr['guid_sig']), $arr['key'])) {
        logger('import_xchan: Unable to verify channel signature for ' . $arr['address']);
        $ret['message'] = t('Unable to verify channel signature');
        return $ret;
    }
    logger('import_xchan: ' . $xchan_hash, LOGGER_DEBUG);
    $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($xchan_hash));
    if (!array_key_exists('connect_url', $arr)) {
        $arr['connect_url'] = '';
    }
    if (strpos($arr['address'], '/') !== false) {
        $arr['address'] = substr($arr['address'], 0, strpos($arr['address'], '/'));
    }
    if ($r) {
        if ($r[0]['xchan_photo_date'] != $arr['photo_updated']) {
            $import_photos = true;
        }
        // if we import an entry from a site that's not ours and either or both of us is off the grid - hide the entry.
        // TODO: check if we're the same directory realm, which would mean we are allowed to see it
        $dirmode = get_config('system', 'directory_mode');
        if (($arr['site']['directory_mode'] === 'standalone' || $dirmode & DIRECTORY_MODE_STANDALONE) && $arr['site']['url'] != z_root()) {
            $arr['searchable'] = false;
        }
        $hidden = 1 - intval($arr['searchable']);
        // Be careful - XCHAN_FLAGS_HIDDEN should evaluate to 1
        if (($r[0]['xchan_flags'] & XCHAN_FLAGS_HIDDEN) != $hidden) {
            $new_flags = $r[0]['xchan_flags'] ^ XCHAN_FLAGS_HIDDEN;
        } else {
            $new_flags = $r[0]['xchan_flags'];
        }
        $adult = $r[0]['xchan_flags'] & XCHAN_FLAGS_SELFCENSORED ? true : false;
        $adult_changed = intval($adult) != intval($arr['adult_content']) ? true : false;
        if ($adult_changed) {
            $new_flags = $new_flags ^ XCHAN_FLAGS_SELFCENSORED;
        }
        $deleted = $r[0]['xchan_flags'] & XCHAN_FLAGS_DELETED ? true : false;
        $deleted_changed = intval($deleted) != intval($arr['deleted']) ? true : false;
        if ($deleted_changed) {
            $new_flags = $new_flags ^ XCHAN_FLAGS_DELETED;
        }
        if ($r[0]['xchan_name_date'] != $arr['name_updated'] || $r[0]['xchan_connurl'] != $arr['connections_url'] || $r[0]['xchan_flags'] != $new_flags || $r[0]['xchan_addr'] != $arr['address'] || $r[0]['xchan_follow'] != $arr['follow_url'] || $r[0]['xchan_connpage'] != $arr['connect_url'] || $r[0]['xchan_url'] != $arr['url']) {
            $r = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', \n\t\t\t\txchan_connpage = '%s', xchan_flags = %d,\n\t\t\t\txchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s' limit 1", dbesc($arr['name']), dbesc($arr['name_updated']), dbesc($arr['connections_url']), dbesc($arr['follow_url']), dbesc($arr['connect_url']), intval($new_flags), dbesc($arr['address']), dbesc($arr['url']), dbesc($xchan_hash));
            logger('import_xchan: existing: ' . print_r($r[0], true), LOGGER_DATA);
            logger('import_xchan: new: ' . print_r($arr, true), LOGGER_DATA);
            $what .= 'xchan ';
            $changed = true;
        }
    } else {
        $import_photos = true;
        if (($arr['site']['directory_mode'] === 'standalone' || $dirmode & DIRECTORY_MODE_STANDALONE) && $arr['site']['url'] != z_root()) {
            $arr['searchable'] = false;
        }
        $hidden = 1 - intval($arr['searchable']);
        if ($hidden) {
            $new_flags = XCHAN_FLAGS_HIDDEN;
        } else {
            $new_flags = 0;
        }
        if ($arr['adult_content']) {
            $new_flags |= XCHAN_FLAGS_SELFCENSORED;
        }
        if (array_key_exists('deleted', $arr) && $arr['deleted']) {
            $new_flags |= XCHAN_FLAGS_DELETED;
        }
        $x = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_mimetype,\n\t\t\t\txchan_photo_l, xchan_addr, xchan_url, xchan_connurl, xchan_follow, xchan_connpage, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_flags)\n\t\t\t\tvalues ( '%s', '%s', '%s', '%s' , '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d) ", dbesc($xchan_hash), dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($arr['key']), dbesc($arr['photo_mimetype']), dbesc($arr['photo']), dbesc($arr['address']), dbesc($arr['url']), dbesc($arr['connections_url']), dbesc($arr['follow_url']), dbesc($arr['connect_url']), dbesc($arr['name']), dbesc('zot'), dbesc($arr['photo_updated']), dbesc($arr['name_updated']), intval($new_flags));
        $what .= 'new_xchan';
        $changed = true;
    }
    if ($import_photos) {
        require_once 'include/photo/photo_driver.php';
        // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections
        $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", dbesc($xchan_hash));
        if ($local) {
            $ph = z_fetch_url($arr['photo'], true);
            if ($ph['success']) {
                import_channel_photo($ph['body'], $arr['photo_mimetype'], $local[0]['channel_account_id'], $local[0]['channel_id']);
                // reset the names in case they got messed up when we had a bug in this function
                $photos = array(z_root() . '/photo/profile/l/' . $local[0]['channel_id'], z_root() . '/photo/profile/m/' . $local[0]['channel_id'], z_root() . '/photo/profile/s/' . $local[0]['channel_id'], $arr['photo_mimetype'], false);
            }
        } else {
            $photos = import_profile_photo($arr['photo'], $xchan_hash);
        }
        if ($photos) {
            if ($photos[4]) {
                // importing the photo failed somehow. Leave the photo_date alone so we can try again at a later date.
                // This often happens when somebody joins the matrix with a bad cert.
                $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s'\n\t\t\t\t\twhere xchan_hash = '%s' limit 1", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($xchan_hash));
            } else {
                $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s'\n\t\t\t\t\twhere xchan_hash = '%s' limit 1", dbesc(datetime_convert('UTC', 'UTC', $arr['photo_updated'])), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($xchan_hash));
            }
            $what .= 'photo ';
            $changed = true;
        }
    }
    // what we are missing for true hub independence is for any changes in the primary hub to
    // get reflected not only in the hublocs, but also to update the URLs and addr in the appropriate xchan
    if ($arr['locations']) {
        $xisting = q("select hubloc_id, hubloc_url, hubloc_sitekey from hubloc where hubloc_hash = '%s'", dbesc($xchan_hash));
        // See if a primary is specified
        $has_primary = false;
        foreach ($arr['locations'] as $location) {
            if ($location['primary']) {
                $has_primary = true;
                break;
            }
        }
        foreach ($arr['locations'] as $location) {
            if (!rsa_verify($location['url'], base64url_decode($location['url_sig']), $arr['key'])) {
                logger('import_xchan: Unable to verify site signature for ' . $location['url']);
                $ret['message'] .= sprintf(t('Unable to verify site signature for %s'), $location['url']) . EOL;
                continue;
            }
            // Ensure that they have one primary hub
            if (!$has_primary) {
                $location['primary'] = true;
            }
            for ($x = 0; $x < count($xisting); $x++) {
                if ($xisting[$x]['hubloc_url'] === $location['url'] && $xisting[$x]['hubloc_sitekey'] === $location['sitekey']) {
                    $xisting[$x]['updated'] = true;
                }
            }
            if (!$location['sitekey']) {
                logger('import_xchan: empty hubloc sitekey. ' . print_r($location, true));
                continue;
            }
            // Catch some malformed entries from the past which still exist
            if (strpos($location['address'], '/') !== false) {
                $location['address'] = substr($location['address'], 0, strpos($location['address'], '/'));
            }
            // match as many fields as possible in case anything at all changed.
            $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_guid = '%s' and hubloc_guid_sig = '%s' and hubloc_url = '%s' and hubloc_url_sig = '%s' and hubloc_host = '%s' and hubloc_addr = '%s' and hubloc_callback = '%s' and hubloc_sitekey = '%s' ", dbesc($xchan_hash), dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($location['url']), dbesc($location['url_sig']), dbesc($location['host']), dbesc($location['address']), dbesc($location['callback']), dbesc($location['sitekey']));
            if ($r) {
                logger('import_xchan: hub exists: ' . $location['url']);
                // update connection timestamp if this is the site we're talking to
                if ($location['url'] == $arr['site']['url']) {
                    q("update hubloc set hubloc_connected = '%s', hubloc_updated = '%s' where hubloc_id = %d limit 1", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['hubloc_id']));
                }
                if ($r[0]['hubloc_status'] & HUBLOC_OFFLINE) {
                    q("update hubloc set hubloc_status = (hubloc_status ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_OFFLINE), intval($r[0]['hubloc_id']));
                    if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_ORPHANCHECK) {
                        q("update hubloc set hubloc_flags = (hubloc_flags ^ %d) where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_ORPHANCHECK), intval($r[0]['hubloc_id']));
                    }
                    q("update xchan set xchan_flags = (xchan_flags ^ %d) where (xchan_flags & %d) and xchan_hash = '%s' limit 1", intval(XCHAN_FLAGS_ORPHAN), intval(XCHAN_FLAGS_ORPHAN), dbesc($xchan_hash));
                }
                // Remove pure duplicates
                if (count($r) > 1) {
                    for ($h = 1; $h < count($r); $h++) {
                        q("delete from hubloc where hubloc_id = %d limit 1", intval($r[$h]['hubloc_id']));
                    }
                }
                if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY && !$location['primary'] || !($r[0]['hubloc_flags'] & HUBLOC_FLAGS_PRIMARY) && $location['primary']) {
                    $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_PRIMARY), dbesc(datetime_convert()), intval($r[0]['hubloc_id']));
                    $what = 'primary_hub ';
                    $changed = true;
                }
                if ($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED && !$location['deleted'] || !($r[0]['hubloc_flags'] & HUBLOC_FLAGS_DELETED) && $location['deleted']) {
                    $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_id = %d limit 1", intval(HUBLOC_FLAGS_DELETED), dbesc(datetime_convert()), intval($r[0]['hubloc_id']));
                    $what = 'delete_hub ';
                    $changed = true;
                }
                continue;
            }
            // new hub claiming to be primary. Make it so.
            if (intval($location['primary'])) {
                $r = q("update hubloc set hubloc_flags = (hubloc_flags ^ %d), hubloc_updated = '%s' where hubloc_hash = '%s' and (hubloc_flags & %d )", intval(HUBLOC_FLAGS_PRIMARY), dbesc(datetime_convert()), dbesc($xchan_hash), intval(HUBLOC_FLAGS_PRIMARY));
            }
            logger('import_xchan: new hub: ' . $location['url']);
            $r = q("insert into hubloc ( hubloc_guid, hubloc_guid_sig, hubloc_hash, hubloc_addr, hubloc_network, hubloc_flags, hubloc_url, hubloc_url_sig, hubloc_host, hubloc_callback, hubloc_sitekey, hubloc_updated, hubloc_connected)\n\t\t\t\t\tvalues ( '%s','%s','%s','%s', '%s', %d ,'%s','%s','%s','%s','%s','%s','%s')", dbesc($arr['guid']), dbesc($arr['guid_sig']), dbesc($xchan_hash), dbesc($location['address']), dbesc('zot'), intval(intval($location['primary']) ? HUBLOC_FLAGS_PRIMARY : 0), dbesc($location['url']), dbesc($location['url_sig']), dbesc($location['host']), dbesc($location['callback']), dbesc($location['sitekey']), dbesc(datetime_convert()), dbesc(datetime_convert()));
            $what .= 'newhub ';
            $changed = true;
        }
        // get rid of any hubs we have for this channel which weren't reported.
        // This was needed at one time to resolve complicated cross-site inconsistencies, but can cause sync conflict.
        // currently disabled.
        //		if($xisting) {
        //			foreach($xisting as $x) {
        //				if(! array_key_exists('updated',$x)) {
        //					logger('import_xchan: removing unreferenced hub location ' . $x['hubloc_url']);
        //					$r = q("delete from hubloc where hubloc_id = %d limit 1",
        //						intval($x['hubloc_id'])
        //					);
        //					$what .= 'removed_hub';
        //					$changed = true;
        //				}
        //			}
        //		}
    }
    // Which entries in the update table are we interested in updating?
    $address = $ud_arr && $ud_arr['ud_addr'] ? $ud_arr['ud_addr'] : $arr['address'];
    // Are we a directory server of some kind?
    $other_realm = false;
    $realm = get_directory_realm();
    if (array_key_exists('site', $arr) && array_key_exists('realm', $arr['site']) && strpos($arr['site']['realm'], $realm) === false) {
        $other_realm = true;
    }
    if ($dirmode != DIRECTORY_MODE_NORMAL) {
        // We're some kind of directory server. However we can only add directory information
        // if the entry is in the same realm (or is a sub-realm). Sub-realms are denoted by
        // including the parent realm in the name. e.g. 'RED_GLOBAL:foo' would allow an entry to
        // be in directories for the local realm (foo) and also the RED_GLOBAL realm.
        if (array_key_exists('profile', $arr) && is_array($arr['profile']) && !$other_realm) {
            $profile_changed = import_directory_profile($xchan_hash, $arr['profile'], $address, $ud_flags, 1);
            if ($profile_changed) {
                $what .= 'profile ';
                $changed = true;
            }
        } else {
            logger('import_xchan: profile not available - hiding');
            // they may have made it private
            $r = q("delete from xprof where xprof_hash = '%s' limit 1", dbesc($xchan_hash));
            $r = q("delete from xtag where xtag_hash = '%s' limit 1", dbesc($xchan_hash));
        }
    }
    if (array_key_exists('site', $arr) && is_array($arr['site'])) {
        $profile_changed = import_site($arr['site'], $arr['key']);
        if ($profile_changed) {
            $what .= 'site ';
            $changed = true;
        }
    }
    if ($changed || $ud_flags == UPDATE_FLAGS_FORCED) {
        $guid = random_string() . '@' . get_app()->get_hostname();
        update_modtime($xchan_hash, $guid, $address, $ud_flags);
        logger('import_xchan: changed: ' . $what, LOGGER_DEBUG);
    } elseif (!$ud_flags) {
        // nothing changed but we still need to update the updates record
        q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) ", intval(UPDATE_FLAGS_UPDATED), dbesc($address), intval(UPDATE_FLAGS_UPDATED));
    }
    if (!x($ret, 'message')) {
        $ret['success'] = true;
        $ret['hash'] = $xchan_hash;
    }
    logger('import_xchan: result: ' . print_r($ret, true), LOGGER_DATA);
    return $ret;
}