/** * Directories may come and go over time. We will need to check that our * directory server is still valid occasionally, and reset to something that * is if our directory has gone offline for any reason */ function check_upstream_directory() { $directory = get_config('system', 'directory_server'); // it's possible there is no directory server configured and the local hub is being used. // If so, default to preserving the absence of a specific server setting. $isadir = true; if ($directory) { $h = parse_url($directory); if ($h) { $j = Zotlabs\Zot\Finger::run('[system]@' . $h['host']); if ($j['success']) { if (array_key_exists('site', $j) && array_key_exists('directory_mode', $j['site'])) { if ($j['site']['directory_mode'] === 'normal') { $isadir = false; } } } } } if (!$isadir) { set_config('system', 'directory_server', ''); } }
function new_contact($uid, $url, $channel, $interactive = false, $confirm = false) { $result = array('success' => false, 'message' => ''); $is_red = false; $is_http = strpos($url, '://') !== false ? true : false; if ($is_http && substr($url, -1, 1) === '/') { $url = substr($url, 0, -1); } if (!allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); return $result; } if (!$url) { $result['message'] = t('Channel location missing.'); return $result; } // check service class limits $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", intval($uid)); if ($r) { $total_channels = $r[0]['total']; } if (!service_class_allows($uid, 'total_channels', $total_channels)) { $result['message'] = upgrade_message(); return $result; } $arr = array('url' => $url, 'channel' => array()); call_hooks('follow', $arr); if ($arr['channel']['success']) { $ret = $arr['channel']; } elseif (!$is_http) { $ret = Zotlabs\Zot\Finger::run($url, $channel); } if ($ret && is_array($ret) && $ret['success']) { $is_red = true; $j = $ret; } $my_perms = get_channel_default_perms($uid); $role = get_pconfig($uid, 'system', 'permissions_role'); if ($role) { $x = \Zotlabs\Access\PermissionRoles::role_perms($role); if ($x['perms_connect']) { $my_perms = $x['perms_connect']; } } if ($is_red && $j) { logger('follow: ' . $url . ' ' . print_r($j, true), LOGGER_DEBUG); if (!($j['success'] && $j['guid'])) { $result['message'] = t('Response from remote channel was incomplete.'); logger('mod_follow: ' . $result['message']); return $result; } // Premium channel, set confirm before callback to avoid recursion if (array_key_exists('connect_url', $j) && $interactive && !$confirm) { goaway(zid($j['connect_url'])); } // do we have an xchan and hubloc? // If not, create them. $x = import_xchan($j); if (array_key_exists('deleted', $j) && intval($j['deleted'])) { $result['message'] = t('Channel was deleted and no longer exists.'); return $result; } if (!$x['success']) { return $x; } $xchan_hash = $x['hash']; if (array_key_exists('permissions', $j) && array_key_exists('data', $j['permissions'])) { $permissions = crypto_unencapsulate(array('data' => $j['permissions']['data'], 'key' => $j['permissions']['key'], 'iv' => $j['permissions']['iv']), $channel['channel_prvkey']); if ($permissions) { $permissions = json_decode($permissions, true); } logger('decrypted permissions: ' . print_r($permissions, true), LOGGER_DATA); } else { $permissions = $j['permissions']; } if (is_array($permissions) && $permissions) { foreach ($permissions as $k => $v) { set_abconfig($channel['channel_uid'], $xchan_hash, 'their_perms', $k, intval($v)); } } } else { $xchan_hash = ''; $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); if (!$r) { // attempt network auto-discovery $d = discover_by_webbie($url); if (!$d && $is_http) { // try RSS discovery if (get_config('system', 'feed_contacts')) { $d = discover_by_url($url); } else { $result['message'] = t('Protocol disabled.'); return $result; } } if ($d) { $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1", dbesc($url), dbesc($url)); } } // if discovery was a success we should have an xchan record in $r if ($r) { $xchan = $r[0]; $xchan_hash = $r[0]['xchan_hash']; $their_perms = 0; } } if (!$xchan_hash) { $result['message'] = t('Channel discovery failed.'); logger('follow: ' . $result['message']); return $result; } $allowed = $is_red || $r[0]['xchan_network'] === 'rss' ? 1 : 0; $x = array('channel_id' => $uid, 'follow_address' => $url, 'xchan' => $r[0], 'allowed' => $allowed, 'singleton' => 0); call_hooks('follow_allow', $x); if (!$x['allowed']) { $result['message'] = t('Protocol disabled.'); return $result; } $singleton = intval($x['singleton']); $aid = $channel['channel_account_id']; $hash = get_observer_hash(); $default_group = $channel['channel_default_group']; if ($xchan['xchan_network'] === 'rss') { // check service class feed limits $r = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", intval($aid)); if ($r) { $total_feeds = $r[0]['total']; } if (!service_class_allows($uid, 'total_feeds', $total_feeds)) { $result['message'] = upgrade_message(); return $result; } } if ($hash == $xchan_hash) { $result['message'] = t('Cannot connect to yourself.'); return $result; } $r = q("select abook_xchan, abook_instance from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($is_http) { // Always set these "remote" permissions for feeds since we cannot interact with them // to negotiate a suitable permission response set_abconfig($uid, $xchan_hash, 'their_perms', 'view_stream', 1); set_abconfig($uid, $xchan_hash, 'their_perms', 'republish', 1); } if ($r) { $abook_instance = $r[0]['abook_instance']; if ($singleton && strpos($abook_instance, z_root()) === false) { if ($abook_instance) { $abook_instance .= ','; } $abook_instance .= z_root(); } $x = q("update abook set abook_instance = '%s' where abook_id = %d", dbesc($abook_instance), intval($r[0]['abook_id'])); } else { $closeness = get_pconfig($uid, 'system', 'new_abook_closeness'); if ($closeness === false) { $closeness = 80; } $r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_created, abook_updated, abook_instance )\n\t\t\tvalues( %d, %d, %d, '%s', %d, '%s', '%s', '%s' ) ", intval($aid), intval($uid), intval($closeness), dbesc($xchan_hash), intval($is_http ? 1 : 0), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($singleton ? z_root() : '')); } if (!$r) { logger('mod_follow: abook creation failed'); } $all_perms = \Zotlabs\Access\Permissions::Perms(); if ($all_perms) { foreach ($all_perms as $k => $v) { if (in_array($k, $my_perms)) { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 1); } else { set_abconfig($uid, $xchan_hash, 'my_perms', $k, 0); } } } $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash \n\t\twhere abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($xchan_hash), intval($uid)); if ($r) { $result['abook'] = $r[0]; Zotlabs\Daemon\Master::Summon(array('Notifier', 'permission_create', $result['abook']['abook_id'])); } $arr = array('channel_id' => $uid, 'channel' => $channel, 'abook' => $result['abook']); call_hooks('follow', $arr); /** If there is a default group for this channel, add this connection to it */ if ($default_group) { require_once 'include/group.php'; $g = group_rec_byhash($uid, $default_group); if ($g) { group_add_member($uid, '', $xchan_hash, $g['id']); } } $result['success'] = true; return $result; }
/** * poco_load * * xchan is your connection * We will load their friend list, and store in xlink_xchan your connection hash and xlink_link the hash for each connection * If xchan isn't provided we will load the list of people from url who have indicated they are willing to be friends with * new folks and add them to xlink with no xlink_xchan. * * Old behaviour: (documentation only): * Given a contact-id (minimum), load the PortableContacts friend list for that contact, * and add the entries to the gcontact (Global Contact) table, or update existing entries * if anything (name or photo) has changed. * We use normalised urls for comparison which ignore http vs https and www.domain vs domain * * Once the global contact is stored add (if necessary) the contact linkage which associates * the given uid, cid to the global contact entry. There can be many uid/cid combinations * pointing to the same global contact id. * * @param string $xchan * @param string $url */ function poco_load($xchan = '', $url = null) { if ($xchan && !$url) { $r = q("select xchan_connurl from xchan where xchan_hash = '%s' limit 1", dbesc($xchan)); if ($r) { $url = $r[0]['xchan_connurl']; } } if (!$url) { logger('poco_load: no url'); return; } $url = $url . '?f=&fields=displayName,hash,urls,photos'; logger('poco_load: ' . $url, LOGGER_DEBUG); $s = z_fetch_url($url); if (!$s['success']) { if ($s['return_code'] == 401) { logger('poco_load: protected'); } elseif ($s['return_code'] == 404) { logger('poco_load: nothing found'); } else { logger('poco_load: returns ' . print_r($s, true)); } return; } $j = json_decode($s['body'], true); if (!$j) { logger('poco_load: unable to json_decode returned data.'); return; } logger('poco_load: ' . print_r($j, true), LOGGER_DATA); if ($xchan) { if (array_key_exists('chatrooms', $j) && is_array($j['chatrooms'])) { foreach ($j['chatrooms'] as $room) { if (!$room['url'] || !$room['desc']) { continue; } $r = q("select * from xchat where xchat_url = '%s' and xchat_xchan = '%s' limit 1", dbesc($room['url']), dbesc($xchan)); if ($r) { q("update xchat set xchat_edited = '%s' where xchat_id = %d", dbesc(datetime_convert()), intval($r[0]['xchat_id'])); } else { $x = q("insert into xchat ( xchat_url, xchat_desc, xchat_xchan, xchat_edited )\n\t\t\t\t\t\tvalues ( '%s', '%s', '%s', '%s' ) ", dbesc(escape_tags($room['url'])), dbesc(escape_tags($room['desc'])), dbesc($xchan), dbesc(datetime_convert())); } } } q("delete from xchat where xchat_edited < %s - INTERVAL %s and xchat_xchan = '%s' ", db_utcnow(), db_quoteinterval('7 DAY'), dbesc($xchan)); } if (!(x($j, 'entry') && is_array($j['entry']))) { logger('poco_load: no entries'); return; } $total = 0; foreach ($j['entry'] as $entry) { $profile_url = ''; $profile_photo = ''; $address = ''; $name = ''; $hash = ''; $rating = 0; $name = $entry['displayName']; $hash = $entry['hash']; if (x($entry, 'urls') && is_array($entry['urls'])) { foreach ($entry['urls'] as $url) { if ($url['type'] == 'profile') { $profile_url = $url['value']; continue; } if ($url['type'] == 'zot' || $url['type'] == 'diaspora' || $url['type'] == 'friendica') { $network = $url['type']; $address = str_replace('acct:', '', $url['value']); continue; } } } if (x($entry, 'photos') && is_array($entry['photos'])) { foreach ($entry['photos'] as $photo) { if ($photo['type'] == 'profile') { $profile_photo = $photo['value']; continue; } } } if (!$name || !$profile_url || !$profile_photo || !$hash || !$address) { logger('poco_load: missing data'); continue; } $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($hash)); // We've never seen this person before. Import them. if ($x !== false && !count($x)) { if ($address) { if ($network === 'zot') { $j = Zotlabs\Zot\Finger::run($address, null); if ($j['success']) { import_xchan($j); } $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($hash)); if (!$x) { continue; } } else { $x = import_author_diaspora(array('address' => $address)); if (!$x) { continue; } } } else { continue; } } $total++; $r = q("select * from xlink where xlink_xchan = '%s' and xlink_link = '%s' and xlink_static = 0 limit 1", dbesc($xchan), dbesc($hash)); if (!$r) { q("insert into xlink ( xlink_xchan, xlink_link, xlink_updated, xlink_static ) values ( '%s', '%s', '%s', 0 ) ", dbesc($xchan), dbesc($hash), dbesc(datetime_convert())); } else { q("update xlink set xlink_updated = '%s' where xlink_id = %d", dbesc(datetime_convert()), intval($r[0]['xlink_id'])); } } logger("poco_load: loaded {$total} entries", LOGGER_DEBUG); q("delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - INTERVAL %s and xlink_static = 0", dbesc($xchan), db_utcnow(), db_quoteinterval('2 DAY')); }