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 left join hubloc on hubloc_hash = abook_xchan where abook_id = %d and not ( abook_flags & %d ) > 0 and 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' and 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 ) values ( '%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 AND ( 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; } if(strpos($target_item['postopts'],'nodeliver') !== false) { logger('notifier: target item is undeliverable', LOGGER_DEBUG); return; } $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; if(strpos($r[0]['postopts'],'nodeliver') !== false) { logger('notifier: target item is undeliverable', LOGGER_DEBUG); 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; } // FIXME - make sure this is complete (the other uplink) and check before enabling // if((! $top_level_post) && (! $relay_to_owner) && (! $uplink) && ($cmd !== 'relay')) { // We've been asked to deliver a comment, but it isn't being sent upstream // and the owner isn't delivering it downstream. This is totally unexpected // and shouldn't happen. We will also not allow it to happen. // logger('Comment being processed with unspecific routing.'); // return; // } 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 where 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 where 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($hub['hubloc_network'] === 'diaspora' || $hub['hubloc_network'] === 'friendica-over-diaspora') { if(! get_config('system','diaspora_enabled')) continue; // allow this to be set per message if(strpos($target_item['postopts'],'nodspr') !== false) 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) + (float) $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; } if (array_search(__file__,get_included_files())===0){ notifier_run($argv,$argc); killme(); }