function import_channel($channel, $account_id, $seize) { if (!array_key_exists('channel_system', $channel)) { $channel['channel_system'] = $channel['channel_pageflags'] & 0x1000 ? 1 : 0; $channel['channel_removed'] = $channel['channel_pageflags'] & 0x8000 ? 1 : 0; } // Ignore the hash provided and re-calculate $channel['channel_hash'] = make_xchan_hash($channel['channel_guid'], $channel['channel_guid_sig']); // Check for duplicate channels $r = q("select * from channel where (channel_guid = '%s' or channel_hash = '%s' or channel_address = '%s' ) limit 1", dbesc($channel['channel_guid']), dbesc($channel['channel_hash']), dbesc($channel['channel_address'])); if ($r && $r[0]['channel_guid'] == $channel['channel_guid'] && $r[0]['channel_pubkey'] === $channel['channel_pubkey'] && $r[0]['channel_hash'] === $channel['channel_hash']) { return $r[0]; } if ($r || check_webbie(array($channel['channel_address'])) !== $channel['channel_address']) { if ($r[0]['channel_guid'] === $channel['channel_guid'] || $r[0]['channel_hash'] === $channel['channel_hash']) { logger('mod_import: duplicate channel. ', print_r($channel, true)); notice(t('Cannot create a duplicate channel identifier on this system. Import failed.') . EOL); return false; } else { // try at most ten times to generate a unique address. $x = 0; $found_unique = false; do { $tmp = $channel['channel_address'] . mt_rand(1000, 9999); $r = q("select * from channel where channel_address = '%s' limit 1", dbesc($tmp)); if (!$r) { $channel['channel_address'] = $tmp; $found_unique = true; break; } $x++; } while ($x < 10); if (!$found_unique) { logger('mod_import: duplicate channel. randomisation failed.', print_r($channel, true)); notice(t('Unable to create a unique channel address. Import failed.') . EOL); return false; } } } unset($channel['channel_id']); $channel['channel_account_id'] = $account_id; $channel['channel_primary'] = $seize ? 1 : 0; if ($channel['channel_pageflags'] & PAGE_ALLOWCODE) { if (!is_site_admin()) { $channel['channel_pageflags'] = $channel['channel_pageflags'] ^ PAGE_ALLOWCODE; } } // remove all the permissions related settings, we will import/upgrade them after the channel // is created. $disallowed = ['channel_id', 'channel_r_stream', 'channel_r_profile', 'channel_r_abook', 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', 'channel_a_delegate', 'perm_limits']; $clean = array(); foreach ($channel as $k => $v) { if (in_array($k, $disallowed)) { continue; } $clean[$k] = $v; } if ($clean) { dbesc_array($clean); $r = dbq("INSERT INTO channel (`" . implode("`, `", array_keys($clean)) . "`) VALUES ('" . implode("', '", array_values($clean)) . "')"); } if (!$r) { logger('mod_import: channel clone failed. ', print_r($channel, true)); notice(t('Channel clone failed. Import failed.') . EOL); return false; } $r = q("select * from channel where channel_account_id = %d and channel_guid = '%s' limit 1", intval($account_id), $channel['channel_guid']); if (!$r) { logger('mod_import: channel not found. ', print_r($channel, true)); notice(t('Cloned channel not found. Import failed.') . EOL); return false; } // extract the permissions from the original imported array and use our new channel_id to set them // These could be in the old channel permission stule or the new pconfig. We have a function to // translate and store them no matter which they throw at us. $channel['channel_id'] = $r[0]['channel_id']; translate_channel_perms_inbound($channel); // reset $channel = $r[0]; set_default_login_identity($account_id, $channel['channel_id'], false); logger('import step 1'); $_SESSION['import_step'] = 1; return $channel; }
/** * @brief * * @param array $sender * @param array $arr * @param array $deliveries * @return array */ function process_channel_sync_delivery($sender, $arr, $deliveries) { if (get_config('system', 'server_role') === 'basic') { return; } require_once 'include/import.php'; /** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */ $result = array(); foreach ($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash'])); if (!$r) { $result[] = array($d['hash'], 'not found'); continue; } $channel = $r[0]; $max_friends = service_class_fetch($channel['channel_id'], 'total_channels'); $max_feeds = account_service_class_fetch($channel['channel_account_id'], 'total_feeds'); if ($channel['channel_hash'] != $sender['hash']) { logger('process_channel_sync_delivery: possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']); $result[] = array($d['hash'], 'channel mismatch', $channel['channel_name'], ''); continue; } if (array_key_exists('config', $arr) && is_array($arr['config']) && count($arr['config'])) { foreach ($arr['config'] as $cat => $k) { foreach ($arr['config'][$cat] as $k => $v) { set_pconfig($channel['channel_id'], $cat, $k, $v); } } } if (array_key_exists('obj', $arr) && $arr['obj']) { sync_objs($channel, $arr['obj']); } if (array_key_exists('likes', $arr) && $arr['likes']) { import_likes($channel, $arr['likes']); } if (array_key_exists('app', $arr) && $arr['app']) { sync_apps($channel, $arr['app']); } if (array_key_exists('chatroom', $arr) && $arr['chatroom']) { sync_chatrooms($channel, $arr['chatroom']); } if (array_key_exists('conv', $arr) && $arr['conv']) { import_conv($channel, $arr['conv']); } if (array_key_exists('mail', $arr) && $arr['mail']) { sync_mail($channel, $arr['mail']); } if (array_key_exists('event', $arr) && $arr['event']) { sync_events($channel, $arr['event']); } if (array_key_exists('event_item', $arr) && $arr['event_item']) { sync_items($channel, $arr['event_item'], array_key_exists('relocate', $arr) ? $arr['relocate'] : null); } if (array_key_exists('item', $arr) && $arr['item']) { sync_items($channel, $arr['item'], array_key_exists('relocate', $arr) ? $arr['relocate'] : null); } // deprecated, maintaining for a few months for upward compatibility // this should sync webpages, but the logic is a bit subtle if (array_key_exists('item_id', $arr) && $arr['item_id']) { sync_items($channel, $arr['item_id']); } if (array_key_exists('menu', $arr) && $arr['menu']) { sync_menus($channel, $arr['menu']); } if (array_key_exists('file', $arr) && $arr['file']) { sync_files($channel, $arr['file']); } if (array_key_exists('channel', $arr) && is_array($arr['channel']) && count($arr['channel'])) { translate_channel_perms_inbound($arr['channel']); if (array_key_exists('channel_pageflags', $arr['channel']) && intval($arr['channel']['channel_pageflags'])) { // These flags cannot be sync'd. // remove the bits from the incoming flags. // These correspond to PAGE_REMOVED and PAGE_SYSTEM on redmatrix if ($arr['channel']['channel_pageflags'] & 0x8000) { $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x8000; } if ($arr['channel']['channel_pageflags'] & 0x1000) { $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x1000; } } $disallowed = ['channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', 'channel_system', 'channel_r_stream', 'channel_r_profile', 'channel_r_abook', 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', 'channel_a_delegate']; $clean = array(); foreach ($arr['channel'] as $k => $v) { if (in_array($k, $disallowed)) { continue; } $clean[$k] = $v; } if (count($clean)) { foreach ($clean as $k => $v) { $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) . "' where channel_id = " . intval($channel['channel_id'])); } } } if (array_key_exists('abook', $arr) && is_array($arr['abook']) && count($arr['abook'])) { $total_friends = 0; $total_feeds = 0; $r = q("select abook_id, abook_feed from abook where abook_channel = %d", intval($channel['channel_id'])); if ($r) { // don't count yourself $total_friends = count($r) > 0 ? count($r) - 1 : 0; foreach ($r as $rr) { if (intval($rr['abook_feed'])) { $total_feeds++; } } } $disallowed = array('abook_id', 'abook_account', 'abook_channel', 'abook_rating', 'abook_rating_text'); foreach ($arr['abook'] as $abook) { $abconfig = null; if (array_key_exists('abconfig', $abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) { $abconfig = $abook['abconfig']; } if (!array_key_exists('abook_blocked', $abook)) { // convert from redmatrix $abook['abook_blocked'] = $abook['abook_flags'] & 0x1 ? 1 : 0; $abook['abook_ignored'] = $abook['abook_flags'] & 0x2 ? 1 : 0; $abook['abook_hidden'] = $abook['abook_flags'] & 0x4 ? 1 : 0; $abook['abook_archived'] = $abook['abook_flags'] & 0x8 ? 1 : 0; $abook['abook_pending'] = $abook['abook_flags'] & 0x10 ? 1 : 0; $abook['abook_unconnected'] = $abook['abook_flags'] & 0x20 ? 1 : 0; $abook['abook_self'] = $abook['abook_flags'] & 0x80 ? 1 : 0; $abook['abook_feed'] = $abook['abook_flags'] & 0x100 ? 1 : 0; } $clean = array(); if ($abook['abook_xchan'] && $abook['entry_deleted']) { logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']); $r = q("select abook_id, abook_feed from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($abook['abook_xchan']), intval($channel['channel_id'])); if ($r) { contact_remove($channel['channel_id'], $r[0]['abook_id']); if ($total_friends) { $total_friends--; } if (intval($r[0]['abook_feed'])) { $total_feeds--; } } continue; } // Perform discovery if the referenced xchan hasn't ever been seen on this hub. // This relies on the undocumented behaviour that red sites send xchan info with the abook // and import_author_xchan will look them up on all federated networks if ($abook['abook_xchan'] && $abook['xchan_addr']) { $h = zot_get_hublocs($abook['abook_xchan']); if (!$h) { $xhash = import_author_xchan(encode_item_xchan($abook)); if (!$xhash) { logger('process_channel_sync_delivery: import of ' . $abook['xchan_addr'] . ' failed.'); continue; } } } foreach ($abook as $k => $v) { if (in_array($k, $disallowed) || strpos($k, 'abook') !== 0) { continue; } $clean[$k] = $v; } if (!array_key_exists('abook_xchan', $clean)) { continue; } $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($clean['abook_xchan']), intval($channel['channel_id'])); // make sure we have an abook entry for this xchan on this system if (!$r) { if ($max_friends !== false && $total_friends > $max_friends) { logger('process_channel_sync_delivery: total_channels service class limit exceeded'); continue; } if ($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) { logger('process_channel_sync_delivery: total_feeds service class limit exceeded'); continue; } q("insert into abook ( abook_xchan, abook_account, abook_channel ) values ('%s', %d, %d ) ", dbesc($clean['abook_xchan']), intval($channel['channel_account_id']), intval($channel['channel_id'])); $total_friends++; if (intval($clean['abook_feed'])) { $total_feeds++; } } if (count($clean)) { foreach ($clean as $k => $v) { if ($k == 'abook_dob') { $v = dbescdate($v); } $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id'])); } } // This will set abconfig vars if the sender is using old-style fixed permissions // using the raw abook record as passed to us. New-style permissions will fall through // and be set using abconfig translate_abook_perms_inbound($channel, $abook); if ($abconfig) { // @fixme does not handle sync of del_abconfig foreach ($abconfig as $abc) { set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], $abc['v']); } } } } // sync collections (privacy groups) oh joy... if (array_key_exists('collections', $arr) && is_array($arr['collections']) && count($arr['collections'])) { $x = q("select * from groups where uid = %d", intval($channel['channel_id'])); foreach ($arr['collections'] as $cl) { $found = false; if ($x) { foreach ($x as $y) { if ($cl['collection'] == $y['hash']) { $found = true; break; } } if ($found) { if ($y['gname'] != $cl['name'] || $y['visible'] != $cl['visible'] || $y['deleted'] != $cl['deleted']) { q("update groups set gname = '%s', visible = %d, deleted = %d where hash = '%s' and uid = %d", dbesc($cl['name']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['hash']), intval($channel['channel_id'])); } if (intval($cl['deleted']) && !intval($y['deleted'])) { q("delete from group_member where gid = %d", intval($y['id'])); } } } if (!$found) { $r = q("INSERT INTO `groups` ( hash, uid, visible, deleted, gname )\n\t\t\t\t\t\tVALUES( '%s', %d, %d, %d, '%s' ) ", dbesc($cl['collection']), intval($channel['channel_id']), intval($cl['visible']), intval($cl['deleted']), dbesc($cl['name'])); } // now look for any collections locally which weren't in the list we just received. // They need to be removed by marking deleted and removing the members. // This shouldn't happen except for clones created before this function was written. if ($x) { $found_local = false; foreach ($x as $y) { foreach ($arr['collections'] as $cl) { if ($cl['collection'] == $y['hash']) { $found_local = true; break; } } if (!$found_local) { q("delete from group_member where gid = %d", intval($y['id'])); q("update groups set deleted = 1 where id = %d and uid = %d", intval($y['id']), intval($channel['channel_id'])); } } } } // reload the group list with any updates $x = q("select * from groups where uid = %d", intval($channel['channel_id'])); // now sync the members if (array_key_exists('collection_members', $arr) && is_array($arr['collection_members']) && count($arr['collection_members'])) { // first sort into groups keyed by the group hash $members = array(); foreach ($arr['collection_members'] as $cm) { if (!array_key_exists($cm['collection'], $members)) { $members[$cm['collection']] = array(); } $members[$cm['collection']][] = $cm['member']; } // our group list is already synchronised if ($x) { foreach ($x as $y) { // for each group, loop on members list we just received foreach ($members[$y['hash']] as $member) { $found = false; $z = q("select xchan from group_member where gid = %d and uid = %d and xchan = '%s' limit 1", intval($y['id']), intval($channel['channel_id']), dbesc($member)); if ($z) { $found = true; } // if somebody is in the group that wasn't before - add them if (!$found) { q("INSERT INTO `group_member` (`uid`, `gid`, `xchan`)\n\t\t\t\t\t\t\t\t\tVALUES( %d, %d, '%s' ) ", intval($channel['channel_id']), intval($y['id']), dbesc($member)); } } // now retrieve a list of members we have on this site $m = q("select xchan from group_member where gid = %d and uid = %d", intval($y['id']), intval($channel['channel_id'])); if ($m) { foreach ($m as $mm) { // if the local existing member isn't in the list we just received - remove them if (!in_array($mm['xchan'], $members[$y['hash']])) { q("delete from group_member where xchan = '%s' and gid = %d and uid = %d", dbesc($mm['xchan']), intval($y['id']), intval($channel['channel_id'])); } } } } } } } if (array_key_exists('profile', $arr) && is_array($arr['profile']) && count($arr['profile'])) { $disallowed = array('id', 'aid', 'uid', 'guid'); foreach ($arr['profile'] as $profile) { $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id'])); if (!$x) { q("insert into profile ( profile_guid, aid, uid ) values ('%s', %d, %d)", dbesc($profile['profile_guid']), intval($channel['channel_account_id']), intval($channel['channel_id'])); $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id'])); if (!$x) { continue; } } $clean = array(); foreach ($profile as $k => $v) { if (in_array($k, $disallowed)) { continue; } if ($k === 'name') { $clean['fullname'] = $v; } elseif ($k === 'with') { $clean['partner'] = $v; } elseif ($k === 'work') { $clean['employment'] = $v; } elseif (array_key_exists($k, $x[0])) { $clean[$k] = $v; } /** * @TODO * We also need to import local photos if a custom photo is selected */ } if (count($clean)) { foreach ($clean as $k => $v) { $r = dbq("UPDATE profile set `" . dbesc($k) . "` = '" . dbesc($v) . "' where profile_guid = '" . dbesc($profile['profile_guid']) . "' and uid = " . intval($channel['channel_id'])); } } } } $addon = array('channel' => $channel, 'data' => $arr); call_hooks('process_channel_sync_delivery', $addon); // we should probably do this for all items, but usually we only send one. if (array_key_exists('item', $arr) && is_array($arr['item'][0])) { $DR = new Zotlabs\Zot\DReport(z_root(), $d['hash'], $d['hash'], $arr['item'][0]['message_id'], 'channel sync processed'); $DR->addto_recipient($channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . App::get_hostname() . '>'); } else { $DR = new Zotlabs\Zot\DReport(z_root(), $d['hash'], $d['hash'], 'sync packet', 'channel sync delivered'); } $result[] = $DR->get(); } return $result; }