/** * @brief Create an array representing the important channel information * which would be necessary to create a nomadic identity clone. This includes * most channel resources and connection information with the exception of content. * * @param int $channel_id * Channel_id to export * @param boolean $items * Include channel posts (wall items), default false * * @returns array * See function for details */ function identity_basic_export($channel_id, $items = false) { /* * Red basic channel export */ $ret = array(); $ret['compatibility'] = array('project' => PLATFORM_NAME, 'version' => RED_VERSION, 'database' => DB_UPDATE_VERSION); $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($r) { $ret['channel'] = $r[0]; } $r = q("select * from profile where uid = %d", intval($channel_id)); if ($r) { $ret['profile'] = $r; } $xchans = array(); $r = q("select * from abook where abook_channel = %d ", intval($channel_id)); if ($r) { $ret['abook'] = $r; foreach ($r as $rr) { $xchans[] = $rr['abook_xchan']; } stringify_array_elms($xchans); } if ($xchans) { $r = q("select * from xchan where xchan_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['xchan'] = $r; } $r = q("select * from hubloc where hubloc_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['hubloc'] = $r; } } $r = q("select * from `groups` where uid = %d ", intval($channel_id)); if ($r) { $ret['group'] = $r; } $r = q("select * from group_member where uid = %d ", intval($channel_id)); if ($r) { $ret['group_member'] = $r; } $r = q("select * from pconfig where uid = %d", intval($channel_id)); if ($r) { $ret['config'] = $r; } $r = q("select type, data, os_storage from photo where scale = 4 and profile = 1 and uid = %d limit 1", intval($channel_id)); if ($r) { $ret['photo'] = array('type' => $r[0]['type'], 'data' => $r[0]['os_storage'] ? base64url_encode(file_get_contents($r[0]['data'])) : base64url_encode($r[0]['data'])); } // All other term types will be included in items, if requested. $r = q("select * from term where type in (%d,%d) and uid = %d", intval(TERM_SAVEDSEARCH), intval(TERM_THING), intval($channel_id)); if ($r) { $ret['term'] = $r; } // add psuedo-column obj_baseurl to aid in relocations $r = q("select obj.*, '%s' as obj_baseurl from obj where obj_channel = %d", dbesc(z_root()), intval($channel_id)); if ($r) { $ret['obj'] = $r; } $r = q("select * from app where app_channel = %d", intval($channel_id)); if ($r) { $ret['app'] = $r; } $r = q("select * from chatroom where cr_uid = %d", intval($channel_id)); if ($r) { $ret['chatroom'] = $r; } $r = q("select * from event where uid = %d", intval($channel_id)); if ($r) { $ret['event'] = $r; } $r = q("select * from item where resource_type = 'event' and uid = %d", intval($channel_id)); if ($r) { $ret['event_item'] = array(); xchan_query($r); $r = fetch_post_tags($r, true); foreach ($r as $rr) { $ret['event_item'][] = encode_item($rr, true); } } $x = menu_list($channel_id); if ($x) { $ret['menu'] = array(); for ($y = 0; $y < count($x); $y++) { $m = menu_fetch($x[$y]['menu_name'], $channel_id, $ret['channel']['channel_hash']); if ($m) { $ret['menu'][] = menu_element($m); } } } $x = menu_list($channel_id); if ($x) { $ret['menu'] = array(); for ($y = 0; $y < count($x); $y++) { $m = menu_fetch($x[$y]['menu_name'], $channel_id, $ret['channel']['channel_hash']); if ($m) { $ret['menu'][] = menu_element($m); } } } $addon = array('channel_id' => $channel_id, 'data' => $ret); call_hooks('identity_basic_export', $addon); $ret = $addon['data']; if (!$items) { return $ret; } $r = q("select * from likes where channel_id = %d", intval($channel_id)); if ($r) { $ret['likes'] = $r; } $r = q("select * from conv where uid = %d", intval($channel_id)); if ($r) { for ($x = 0; $x < count($r); $x++) { $r[$x]['subject'] = base64url_decode(str_rot47($r[$x]['subject'])); } $ret['conv'] = $r; } $r = q("select * from mail where mail.uid = %d", intval($channel_id)); if ($r) { $m = array(); foreach ($r as $rr) { xchan_mail_query($rr); $m[] = mail_encode($rr, true); } $ret['mail'] = $m; } $r = q("select item_id.*, item.mid from item_id left join item on item_id.iid = item.id where item_id.uid = %d", intval($channel_id)); if ($r) { $ret['item_id'] = $r; } //$key = get_config('system','prvkey'); /** @warning this may run into memory limits on smaller systems */ /** export three months of posts. If you want to export and import all posts you have to start with * the first year and export/import them in ascending order. * * Don't export linked resource items. we'll have to pull those out separately. */ $r = q("select * from item where item_wall = 1 and item_deleted = 0 and uid = %d and created > %s - INTERVAL %s and resource_type = '' order by created", intval($channel_id), db_utcnow(), db_quoteinterval('3 MONTH')); if ($r) { $ret['item'] = array(); xchan_query($r); $r = fetch_post_tags($r, true); foreach ($r as $rr) { $ret['item'][] = encode_item($rr, true); } } return $ret; }
/** * @brief Process incoming array of messages. * * Process an incoming array of messages which were obtained via pickup, and * import, update, delete as directed. * * The message types handled here are 'activity' (e.g. posts), 'mail' , * 'profile', 'location' and 'channel_sync'. * * @param array $arr * 'pickup' structure returned from remote site * @param string $sender_url * the url specified by the sender in the initial communication. * We will verify the sender and url in each returned message structure and * also verify that all the messages returned match the site url that we are * currently processing. * * @returns array * suitable for logging remotely, enumerating the processing results of each message/recipient combination * * [0] => \e string $channel_hash * * [1] => \e string $delivery_status * * [2] => \e string $address */ function zot_import($arr, $sender_url) { $data = json_decode($arr['body'], true); if (!$data) { logger('zot_import: empty body'); return array(); } if (array_key_exists('iv', $data)) { $data = json_decode(crypto_unencapsulate($data, get_config('system', 'prvkey')), true); } if (!$data['success']) { if ($data['message']) { logger('remote pickup failed: ' . $data['message']); } return false; } $incoming = $data['pickup']; $return = array(); if (is_array($incoming)) { foreach ($incoming as $i) { if (!is_array($i)) { logger('incoming is not an array'); continue; } $result = null; if (array_key_exists('iv', $i['notify'])) { $i['notify'] = json_decode(crypto_unencapsulate($i['notify'], get_config('system', 'prvkey')), true); } logger('zot_import: notify: ' . print_r($i['notify'], true), LOGGER_DATA); $hub = zot_gethub($i['notify']['sender']); if (!$hub || $hub['hubloc_url'] != $sender_url) { logger('zot_import: potential forgery: wrong site for sender: ' . $sender_url . ' != ' . print_r($i['notify'], true)); continue; } $message_request = array_key_exists('message_id', $i['notify']) ? true : false; if ($message_request) { logger('processing message request'); } $i['notify']['sender']['hash'] = make_xchan_hash($i['notify']['sender']['guid'], $i['notify']['sender']['guid_sig']); $deliveries = null; if (array_key_exists('message', $i) && array_key_exists('type', $i['message']) && $i['message']['type'] === 'rating') { // rating messages are processed only by directory servers logger('Rating received: ' . print_r($arr, true), LOGGER_DATA); $result = process_rating_delivery($i['notify']['sender'], $i['message']); continue; } if (array_key_exists('recipients', $i['notify']) && count($i['notify']['recipients'])) { logger('specific recipients'); $recip_arr = array(); foreach ($i['notify']['recipients'] as $recip) { if (is_array($recip)) { $recip_arr[] = make_xchan_hash($recip['guid'], $recip['guid_sig']); } } $r = false; if ($recip_arr) { stringify_array_elms($recip_arr); $recips = implode(',', $recip_arr); $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) \n\t\t\t\t\t\tand channel_removed = 0 "); } if (!$r) { logger('recips: no recipients on this site'); continue; } // It's a specifically targetted post. If we were sent a public_scope hint (likely), // get rid of it so that it doesn't get stored and cause trouble. if ($i && is_array($i) && array_key_exists('message', $i) && is_array($i['message']) && $i['message']['type'] === 'activity' && array_key_exists('public_scope', $i['message'])) { unset($i['message']['public_scope']); } $deliveries = $r; // We found somebody on this site that's in the recipient list. } else { if ($i['message'] && array_key_exists('flags', $i['message']) && in_array('private', $i['message']['flags']) && $i['message']['type'] === 'activity') { if (array_key_exists('public_scope', $i['message']) && $i['message']['public_scope'] === 'public') { // This should not happen but until we can stop it... logger('private message was delivered with no recipients.'); continue; } } logger('public post'); // Public post. look for any site members who are or may be accepting posts from this sender // and who are allowed to see them based on the sender's permissions $deliveries = allowed_public_recips($i); if ($i['message'] && array_key_exists('type', $i['message']) && $i['message']['type'] === 'location') { $sys = get_sys_channel(); $deliveries = array(array('hash' => $sys['xchan_hash'])); } // if the scope is anything but 'public' we're going to store it as private regardless // of the private flag on the post. if ($i['message'] && array_key_exists('public_scope', $i['message']) && $i['message']['public_scope'] !== 'public') { if (!array_key_exists('flags', $i['message'])) { $i['message']['flags'] = array(); } if (!in_array('private', $i['message']['flags'])) { $i['message']['flags'][] = 'private'; } } } // Go through the hash array and remove duplicates. array_unique() won't do this because the array is more than one level. $no_dups = array(); if ($deliveries) { foreach ($deliveries as $d) { if (!in_array($d['hash'], $no_dups)) { $no_dups[] = $d['hash']; } } if ($no_dups) { $deliveries = array(); foreach ($no_dups as $n) { $deliveries[] = array('hash' => $n); } } } if (!$deliveries) { logger('zot_import: no deliveries on this site'); continue; } if ($i['message']) { if ($i['message']['type'] === 'activity') { $arr = get_item_elements($i['message']); $v = validate_item_elements($i['message'], $arr); if (!$v['success']) { logger('Activity rejected: ' . $v['message'] . ' ' . print_r($i['message'], true)); continue; } logger('Activity received: ' . print_r($arr, true), LOGGER_DATA); logger('Activity recipients: ' . print_r($deliveries, true), LOGGER_DATA); $relay = array_key_exists('flags', $i['message']) && in_array('relay', $i['message']['flags']) ? true : false; $result = process_delivery($i['notify']['sender'], $arr, $deliveries, $relay, false, $message_request); } elseif ($i['message']['type'] === 'mail') { $arr = get_mail_elements($i['message']); logger('Mail received: ' . print_r($arr, true), LOGGER_DATA); logger('Mail recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_mail_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'profile') { $arr = get_profile_elements($i['message']); logger('Profile received: ' . print_r($arr, true), LOGGER_DATA); logger('Profile recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_profile_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'channel_sync') { // $arr = get_channelsync_elements($i['message']); $arr = $i['message']; logger('Channel sync received: ' . print_r($arr, true), LOGGER_DATA); logger('Channel sync recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_channel_sync_delivery($i['notify']['sender'], $arr, $deliveries); } elseif ($i['message']['type'] === 'location') { $arr = $i['message']; logger('Location message received: ' . print_r($arr, true), LOGGER_DATA); logger('Location message recipients: ' . print_r($deliveries, true), LOGGER_DATA); $result = process_location_delivery($i['notify']['sender'], $arr, $deliveries); } } if ($result) { $return = array_merge($return, $result); } } } return $return; }
function lockview_content(&$a) { $type = argc() > 1 ? argv(1) : 0; if (is_numeric($type)) { $item_id = intval($type); $type = 'item'; } else { $item_id = argc() > 2 ? intval(argv(2)) : 0; } if (!$item_id) { killme(); } if (!in_array($type, array('item', 'photo', 'event'))) { killme(); } $r = q("SELECT * FROM %s WHERE id = %d LIMIT 1", dbesc($type), intval($item_id)); if (!$r) { killme(); } $item = $r[0]; if ($item['uid'] != local_user()) { echo '<li>' . t('Remote privacy information not available.') . '</li>'; killme(); } if ($item['item_private'] == 1 && !strlen($item['allow_cid']) && !strlen($item['allow_gid']) && !strlen($item['deny_cid']) && !strlen($item['deny_gid'])) { // if the post is private, but public_policy is blank ("visible to the internet"), and there aren't any // specific recipients, we're the recipient of a post with "bcc" or targeted recipients; so we'll just show it // as unknown specific recipients. The sender will have the visibility list and will fall through to the // next section. echo '<li>' . translate_scope(!$item['public_policy'] ? 'specific' : $item['public_policy']) . '</li>'; killme(); } $allowed_users = expand_acl($item['allow_cid']); $allowed_groups = expand_acl($item['allow_gid']); $deny_users = expand_acl($item['deny_cid']); $deny_groups = expand_acl($item['deny_gid']); $o = '<li>' . t('Visible to:') . '</li>'; $l = array(); stringify_array_elms($allowed_groups, true); stringify_array_elms($allowed_users, true); stringify_array_elms($deny_groups, true); stringify_array_elms($deny_users, true); if (count($allowed_groups)) { $r = q("SELECT name FROM `groups` WHERE hash IN ( " . implode(', ', $allowed_groups) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><b>' . $rr['name'] . '</b></li>'; } } } if (count($allowed_users)) { $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ', $allowed_users) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li>' . $rr['xchan_name'] . '</li>'; } } } if (count($deny_groups)) { $r = q("SELECT name FROM `groups` WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><b><strike>' . $rr['name'] . '</strike></b></li>'; } } } if (count($deny_users)) { $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ', $deny_users) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><strike>' . $rr['xchan_name'] . '</strike></li>'; } } } echo $o . implode($l); killme(); }
/** * @brief Create an array representing the important channel information * which would be necessary to create a nomadic identity clone. This includes * most channel resources and connection information with the exception of content. * * @param int $channel_id * Channel_id to export * @param boolean $items * Include channel posts (wall items), default false * * @returns array * See function for details */ function identity_basic_export($channel_id, $items = false) { /* * Red basic channel export */ $ret = array(); $ret['compatibility'] = array('project' => RED_PLATFORM, 'version' => RED_VERSION, 'database' => DB_UPDATE_VERSION); $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($r) { $ret['channel'] = $r[0]; } $r = q("select * from profile where uid = %d", intval($channel_id)); if ($r) { $ret['profile'] = $r; } $xchans = array(); $r = q("select * from abook where abook_channel = %d ", intval($channel_id)); if ($r) { $ret['abook'] = $r; foreach ($r as $rr) { $xchans[] = $rr['abook_xchan']; } stringify_array_elms($xchans); } if ($xchans) { $r = q("select * from xchan where xchan_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['xchan'] = $r; } $r = q("select * from hubloc where hubloc_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['hubloc'] = $r; } } $r = q("select * from `groups` where uid = %d ", intval($channel_id)); if ($r) { $ret['group'] = $r; } $r = q("select * from group_member where uid = %d ", intval($channel_id)); if ($r) { $ret['group_member'] = $r; } $r = q("select * from pconfig where uid = %d", intval($channel_id)); if ($r) { $ret['config'] = $r; } $r = q("select type, data from photo where scale = 4 and profile = 1 and uid = %d limit 1", intval($channel_id)); if ($r) { $ret['photo'] = array('type' => $r[0]['type'], 'data' => base64url_encode($r[0]['data'])); } // All other term types will be included in items, if requested. $r = q("select * from term where type in (%d,%d) and uid = %d", intval(TERM_SAVEDSEARCH), intval(TERM_THING), intval($channel_id)); if ($r) { $ret['term'] = $r; } $r = q("select * from obj where obj_channel = %d", intval($channel_id)); if ($r) { $ret['obj'] = $r; } if (!$items) { return $ret; } $r = q("select likes.*, item.mid from likes left join item on likes.iid = item.id where likes.channel_id = %d", intval($channel_id)); if ($r) { $ret['likes'] = $r; } $r = q("select item_id.*, item.mid from item_id left join item on item_id.iid = item.id where item_id.uid = %d", intval($channel_id)); if ($r) { $ret['item_id'] = $r; } //$key = get_config('system','prvkey'); /** @warning this may run into memory limits on smaller systems */ $r = q("select * from item where (item_flags & %d)>0 and not (item_restrict & %d)>0 and uid = %d", intval(ITEM_WALL), intval(ITEM_DELETED), intval($channel_id)); if ($r) { $ret['item'] = array(); xchan_query($r); $r = fetch_post_tags($r, true); foreach ($r as $rr) { $ret['item'][] = encode_item($rr, true); } } return $ret; }
function expand_groups($a) { if (!(is_array($a) && count($a))) { return array(); } $x = $a; stringify_array_elms($x, true); $groups = implode(',', $x); if ($groups) { $r = q("SELECT xchan FROM group_member WHERE gid IN ( select id from `groups` where hash in ( {$groups} ))"); } $ret = array(); if ($r) { foreach ($r as $rr) { $ret[] = $rr['xchan']; } } return $ret; }
function get_things($profile_hash, $uid) { $sql_extra = $profile_hash ? " and obj_page = '" . $profile_hash . "' " : ''; $r = q("select * from obj where obj_channel = %d and obj_type = %d {$sql_extra} order by obj_verb, obj_term", intval($uid), intval(TERM_OBJ_THING)); $things = $sorted_things = null; $profile_hashes = array(); if ($r) { // if no profile_hash was specified (display on profile page mode), match each of the things to a profile name // (list all my things mode). This is harder than it sounds. foreach ($r as $rr) { $rr['profile_name'] = ''; if (!in_array($rr['obj_obj'], $profile_hashes)) { $profile_hashes[] = $rr['obj_obj']; } } stringify_array_elms($profile_hashes); if (!$profile_hash) { $exp = explode(',', $profile_hashes); $p = q("select profile_guid as hash, profile_name as name from profile where profile_guid in ( {$exp} ) "); if ($p) { foreach ($r as $rr) { foreach ($p as $pp) { if ($rr['obj_page'] == $pp['hash']) { $rr['profile_name'] == $pp['name']; } } } } } $things = array(); // Use the system obj_verbs array as a sort key, since we don't really // want an alphabetic sort. To change the order, use a plugin to // alter the obj_verbs() array or alter it in code. Unknown verbs come // after the known ones - in no particular order. $v = obj_verbs(); foreach ($v as $k => $foo) { $things[$k] = null; } foreach ($r as $rr) { $l = q("select xchan_name, xchan_url from likes left join xchan on likee = xchan_hash where\n\t\t\t\ttarget_type = '%s' and target_id = '%s' and channel_id = %d", dbesc(ACTIVITY_OBJ_THING), dbesc($rr['obj_obj']), intval($uid)); for ($x = 0; $x < count($l); $x++) { $l[$x]['xchan_url'] = zid($l[$x]['xchan_url']); } if (!$things[$rr['obj_verb']]) { $things[$rr['obj_verb']] = array(); } $things[$rr['obj_verb']][] = array('term' => $rr['obj_term'], 'url' => $rr['obj_url'], 'img' => $rr['obj_imgurl'], 'profile' => $rr['profile_name'], 'term_hash' => $rr['obj_obj'], 'likes' => $l, 'like_count' => count($l), 'like_label' => tt('Like', 'Likes', count($l), 'noun')); } $sorted_things = array(); if ($things) { foreach ($things as $k => $v) { if (is_array($things[$k])) { $sorted_things[$k] = $v; } } } } //logger('things: ' . print_r($sorted_things,true)); return $sorted_things; }
function notifier_run($argv, $argc) { cli_startup(); $a = get_app(); if ($argc < 3) { return; } logger('notifier: invoked: ' . print_r($argv, true), LOGGER_DEBUG); $cmd = $argv[1]; $item_id = $argv[2]; $extra = $argc > 3 ? $argv[3] : null; if (!$item_id) { return; } $sys = get_sys_channel(); $deliveries = array(); $dead_hubs = array(); $dh = q("select site_url from site where site_dead = 1"); if ($dh) { foreach ($dh as $dead) { $dead_hubs[] = $dead['site_url']; } } $request = false; $mail = false; $top_level = false; $location = false; $recipients = array(); $url_recipients = array(); $normal_mode = true; $packet_type = 'undefined'; if ($cmd === 'mail') { $normal_mode = false; $mail = true; $private = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!$message) { return; } xchan_mail_query($message[0]); $uid = $message[0]['channel_id']; $recipients[] = $message[0]['from_xchan']; // include clones $recipients[] = $message[0]['to_xchan']; $item = $message[0]; $encoded_item = encode_mail($item); $s = q("select * from channel where channel_id = %d limit 1", intval($item['channel_id'])); if ($s) { $channel = $s[0]; } } elseif ($cmd === 'request') { $channel_id = $item_id; $xchan = $argv[3]; $request_message_id = $argv[4]; $s = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($s) { $channel = $s[0]; } $private = true; $recipients[] = $xchan; $packet_type = 'request'; $normal_mode = false; } elseif ($cmd == 'permission_update' || $cmd == 'permission_create') { // Get the (single) recipient $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d and abook_self = 0", intval($item_id)); if ($r) { $uid = $r[0]['abook_channel']; // Get the sender $channel = channelx_by_n($uid); if ($channel) { $perm_update = array('sender' => $channel, 'recipient' => $r[0], 'success' => false, 'deliveries' => ''); if ($cmd == 'permission_create') { call_hooks('permissions_create', $perm_update); } else { call_hooks('permissions_update', $perm_update); } if ($perm_update['success']) { if ($perm_update['deliveries']) { $deliveries[] = $perm_update['deliveries']; do_delivery($deliveries); } return; } else { $recipients[] = $r[0]['abook_xchan']; $private = false; $packet_type = 'refresh'; $packet_recips = array(array('guid' => $r[0]['xchan_guid'], 'guid_sig' => $r[0]['xchan_guid_sig'], 'hash' => $r[0]['xchan_hash'])); } } } } elseif ($cmd === 'refresh_all') { logger('notifier: refresh_all: ' . $item_id); $uid = $item_id; $channel = channelx_by_n($item_id); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $private = false; $packet_type = 'refresh'; } elseif ($cmd === 'location') { logger('notifier: location: ' . $item_id); $s = q("select * from channel where channel_id = %d limit 1", intval($item_id)); if ($s) { $channel = $s[0]; } $uid = $item_id; $recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $encoded_item = array('locations' => zot_encode_locations($channel), 'type' => 'location', 'encoding' => 'zot'); $target_item = array('aid' => $channel['channel_account_id'], 'uid' => $channel['channel_id']); $private = false; $packet_type = 'location'; $location = true; } elseif ($cmd === 'purge_all') { logger('notifier: purge_all: ' . $item_id); $s = q("select * from channel where channel_id = %d limit 1", intval($item_id)); if ($s) { $channel = $s[0]; } $uid = $item_id; $recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $private = false; $packet_type = 'purge'; } else { // Normal items // Fetch the target item $r = q("SELECT * FROM item WHERE id = %d and parent != 0 LIMIT 1", intval($item_id)); if (!$r) { return; } xchan_query($r); $r = fetch_post_tags($r); $target_item = $r[0]; $deleted_item = false; if (intval($target_item['item_deleted'])) { logger('notifier: target item ITEM_DELETED', LOGGER_DEBUG); $deleted_item = true; } if (intval($target_item['item_type']) != ITEM_TYPE_POST) { logger('notifier: target item not forwardable: type ' . $target_item['item_type'], LOGGER_DEBUG); return; } if (intval($target_item['item_unpublished']) || intval($target_item['item_delayed'])) { logger('notifier: target item not published, so not forwardable', LOGGER_DEBUG); return; } if (strpos($target_item['postopts'], 'nodeliver') !== false) { logger('notifier: target item is undeliverable', LOGGER_DEBUG); return; } $s = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", intval($target_item['uid'])); if ($s) { $channel = $s[0]; } if ($channel['channel_hash'] !== $target_item['author_xchan'] && $channel['channel_hash'] !== $target_item['owner_xchan']) { logger("notifier: Sending channel {$channel['channel_hash']} is not owner {$target_item['owner_xchan']} or author {$target_item['author_xchan']}", LOGGER_NORMAL, LOG_WARNING); return; } if ($target_item['id'] == $target_item['parent']) { $parent_item = $target_item; $top_level_post = true; } else { // fetch the parent item $r = q("SELECT * from item where id = %d order by id asc", intval($target_item['parent'])); if (!$r) { return; } if (strpos($r[0]['postopts'], 'nodeliver') !== false) { logger('notifier: target item is undeliverable', LOGGER_DEBUG, LOG_NOTICE); return; } xchan_query($r); $r = fetch_post_tags($r); $parent_item = $r[0]; $top_level_post = false; } // avoid looping of discover items 12/4/2014 if ($sys && $parent_item['uid'] == $sys['channel_id']) { return; } $encoded_item = encode_item($target_item); // Send comments to the owner to re-deliver to everybody in the conversation // We only do this if the item in question originated on this site. This prevents looping. // To clarify, a site accepting a new comment is responsible for sending it to the owner for relay. // Relaying should never be initiated on a post that arrived from elsewhere. // We should normally be able to rely on ITEM_ORIGIN, but start_delivery_chain() incorrectly set this // flag on comments for an extended period. So we'll also call comment_local_origin() which looks at // the hostname in the message_id and provides a second (fallback) opinion. $relay_to_owner = !$top_level_post && intval($target_item['item_origin']) && comment_local_origin($target_item) ? true : false; $uplink = false; // $cmd === 'relay' indicates the owner is sending it to the original recipients // don't allow the item in the relay command to relay to owner under any circumstances, it will loop logger('notifier: relay_to_owner: ' . ($relay_to_owner ? 'true' : 'false'), LOGGER_DATA, LOG_DEBUG); logger('notifier: top_level_post: ' . ($top_level_post ? 'true' : 'false'), LOGGER_DATA, LOG_DEBUG); // tag_deliver'd post which needs to be sent back to the original author if ($cmd === 'uplink' && intval($parent_item['item_uplink']) && !$top_level_post) { logger('notifier: uplink'); $uplink = true; } if (($relay_to_owner || $uplink) && $cmd !== 'relay') { logger('notifier: followup relay', LOGGER_DEBUG); $recipients = array($uplink ? $parent_item['source_xchan'] : $parent_item['owner_xchan']); $private = true; if (!$encoded_item['flags']) { $encoded_item['flags'] = array(); } $encoded_item['flags'][] = 'relay'; } else { logger('notifier: normal distribution', LOGGER_DEBUG); if ($cmd === 'relay') { logger('notifier: owner relay'); } // if our parent is a tag_delivery recipient, uplink to the original author causing // a delivery fork. if ($parent_item && intval($parent_item['item_uplink']) && !$top_level_post && $cmd !== 'uplink') { // don't uplink a relayed post to the relay owner if ($parent_item['source_xchan'] !== $parent_item['owner_xchan']) { logger('notifier: uplinking this item'); proc_run('php', 'include/notifier.php', 'uplink', $item_id); } } $private = false; $recipients = collect_recipients($parent_item, $private); // FIXME add any additional recipients such as mentions, etc. // don't send deletions onward for other people's stuff // TODO verify this is needed - copied logic from same place in old code if (intval($target_item['item_deleted']) && !intval($target_item['item_wall'])) { logger('notifier: ignoring delete notification for non-wall item', LOGGER_NORMAL, LOG_NOTICE); return; } } } $walltowall = $top_level_post && $channel['xchan_hash'] === $target_item['author_xchan'] ? true : false; // Generic delivery section, we have an encoded item and recipients // Now start the delivery process $x = $encoded_item; $x['title'] = 'private'; $x['body'] = 'private'; logger('notifier: encoded item: ' . print_r($x, true), LOGGER_DATA, LOG_DEBUG); stringify_array_elms($recipients); if (!$recipients) { return; } // logger('notifier: recipients: ' . print_r($recipients,true), LOGGER_NORMAL, LOG_DEBUG); $env_recips = $private ? array() : null; $details = q("select xchan_hash, xchan_instance_url, xchan_network, xchan_addr, xchan_guid, xchan_guid_sig from xchan where xchan_hash in (" . implode(',', $recipients) . ")"); $recip_list = array(); if ($details) { foreach ($details as $d) { $recip_list[] = $d['xchan_addr'] . ' (' . $d['xchan_hash'] . ')'; if ($private) { $env_recips[] = array('guid' => $d['xchan_guid'], 'guid_sig' => $d['xchan_guid_sig'], 'hash' => $d['xchan_hash']); } if ($d['xchan_network'] === 'mail' && $normal_mode) { $delivery_options = get_xconfig($d['xchan_hash'], 'system', 'delivery_mode'); if (!$delivery_options) { format_and_send_email($channel, $d, $target_item); } } } } $narr = array('channel' => $channel, 'env_recips' => $env_recips, 'packet_recips' => $packet_recips, 'recipients' => $recipients, 'item' => $item, 'target_item' => $target_item, 'top_level_post' => $top_level_post, 'private' => $private, 'followup' => $followup, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'mail' => $mail, 'location' => $location, 'request' => $request, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall, 'queued' => array()); call_hooks('notifier_process', $narr); if ($narr['queued']) { foreach ($narr['queued'] as $pq) { $deliveries[] = $pq; } } if ($private && !$env_recips) { // shouldn't happen logger('notifier: private message with no envelope recipients.' . print_r($argv, true), LOGGER_NORMAL, LOG_NOTICE); } logger('notifier: recipients (may be delivered to more if public): ' . print_r($recip_list, true), LOGGER_DEBUG); // Now we have collected recipients (except for external mentions, FIXME) // Let's reduce this to a set of hubs. $r = q("select * from hubloc where hubloc_hash in (" . implode(',', $recipients) . ") \n\t\tand hubloc_error = 0 and hubloc_deleted = 0"); if (!$r) { logger('notifier: no hubs', LOGGER_NORMAL, LOG_NOTICE); return; } $hubs = $r; /** * Reduce the hubs to those that are unique. For zot hubs, we need to verify uniqueness by the sitekey, since it may have been * a re-install which has not yet been detected and pruned. * For other networks which don't have or require sitekeys, we'll have to use the URL */ $hublist = array(); // this provides an easily printable list for the logs $dhubs = array(); // delivery hubs where we store our resulting unique array $keys = array(); // array of keys to check uniquness for zot hubs $urls = array(); // array of urls to check uniqueness of hubs from other networks foreach ($hubs as $hub) { if (in_array($hub['hubloc_url'], $dead_hubs)) { logger('skipping dead hub: ' . $hub['hubloc_url'], LOGGER_DEBUG, LOG_INFO); continue; } if ($hub['hubloc_network'] == 'zot') { if (!in_array($hub['hubloc_sitekey'], $keys)) { $hublist[] = $hub['hubloc_host']; $dhubs[] = $hub; $keys[] = $hub['hubloc_sitekey']; } } else { if (!in_array($hub['hubloc_url'], $urls)) { $hublist[] = $hub['hubloc_host']; $dhubs[] = $hub; $urls[] = $hub['hubloc_url']; } } } logger('notifier: will notify/deliver to these hubs: ' . print_r($hublist, true), LOGGER_DEBUG, LOG_DEBUG); foreach ($dhubs as $hub) { if ($hub['hubloc_network'] !== 'zot') { $narr = array('channel' => $channel, 'env_recips' => $env_recips, 'packet_recips' => $packet_recips, 'recipients' => $recipients, 'item' => $item, 'target_item' => $target_item, 'hub' => $hub, 'top_level_post' => $top_level_post, 'private' => $private, 'followup' => $followup, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'mail' => $mail, 'location' => $location, 'request' => $request, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall, 'queued' => array()); call_hooks('notifier_hub', $narr); if ($narr['queued']) { foreach ($narr['queued'] as $pq) { $deliveries[] = $pq; } } continue; } // default: zot protocol $hash = random_string(); $packet = null; if ($packet_type === 'refresh' || $packet_type === 'purge') { $packet = zot_build_packet($channel, $packet_type, $packet_recips ? $packet_recips : null); } elseif ($packet_type === 'request') { $packet = zot_build_packet($channel, $packet_type, $env_recips, $hub['hubloc_sitekey'], $hash, array('message_id' => $request_message_id)); } if ($packet) { queue_insert(array('hash' => $hash, 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], 'posturl' => $hub['hubloc_callback'], 'notify' => $packet)); } else { $packet = zot_build_packet($channel, 'notify', $env_recips, $private ? $hub['hubloc_sitekey'] : null, $hash); queue_insert(array('hash' => $hash, 'account_id' => $target_item['aid'], 'channel_id' => $target_item['uid'], 'posturl' => $hub['hubloc_callback'], 'notify' => $packet, 'msg' => json_encode($encoded_item))); // only create delivery reports for normal undeleted items if (is_array($target_item) && array_key_exists('postopts', $target_item) && !$target_item['item_deleted'] && !get_config('system', 'disable_dreport')) { q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan, dreport_queue ) values ( '%s','%s','%s','%s','%s','%s','%s' ) ", dbesc($target_item['mid']), dbesc($hub['hubloc_host']), dbesc($hub['hubloc_host']), dbesc('queued'), dbesc(datetime_convert()), dbesc($channel['channel_hash']), dbesc($hash)); } } $deliveries[] = $hash; } if ($normal_mode) { $x = q("select * from hook where hook = 'notifier_normal'"); if ($x) { proc_run('php', 'include/deliver_hooks.php', $target_item['id']); } } if ($deliveries) { do_delivery($deliveries); } logger('notifier: basic loop complete.', LOGGER_DEBUG); call_hooks('notifier_end', $target_item); logger('notifer: complete.'); return; }
function notifier_run($argv, $argc) { cli_startup(); $a = get_app(); require_once "session.php"; require_once "datetime.php"; require_once 'include/items.php'; require_once 'include/bbcode.php'; if ($argc < 3) { return; } logger('notifier: invoked: ' . print_r($argv, true), LOGGER_DEBUG); $cmd = $argv[1]; $item_id = $argv[2]; $extra = $argc > 3 ? $argv[3] : null; if (!$item_id) { return; } require_once 'include/identity.php'; $sys = get_sys_channel(); if ($cmd == 'permission_update') { // Get the recipient $r = q("select abook.*, hubloc.* from abook \n\t\t\tleft join hubloc on hubloc_hash = abook_xchan\n\t\t\twhere abook_id = %d and not ( abook_flags & %d ) > 0 \n\t\t\tand not (hubloc_flags & %d) > 0 and not (hubloc_status & %d) > 0 limit 1", intval($item_id), intval(ABOOK_FLAG_SELF), intval(HUBLOC_FLAGS_DELETED), intval(HUBLOC_OFFLINE)); if ($r) { // Get the sender $s = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", intval($r[0]['abook_channel'])); if ($s) { if ($r[0]['hubloc_network'] === 'diaspora' || $r[0]['hubloc_network'] === 'friendica-over-diaspora') { require_once 'include/diaspora.php'; diaspora_share($s[0], $r[0]); } else { // send a refresh message to each hub they have registered here $h = q("select * from hubloc where hubloc_hash = '%s' \n\t\t\t\t\t\tand not (hubloc_flags & %d) > 0 and not (hubloc_status & %d) > 0", dbesc($r[0]['hubloc_hash']), intval(HUBLOC_FLAGS_DELETED), intval(HUBLOC_OFFLINE)); if ($h) { foreach ($h as $hh) { $data = zot_build_packet($s[0], 'refresh', array(array('guid' => $hh['hubloc_guid'], 'guid_sig' => $hh['hubloc_guid_sig'], 'url' => $hh['hubloc_url']))); if ($data) { $result = zot_zot($hh['hubloc_callback'], $data); // if immediate delivery failed, stick it in the queue to try again later. if (!$result['success']) { $hash = random_string(); q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) \n\t\t\t\t\t\t\t\t\t\tvalues ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", dbesc($hash), intval($s[0]['channel_account_id']), intval($s[0]['channel_id']), dbesc('zot'), dbesc($hh['hubloc_callback']), intval(1), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($data), dbesc('')); } } } } } } } return; } $expire = false; $request = false; $mail = false; $fsuggest = false; $top_level = false; $location = false; $recipients = array(); $url_recipients = array(); $normal_mode = true; $packet_type = 'undefined'; if ($cmd === 'mail') { $normal_mode = false; $mail = true; $private = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!$message) { return; } xchan_mail_query($message[0]); $uid = $message[0]['channel_id']; $recipients[] = $message[0]['from_xchan']; // include clones $recipients[] = $message[0]['to_xchan']; $item = $message[0]; $encoded_item = encode_mail($item); $s = q("select * from channel where channel_id = %d limit 1", intval($item['channel_id'])); if ($s) { $channel = $s[0]; } } elseif ($cmd === 'request') { $channel_id = $item_id; $xchan = $argv[3]; $request_message_id = $argv[4]; $s = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($s) { $channel = $s[0]; } $private = true; $recipients[] = $xchan; $packet_type = 'request'; $normal_mode = false; } elseif ($cmd === 'expire') { // FIXME // This will require a special zot packet containing a list of item message_id's to be expired. // This packet will be public, since we cannot selectively deliver here. // We need the handling on this end to create the array, and the handling on the remote end // to verify permissions (for each item) and process it. Until this is complete, the expire feature will be disabled. return; $normal_mode = false; $expire = true; $items = q("SELECT * FROM item WHERE uid = %d AND ( item_flags & %d )>0\n\t\t\tAND ( item_restrict & %d )>0 AND `changed` > %s - INTERVAL %s", intval($item_id), intval(ITEM_WALL), intval(ITEM_DELETED), db_utcnow(), db_quoteinterval('10 MINUTE')); $uid = $item_id; $item_id = 0; if (!$items) { return; } } elseif ($cmd === 'suggest') { $normal_mode = false; $fsuggest = true; $suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!count($suggest)) { return; } $uid = $suggest[0]['uid']; $recipients[] = $suggest[0]['cid']; $item = $suggest[0]; } elseif ($cmd === 'refresh_all') { logger('notifier: refresh_all: ' . $item_id); $s = q("select * from channel where channel_id = %d limit 1", intval($item_id)); if ($s) { $channel = $s[0]; } $uid = $item_id; $recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $private = false; $packet_type = 'refresh'; } elseif ($cmd === 'location') { logger('notifier: location: ' . $item_id); $s = q("select * from channel where channel_id = %d limit 1", intval($item_id)); if ($s) { $channel = $s[0]; } $uid = $item_id; $recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $encoded_item = array('locations' => zot_encode_locations($channel), 'type' => 'location', 'encoding' => 'zot'); $target_item = array('aid' => $channel['channel_account_id'], 'uid' => $channel['channel_id']); $private = false; $packet_type = 'location'; $location = true; } elseif ($cmd === 'purge_all') { logger('notifier: purge_all: ' . $item_id); $s = q("select * from channel where channel_id = %d limit 1", intval($item_id)); if ($s) { $channel = $s[0]; } $uid = $item_id; $recipients = array(); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id)); if ($r) { foreach ($r as $rr) { $recipients[] = $rr['abook_xchan']; } } $private = false; $packet_type = 'purge'; } else { // Normal items // Fetch the target item $r = q("SELECT * FROM item WHERE id = %d and parent != 0 LIMIT 1", intval($item_id)); if (!$r) { return; } xchan_query($r); $r = fetch_post_tags($r); $target_item = $r[0]; $deleted_item = false; if ($target_item['item_restrict'] & ITEM_DELETED) { logger('notifier: target item ITEM_DELETED', LOGGER_DEBUG); $deleted_item = true; } $unforwardable = ITEM_UNPUBLISHED | ITEM_DELAYED_PUBLISH | ITEM_WEBPAGE | ITEM_BUILDBLOCK | ITEM_PDL; if ($target_item['item_restrict'] & $unforwardable) { logger('notifier: target item not forwardable: flags ' . $target_item['item_restrict'], LOGGER_DEBUG); return; } $s = q("select * from channel where channel_id = %d limit 1", intval($target_item['uid'])); if ($s) { $channel = $s[0]; } if ($channel['channel_hash'] !== $target_item['author_xchan'] && $channel['channel_hash'] !== $target_item['owner_xchan']) { logger("notifier: Sending channel {$channel['channel_hash']} is not owner {$target_item['owner_xchan']} or author {$target_item['author_xchan']}"); return; } if ($target_item['id'] == $target_item['parent']) { $parent_item = $target_item; $top_level_post = true; } else { // fetch the parent item $r = q("SELECT * from item where id = %d order by id asc", intval($target_item['parent'])); if (!$r) { return; } xchan_query($r); $r = fetch_post_tags($r); $parent_item = $r[0]; $top_level_post = false; } // avoid looping of discover items 12/4/2014 if ($sys && $parent_item['uid'] == $sys['channel_id']) { return; } $encoded_item = encode_item($target_item); // Send comments to the owner to re-deliver to everybody in the conversation // We only do this if the item in question originated on this site. This prevents looping. // To clarify, a site accepting a new comment is responsible for sending it to the owner for relay. // Relaying should never be initiated on a post that arrived from elsewhere. // We should normally be able to rely on ITEM_ORIGIN, but start_delivery_chain() incorrectly set this // flag on comments for an extended period. So we'll also call comment_local_origin() which looks at // the hostname in the message_id and provides a second (fallback) opinion. $relay_to_owner = !$top_level_post && $target_item['item_flags'] & ITEM_ORIGIN && comment_local_origin($target_item) ? true : false; $uplink = false; // $cmd === 'relay' indicates the owner is sending it to the original recipients // don't allow the item in the relay command to relay to owner under any circumstances, it will loop logger('notifier: relay_to_owner: ' . ($relay_to_owner ? 'true' : 'false'), LOGGER_DATA); logger('notifier: top_level_post: ' . ($top_level_post ? 'true' : 'false'), LOGGER_DATA); logger('notifier: target_item_flags: ' . $target_item['item_flags'] . ' ' . ($target_item['item_flags'] & ITEM_ORIGIN ? 'true' : 'false'), LOGGER_DATA); // tag_deliver'd post which needs to be sent back to the original author if ($cmd === 'uplink' && $parent_item['item_flags'] & ITEM_UPLINK && !$top_level_post) { logger('notifier: uplink'); $uplink = true; } if (($relay_to_owner || $uplink) && $cmd !== 'relay') { logger('notifier: followup relay', LOGGER_DEBUG); $recipients = array($uplink ? $parent_item['source_xchan'] : $parent_item['owner_xchan']); $private = true; if (!$encoded_item['flags']) { $encoded_item['flags'] = array(); } $encoded_item['flags'][] = 'relay'; } else { logger('notifier: normal distribution', LOGGER_DEBUG); if ($cmd === 'relay') { logger('notifier: owner relay'); } // if our parent is a tag_delivery recipient, uplink to the original author causing // a delivery fork. if ($parent_item['item_flags'] & ITEM_UPLINK && !$top_level_post && $cmd !== 'uplink') { logger('notifier: uplinking this item'); proc_run('php', 'include/notifier.php', 'uplink', $item_id); } $private = false; $recipients = collect_recipients($parent_item, $private); // FIXME add any additional recipients such as mentions, etc. // don't send deletions onward for other people's stuff // TODO verify this is needed - copied logic from same place in old code if ($target_item['item_restrict'] & ITEM_DELETED && !($target_item['item_flags'] & ITEM_WALL)) { logger('notifier: ignoring delete notification for non-wall item'); return; } } } $walltowall = $top_level_post && $channel['xchan_hash'] === $target_item['author_xchan'] ? true : false; // Generic delivery section, we have an encoded item and recipients // Now start the delivery process $x = $encoded_item; $x['title'] = 'private'; $x['body'] = 'private'; logger('notifier: encoded item: ' . print_r($x, true), LOGGER_DATA); stringify_array_elms($recipients); if (!$recipients) { return; } // logger('notifier: recipients: ' . print_r($recipients,true)); $env_recips = $private ? array() : null; $details = q("select xchan_hash, xchan_instance_url, xchan_network, xchan_addr, xchan_guid, xchan_guid_sig from xchan where xchan_hash in (" . implode(',', $recipients) . ")"); $recip_list = array(); if ($details) { foreach ($details as $d) { // If the recipient is federated from a traditional network they won't be able to // handle nomadic identity. If we're publishing from a site that they aren't // directly connected with, ignore them. // FIXME: make sure we run through a notifier loop on the hub they're connected // with if this post comes in from a different hub - so that we will deliver to them. // On the down side, these channels will stop working if the hub they connected with // goes down permanently, as they are (doh) not nomadic. if ($d['xchan_instance_url'] && $d['xchan_instance_url'] != z_root()) { continue; } $recip_list[] = $d['xchan_addr'] . ' (' . $d['xchan_hash'] . ')'; if ($private) { $env_recips[] = array('guid' => $d['xchan_guid'], 'guid_sig' => $d['xchan_guid_sig'], 'hash' => $d['xchan_hash']); } } } if ($private && !$env_recips) { // shouldn't happen logger('notifier: private message with no envelope recipients.' . print_r($argv, true)); } logger('notifier: recipients (may be delivered to more if public): ' . print_r($recip_list, true), LOGGER_DEBUG); // Now we have collected recipients (except for external mentions, FIXME) // Let's reduce this to a set of hubs. logger('notifier: hub choice: ' . intval($relay_to_owner) . ' ' . intval($private) . ' ' . $cmd, LOGGER_DEBUG); // FIXME: I think we need to remove the private bit or this clause will never execute. Needs more coffee to think it through. // We may in fact have to send it to clones in case the one we pick recently died. if ($relay_to_owner && !$private && $cmd !== 'relay') { // If sending a followup to the post owner, only send it to one channel clone - to avoid race conditions. // In this case we'll pick the most recently contacted hub, as their primary might be down and the most // recently contacted has the best chance of being alive. // For private posts or uplinks we have to do things differently as only the sending clone will have the recipient list. // We have to send to all clone channels of the owner to find out who has the definitive list. Posts with // item_private set (but no ACL list) will return empty recipients (except for the sender and owner) in // collect_recipients() above. The end result is we should get only one delivery per delivery chain if we // aren't the owner or author. $r = q("select hubloc_guid, hubloc_url, hubloc_sitekey, hubloc_network, hubloc_flags, hubloc_callback, hubloc_host from hubloc \n\t\t\twhere hubloc_hash in (" . implode(',', $recipients) . ") order by hubloc_connected desc limit 1"); } else { $r = q("select hubloc_guid, hubloc_url, hubloc_sitekey, hubloc_network, hubloc_flags, hubloc_callback, hubloc_host from hubloc \n\t\t\twhere hubloc_hash in (" . implode(',', $recipients) . ") and not (hubloc_flags & %d) > 0 and not (hubloc_status & %d) > 0", intval(HUBLOC_FLAGS_DELETED), intval(HUBLOC_OFFLINE)); } if (!$r) { logger('notifier: no hubs'); return; } $hubs = $r; /** * Reduce the hubs to those that are unique. For zot hubs, we need to verify uniqueness by the sitekey, since it may have been * a re-install which has not yet been detected and pruned. * For other networks which don't have or require sitekeys, we'll have to use the URL */ $hublist = array(); // this provides an easily printable list for the logs $dhubs = array(); // delivery hubs where we store our resulting unique array $keys = array(); // array of keys to check uniquness for zot hubs $urls = array(); // array of urls to check uniqueness of hubs from other networks foreach ($hubs as $hub) { if ($hub['hubloc_network'] == 'zot') { if (!in_array($hub['hubloc_sitekey'], $keys)) { $hublist[] = $hub['hubloc_host']; $dhubs[] = $hub; $keys[] = $hub['hubloc_sitekey']; } } else { if (!in_array($hub['hubloc_url'], $urls)) { $hublist[] = $hub['hubloc_host']; $dhubs[] = $hub; $urls[] = $hub['hubloc_url']; } } } logger('notifier: will notify/deliver to these hubs: ' . print_r($hublist, true), LOGGER_DEBUG); $interval = get_config('system', 'delivery_interval') !== false ? intval(get_config('system', 'delivery_interval')) : 2; $deliveries_per_process = intval(get_config('system', 'delivery_batch_count')); if ($deliveries_per_process <= 0) { $deliveries_per_process = 1; } $deliver = array(); foreach ($dhubs as $hub) { if (defined('DIASPORA_RELIABILITY_EMULATION')) { $cointoss = mt_rand(0, 2); if ($cointoss == 2) { continue; } } if ($hub['hubloc_network'] === 'diaspora' || $hub['hubloc_network'] === 'friendica-over-diaspora') { if (!get_config('system', 'diaspora_enabled')) { continue; } require_once 'include/diaspora.php'; diaspora_process_outbound(array('channel' => $channel, 'env_recips' => $env_recips, 'recipients' => $recipients, 'item' => $item, 'target_item' => $target_item, 'hub' => $hub, 'top_level_post' => $top_level_post, 'private' => $private, 'followup' => $followup, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'expire' => $expire, 'mail' => $mail, 'location' => $location, 'fsuggest' => $fsuggest, 'request' => $request, 'normal_mode' => $normal_mode, 'packet_type' => $packet_type, 'walltowall' => $walltowall)); continue; } // default: zot protocol $hash = random_string(); if ($packet_type === 'refresh' || $packet_type === 'purge') { $n = zot_build_packet($channel, $packet_type); q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", dbesc($hash), intval($channel['channel_account_id']), intval($channel['channel_id']), dbesc('zot'), dbesc($hub['hubloc_callback']), intval(1), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($n), dbesc('')); } elseif ($packet_type === 'request') { $n = zot_build_packet($channel, 'request', $env_recips, $hub['hubloc_sitekey'], $hash, array('message_id' => $request_message_id)); q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", dbesc($hash), intval($channel['channel_account_id']), intval($channel['channel_id']), dbesc('zot'), dbesc($hub['hubloc_callback']), intval(1), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($n), dbesc('')); } else { $n = zot_build_packet($channel, 'notify', $env_recips, $private ? $hub['hubloc_sitekey'] : null, $hash); q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_created, outq_updated, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, '%s', '%s', '%s', '%s' )", dbesc($hash), intval($target_item['aid']), intval($target_item['uid']), dbesc('zot'), dbesc($hub['hubloc_callback']), intval(1), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc($n), dbesc(json_encode($encoded_item))); } $deliver[] = $hash; if (count($deliver) >= $deliveries_per_process) { proc_run('php', 'include/deliver.php', $deliver); $deliver = array(); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } } // catch any stragglers if (count($deliver)) { proc_run('php', 'include/deliver.php', $deliver); } logger('notifier: basic loop complete.', LOGGER_DEBUG); if ($normal_mode) { call_hooks('notifier_normal', $target_item); } call_hooks('notifier_end', $target_item); logger('notifer: complete.'); return; }
function get() { $atokens = array(); if (local_channel()) { $at = q("select * from atoken where atoken_uid = %d", intval(local_channel())); if ($at) { foreach ($at as $t) { $atokens[] = atoken_xchan($t); } } } $type = argc() > 1 ? argv(1) : 0; if (is_numeric($type)) { $item_id = intval($type); $type = 'item'; } else { $item_id = argc() > 2 ? intval(argv(2)) : 0; } if (!$item_id) { killme(); } if (!in_array($type, array('item', 'photo', 'event', 'menu_item', 'chatroom'))) { killme(); } //we have different naming in in menu_item table and chatroom table switch ($type) { case 'menu_item': $id = 'mitem_id'; break; case 'chatroom': $id = 'cr_id'; break; default: $id = 'id'; break; } $r = q("SELECT * FROM %s WHERE {$id} = %d LIMIT 1", dbesc($type), intval($item_id)); if (!$r) { killme(); } $item = $r[0]; //we have different naming in in menu_item table and chatroom table switch ($type) { case 'menu_item': $uid = $item['mitem_channel_id']; break; case 'chatroom': $uid = $item['cr_uid']; break; default: $uid = $item['uid']; break; } if ($uid != local_channel()) { echo '<li>' . t('Remote privacy information not available.') . '</li>'; killme(); } if ($item['item_private'] == 1 && !strlen($item['allow_cid']) && !strlen($item['allow_gid']) && !strlen($item['deny_cid']) && !strlen($item['deny_gid'])) { // if the post is private, but public_policy is blank ("visible to the internet"), and there aren't any // specific recipients, we're the recipient of a post with "bcc" or targeted recipients; so we'll just show it // as unknown specific recipients. The sender will have the visibility list and will fall through to the // next section. echo '<li>' . translate_scope(!$item['public_policy'] ? 'specific' : $item['public_policy']) . '</li>'; killme(); } $allowed_users = expand_acl($item['allow_cid']); $allowed_groups = expand_acl($item['allow_gid']); $deny_users = expand_acl($item['deny_cid']); $deny_groups = expand_acl($item['deny_gid']); $o = '<li>' . t('Visible to:') . '</li>'; $l = array(); stringify_array_elms($allowed_groups, true); stringify_array_elms($allowed_users, true); stringify_array_elms($deny_groups, true); stringify_array_elms($deny_users, true); if (count($allowed_groups)) { $r = q("SELECT gname FROM `groups` WHERE hash IN ( " . implode(', ', $allowed_groups) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><b>' . $rr['gname'] . '</b></li>'; } } } if (count($allowed_users)) { $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ', $allowed_users) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li>' . $rr['xchan_name'] . '</li>'; } } if ($atokens) { foreach ($atokens as $at) { if (in_array("'" . $at['xchan_hash'] . "'", $allowed_users)) { $l[] = '<li>' . $at['xchan_name'] . '</li>'; } } } } if (count($deny_groups)) { $r = q("SELECT gname FROM `groups` WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><b><strike>' . $rr['gname'] . '</strike></b></li>'; } } } if (count($deny_users)) { $r = q("SELECT xchan_name FROM xchan WHERE xchan_hash IN ( " . implode(', ', $deny_users) . " )"); if ($r) { foreach ($r as $rr) { $l[] = '<li><strike>' . $rr['xchan_name'] . '</strike></li>'; } } if ($atokens) { foreach ($atokens as $at) { if (in_array("'" . $at['xchan_hash'] . "'", $deny_users)) { $l[] = '<li><strike>' . $at['xchan_name'] . '</strike></li>'; } } } } echo $o . implode($l); killme(); }
/** * @function identity_basic_export($channel_id) * Create an array representing the important channel information * which would be necessary to create a nomadic identity clone. This includes * most channel resources and connection information with the exception of content. * * @param int $channel_id * Channel_id to export * * * @returns array * See function for details * */ function identity_basic_export($channel_id) { /* * Red basic channel export */ $ret = array(); $ret['compatibility'] = array('project' => RED_PLATFORM, 'version' => RED_VERSION, 'database' => DB_UPDATE_VERSION); $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($r) { $ret['channel'] = $r[0]; } $r = q("select * from profile where uid = %d", intval($channel_id)); if ($r) { $ret['profile'] = $r; } $xchans = array(); $r = q("select * from abook where abook_channel = %d ", intval($channel_id)); if ($r) { $ret['abook'] = $r; foreach ($r as $rr) { $xchans[] = $rr['abook_xchan']; } stringify_array_elms($xchans); } if ($xchans) { $r = q("select * from xchan where xchan_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['xchan'] = $r; } $r = q("select * from hubloc where hubloc_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['hubloc'] = $r; } } $r = q("select * from `groups` where uid = %d ", intval($channel_id)); if ($r) { $ret['group'] = $r; } $r = q("select * from group_member where uid = %d ", intval($channel_id)); if ($r) { $ret['group_member'] = $r; } $r = q("select * from pconfig where uid = %d", intval($channel_id)); if ($r) { $ret['config'] = $r; } $r = q("select type, data from photo where scale = 4 and profile = 1 and uid = %d limit 1", intval($channel_id)); if ($r) { $ret['photo'] = array('type' => $r[0]['type'], 'data' => base64url_encode($r[0]['data'])); } return $ret; }
/** * @brief Create an array representing the important channel information * which would be necessary to create a nomadic identity clone. This includes * most channel resources and connection information with the exception of content. * * @param int $channel_id * Channel_id to export * @param boolean $items * Include channel posts (wall items), default false * * @returns array * See function for details */ function identity_basic_export($channel_id, $items = false) { /* * Red basic channel export */ $ret = array(); $ret['compatibility'] = array('project' => PLATFORM_NAME, 'version' => RED_VERSION, 'database' => DB_UPDATE_VERSION); $r = q("select * from channel where channel_id = %d limit 1", intval($channel_id)); if ($r) { $ret['channel'] = $r[0]; } $r = q("select * from profile where uid = %d", intval($channel_id)); if ($r) { $ret['profile'] = $r; } $xchans = array(); $r = q("select * from abook where abook_channel = %d ", intval($channel_id)); if ($r) { $ret['abook'] = $r; foreach ($r as $rr) { $xchans[] = $rr['abook_xchan']; } stringify_array_elms($xchans); } if ($xchans) { $r = q("select * from xchan where xchan_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['xchan'] = $r; } $r = q("select * from hubloc where hubloc_hash in ( " . implode(',', $xchans) . " ) "); if ($r) { $ret['hubloc'] = $r; } } $r = q("select * from `groups` where uid = %d ", intval($channel_id)); if ($r) { $ret['group'] = $r; } $r = q("select * from group_member where uid = %d ", intval($channel_id)); if ($r) { $ret['group_member'] = $r; } $r = q("select * from pconfig where uid = %d", intval($channel_id)); if ($r) { $ret['config'] = $r; } $r = q("select type, data from photo where scale = 4 and profile = 1 and uid = %d limit 1", intval($channel_id)); if ($r) { $ret['photo'] = array('type' => $r[0]['type'], 'data' => base64url_encode($r[0]['data'])); } // All other term types will be included in items, if requested. $r = q("select * from term where type in (%d,%d) and uid = %d", intval(TERM_SAVEDSEARCH), intval(TERM_THING), intval($channel_id)); if ($r) { $ret['term'] = $r; } // make the obj output match the hubzilla file format $datestamp = datetime_convert(); $r = q("select obj.*, term.term as obj_term, term.url as obj_url, term.imgurl as obj_imgurl, '%s' as obj_created, '%s' as obj_edited, '%s' as obj_baseurl from obj left join term on obj_obj = term.term_hash where obj_channel = %d", dbesc($datestamp), dbesc($datestamp), dbesc(z_root()), intval($channel_id)); if ($r) { $ret['obj'] = $r; } $r = q("select * from app where app_channel = %d", intval($channel_id)); if ($r) { $ret['app'] = $r; } $r = q("select * from chatroom where cr_uid = %d", intval($channel_id)); if ($r) { $ret['chatroom'] = $r; } $r = q("select * from event where uid = %d", intval($channel_id)); if ($r) { $ret['event'] = $r; } $r = q("select * from item where resource_type = 'event' and uid = %d", intval($channel_id)); if ($r) { $ret['event_item'] = array(); xchan_query($r); $r = fetch_post_tags($r, true); foreach ($r as $rr) { $ret['event_item'][] = encode_item($rr, true); } } $x = menu_list($channel_id); if ($x) { $ret['menu'] = array(); for ($y = 0; $y < count($x); $y++) { $m = menu_fetch($x[$y]['menu_name'], $channel_id, $ret['channel']['channel_hash']); if ($m) { $ret['menu'][] = menu_element($m); } } } if (!$items) { return $ret; } $r = q("select * from likes where channel_id = %d", intval($channel_id)); if ($r) { $ret['likes'] = $r; } $r = q("select item_id.*, item.mid from item_id left join item on item_id.iid = item.id where item_id.uid = %d", intval($channel_id)); if ($r) { $ret['item_id'] = $r; } //$key = get_config('system','prvkey'); /** @warning this may run into memory limits on smaller systems */ /** Don't export linked resource items. we'll have to pull those out separately. */ $r = q("select * from item where (item_flags & %d) > 0 and not (item_restrict & %d) > 0 and uid = %d and resource_type = '' order by created", intval(ITEM_WALL), intval(ITEM_DELETED), intval($channel_id)); if ($r) { $ret['item'] = array(); xchan_query($r); $r = fetch_post_tags($r, true); foreach ($r as $rr) { $ret['item'][] = encode_item($rr, true); } } return $ret; }