public static function run($argc, $argv)
 {
     /**
      * Cron Weekly
      * 
      * Actions in the following block are executed once per day only on Sunday (once per week).
      *
      */
     call_hooks('cron_weekly', datetime_convert());
     z_check_cert();
     require_once 'include/hubloc.php';
     prune_hub_reinstalls();
     mark_orphan_hubsxchans();
     // get rid of really old poco records
     q("delete from xlink where xlink_updated < %s - INTERVAL %s and xlink_static = 0 ", db_utcnow(), db_quoteinterval('14 DAY'));
     $dirmode = intval(get_config('system', 'directory_mode'));
     if ($dirmode === DIRECTORY_MODE_SECONDARY || $dirmode === DIRECTORY_MODE_PRIMARY) {
         logger('regdir: ' . print_r(z_fetch_url(get_directory_primary() . '/regdir?f=&url=' . urlencode(z_root()) . '&realm=' . urlencode(get_directory_realm())), true));
     }
     // Check for dead sites
     Master::Summon(array('Checksites'));
     // update searchable doc indexes
     Master::Summon(array('Importdoc'));
     /**
      * End Cron Weekly
      */
 }
Example #2
0
 public static function run($argc, $argv)
 {
     if ($argc < 2) {
         return;
     }
     $force = false;
     $pushall = true;
     if ($argc > 2) {
         if ($argv[2] === 'force') {
             $force = true;
         }
         if ($argv[2] === 'nopush') {
             $pushall = false;
         }
     }
     logger('directory update', LOGGER_DEBUG);
     $dirmode = get_config('system', 'directory_mode');
     if ($dirmode === false) {
         $dirmode = DIRECTORY_MODE_NORMAL;
     }
     $x = q("select * from channel where channel_id = %d limit 1", intval($argv[1]));
     if (!$x) {
         return;
     }
     $channel = $x[0];
     if ($dirmode != DIRECTORY_MODE_NORMAL) {
         // this is an in-memory update and we don't need to send a network packet.
         local_dir_update($argv[1], $force);
         q("update channel set channel_dirdate = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($channel['channel_id']));
         // Now update all the connections
         if ($pushall) {
             Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id']));
         }
         return;
     }
     // otherwise send the changes upstream
     $directory = find_upstream_directory($dirmode);
     $url = $directory['url'] . '/post';
     // ensure the upstream directory is updated
     $packet = zot_build_packet($channel, $force ? 'force_refresh' : 'refresh');
     $z = zot_zot($url, $packet);
     // re-queue if unsuccessful
     if (!$z['success']) {
         /** @FIXME we aren't updating channel_dirdate if we have to queue
          * the directory packet. That means we'll try again on the next poll run.
          */
         $hash = random_string();
         queue_insert(array('hash' => $hash, 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], 'posturl' => $url, 'notify' => $packet));
     } else {
         q("update channel set channel_dirdate = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($channel['channel_id']));
     }
     // Now update all the connections
     if ($pushall) {
         Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id']));
     }
 }
Example #3
0
 public static function run($argc, $argv)
 {
     logger('cron_daily: start');
     /**
      * Cron Daily
      *
      */
     require_once 'include/dir_fns.php';
     check_upstream_directory();
     // Fire off the Cron_weekly process if it's the correct day.
     $d3 = intval(datetime_convert('UTC', 'UTC', 'now', 'N'));
     if ($d3 == 7) {
         Master::Summon(array('Cron_weekly'));
     }
     // once daily run birthday_updates and then expire in background
     // FIXME: add birthday updates, both locally and for xprof for use
     // by directory servers
     update_birthdays();
     // expire any read notifications over a month old
     q("delete from notify where seen = 1 and created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 DAY'));
     //update statistics in config
     require_once 'include/statistics_fns.php';
     update_channels_total_stat();
     update_channels_active_halfyear_stat();
     update_channels_active_monthly_stat();
     update_local_posts_stat();
     // expire old delivery reports
     $keep_reports = intval(get_config('system', 'expire_delivery_reports'));
     if ($keep_reports === 0) {
         $keep_reports = 10;
     }
     q("delete from dreport where dreport_time < %s - INTERVAL %s", db_utcnow(), db_quoteinterval($keep_reports . ' DAY'));
     // expire any expired accounts
     downgrade_accounts();
     // If this is a directory server, request a sync with an upstream
     // directory at least once a day, up to once every poll interval.
     // Pull remote changes and push local changes.
     // potential issue: how do we keep from creating an endless update loop?
     $dirmode = get_config('system', 'directory_mode');
     if ($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) {
         require_once 'include/dir_fns.php';
         sync_directories($dirmode);
     }
     Master::Summon(array('Expire'));
     Master::Summon(array('Cli_suggest'));
     require_once 'include/hubloc.php';
     remove_obsolete_hublocs();
     call_hooks('cron_daily', datetime_convert());
     set_config('system', 'last_expire_day', $d2);
     /**
      * End Cron Daily
      */
 }
Example #4
0
 public static function run($argc, $argv)
 {
     $maxsysload = intval(get_config('system', 'maxloadavg'));
     if ($maxsysload < 1) {
         $maxsysload = 50;
     }
     if (function_exists('sys_getloadavg')) {
         $load = sys_getloadavg();
         if (intval($load[0]) > $maxsysload) {
             logger('system: load ' . $load . ' too high. Cron deferred to next scheduled run.');
             return;
         }
     }
     // Check for a lockfile.  If it exists, but is over an hour old, it's stale.  Ignore it.
     $lockfile = 'store/[data]/cron';
     if (file_exists($lockfile) && filemtime($lockfile) > time() - 3600 && !get_config('system', 'override_cron_lockfile')) {
         logger("cron: Already running");
         return;
     }
     // Create a lockfile.  Needs two vars, but $x doesn't need to contain anything.
     file_put_contents($lockfile, $x);
     logger('cron: start');
     // run queue delivery process in the background
     Master::Summon(array('Queue'));
     Master::Summon(array('Poller'));
     // maintenance for mod sharedwithme - check for updated items and remove them
     require_once 'include/sharedwithme.php';
     apply_updates();
     // expire any expired mail
     q("delete from mail where expires > '%s' and expires < %s ", dbesc(NULL_DATE), db_utcnow());
     // expire any expired items
     $r = q("select id from item where expires > '2001-01-01 00:00:00' and expires < %s \n\t\t\tand item_deleted = 0 ", db_utcnow());
     if ($r) {
         require_once 'include/items.php';
         foreach ($r as $rr) {
             drop_item($rr['id'], false);
         }
     }
     // delete expired access tokens
     $r = q("select atoken_id from atoken where atoken_expires > '%s' and atoken_expires < %s", dbesc(NULL_DATE), db_utcnow());
     if ($r) {
         require_once 'include/security.php';
         foreach ($r as $rr) {
             atoken_delete($rr['atoken_id']);
         }
     }
     // Ensure that every channel pings a directory server once a month. This way we can discover
     // channels and sites that quietly vanished and prevent the directory from accumulating stale
     // or dead entries.
     $r = q("select channel_id from channel where channel_dirdate < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 DAY'));
     if ($r) {
         foreach ($r as $rr) {
             Master::Summon(array('Directory', $rr['channel_id'], 'force'));
             if ($interval) {
                 @time_sleep_until(microtime(true) + (double) $interval);
             }
         }
     }
     // publish any applicable items that were set to be published in the future
     // (time travel posts). Restrict to items that have come of age in the last
     // couple of days to limit the query to something reasonable.
     $r = q("select id from item where item_delayed = 1 and created <= %s  and created > '%s' ", db_utcnow(), dbesc(datetime_convert('UTC', 'UTC', 'now - 2 days')));
     if ($r) {
         foreach ($r as $rr) {
             $x = q("update item set item_delayed = 0 where id = %d", intval($rr['id']));
             if ($x) {
                 $z = q("select * from item where id = %d", intval($message_id));
                 if ($z) {
                     xchan_query($z);
                     $sync_item = fetch_post_tags($z);
                     build_sync_packet($sync_item[0]['uid'], ['item' => [encode_item($sync_item[0], true)]]);
                 }
                 Master::Summon(array('Notifier', 'wall-new', $rr['id']));
             }
         }
     }
     $abandon_days = intval(get_config('system', 'account_abandon_days'));
     if ($abandon_days < 1) {
         $abandon_days = 0;
     }
     // once daily run birthday_updates and then expire in background
     // FIXME: add birthday updates, both locally and for xprof for use
     // by directory servers
     $d1 = intval(get_config('system', 'last_expire_day'));
     $d2 = intval(datetime_convert('UTC', 'UTC', 'now', 'd'));
     // Allow somebody to staggger daily activities if they have more than one site on their server,
     // or if it happens at an inconvenient (busy) hour.
     $h1 = intval(get_config('system', 'cron_hour'));
     $h2 = intval(datetime_convert('UTC', 'UTC', 'now', 'G'));
     if ($d2 != $d1 && $h1 == $h2) {
         Master::Summon(array('Cron_daily'));
     }
     // update any photos which didn't get imported properly
     // This should be rare
     $r = q("select xchan_photo_l, xchan_hash from xchan where xchan_photo_l != '' and xchan_photo_m = '' \n\t\t\tand xchan_photo_date < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 DAY'));
     if ($r) {
         require_once 'include/photo/photo_driver.php';
         foreach ($r as $rr) {
             $photos = import_xchan_photo($rr['xchan_photo_l'], $rr['xchan_hash']);
             $x = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s'\n\t\t\t\t\twhere xchan_hash = '%s'", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), dbesc($photos[3]), dbesc($rr['xchan_hash']));
         }
     }
     // pull in some public posts
     if (!get_config('system', 'disable_discover_tab')) {
         Master::Summon(array('Externals'));
     }
     $generation = 0;
     $restart = false;
     if ($argc > 1 && $argv[1] == 'restart') {
         $restart = true;
         $generation = intval($argv[2]);
         if (!$generation) {
             killme();
         }
     }
     reload_plugins();
     $d = datetime_convert();
     // TODO check to see if there are any cronhooks before wasting a process
     if (!$restart) {
         Master::Summon(array('Cronhooks'));
     }
     set_config('system', 'lastcron', datetime_convert());
     //All done - clear the lockfile
     @unlink($lockfile);
     return;
 }
Example #5
0
 public static function run($argc, $argv)
 {
     $maxsysload = intval(get_config('system', 'maxloadavg'));
     if ($maxsysload < 1) {
         $maxsysload = 50;
     }
     if (function_exists('sys_getloadavg')) {
         $load = sys_getloadavg();
         if (intval($load[0]) > $maxsysload) {
             logger('system: load ' . $load . ' too high. Poller deferred to next scheduled run.');
             return;
         }
     }
     $interval = intval(get_config('system', 'poll_interval'));
     if (!$interval) {
         $interval = get_config('system', 'delivery_interval') === false ? 3 : intval(get_config('system', 'delivery_interval'));
     }
     // Check for a lockfile.  If it exists, but is over an hour old, it's stale.  Ignore it.
     $lockfile = 'store/[data]/poller';
     if (file_exists($lockfile) && filemtime($lockfile) > time() - 3600 && !get_config('system', 'override_poll_lockfile')) {
         logger("poller: Already running");
         return;
     }
     // Create a lockfile.  Needs two vars, but $x doesn't need to contain anything.
     file_put_contents($lockfile, $x);
     logger('poller: start');
     $manual_id = 0;
     $generation = 0;
     $force = false;
     $restart = false;
     if ($argc > 1 && $argv[1] == 'force') {
         $force = true;
     }
     if ($argc > 1 && $argv[1] == 'restart') {
         $restart = true;
         $generation = intval($argv[2]);
         if (!$generation) {
             killme();
         }
     }
     if ($argc > 1 && intval($argv[1])) {
         $manual_id = intval($argv[1]);
         $force = true;
     }
     $sql_extra = $manual_id ? " AND abook_id = " . intval($manual_id) . " " : "";
     reload_plugins();
     $d = datetime_convert();
     // Only poll from those with suitable relationships
     $abandon_sql = $abandon_days ? sprintf(" AND account_lastlog > %s - INTERVAL %s ", db_utcnow(), db_quoteinterval(intval($abandon_days) . ' DAY')) : '';
     $randfunc = db_getfunc('RAND');
     $contacts = q("SELECT * FROM abook LEFT JOIN xchan on abook_xchan = xchan_hash \n\t\t\tLEFT JOIN account on abook_account = account_id\n\t\t\twhere abook_self = 0\n\t\t\t{$sql_extra} \n\t\t\tAND (( account_flags = %d ) OR ( account_flags = %d )) {$abandon_sql} ORDER BY {$randfunc}", intval(ACCOUNT_OK), intval(ACCOUNT_UNVERIFIED));
     if ($contacts) {
         foreach ($contacts as $contact) {
             $update = false;
             $t = $contact['abook_updated'];
             $c = $contact['abook_connected'];
             if (intval($contact['abook_feed'])) {
                 $min = service_class_fetch($contact['abook_channel'], 'minimum_feedcheck_minutes');
                 if (!$min) {
                     $min = intval(get_config('system', 'minimum_feedcheck_minutes'));
                 }
                 if (!$min) {
                     $min = 60;
                 }
                 $x = datetime_convert('UTC', 'UTC', "now - {$min} minutes");
                 if ($c < $x) {
                     Master::Summon(array('Onepoll', $contact['abook_id']));
                     if ($interval) {
                         @time_sleep_until(microtime(true) + (double) $interval);
                     }
                 }
                 continue;
             }
             if ($contact['xchan_network'] !== 'zot') {
                 continue;
             }
             if ($c == $t) {
                 if (datetime_convert('UTC', 'UTC', 'now') > datetime_convert('UTC', 'UTC', $t . " + 1 day")) {
                     $update = true;
                 }
             } else {
                 // if we've never connected with them, start the mark for death countdown from now
                 if ($c == NULL_DATE) {
                     $r = q("update abook set abook_connected = '%s'  where abook_id = %d", dbesc(datetime_convert()), intval($contact['abook_id']));
                     $c = datetime_convert();
                     $update = true;
                 }
                 // He's dead, Jim
                 if (strcmp(datetime_convert('UTC', 'UTC', 'now'), datetime_convert('UTC', 'UTC', $c . " + 30 day")) > 0) {
                     $r = q("update abook set abook_archived = 1 where abook_id = %d", intval($contact['abook_id']));
                     $update = false;
                     continue;
                 }
                 if (intval($contact['abook_archived'])) {
                     $update = false;
                     continue;
                 }
                 // might be dead, so maybe don't poll quite so often
                 // recently deceased, so keep up the regular schedule for 3 days
                 if (strcmp(datetime_convert('UTC', 'UTC', 'now'), datetime_convert('UTC', 'UTC', $c . " + 3 day")) > 0 && strcmp(datetime_convert('UTC', 'UTC', 'now'), datetime_convert('UTC', 'UTC', $t . " + 1 day")) > 0) {
                     $update = true;
                 }
                 // After that back off and put them on a morphine drip
                 if (strcmp(datetime_convert('UTC', 'UTC', 'now'), datetime_convert('UTC', 'UTC', $t . " + 2 day")) > 0) {
                     $update = true;
                 }
             }
             if (intval($contact['abook_pending']) || intval($contact['abook_archived']) || intval($contact['abook_ignored']) || intval($contact['abook_blocked'])) {
                 continue;
             }
             if (!$update && !$force) {
                 continue;
             }
             Master::Summon(array('Onepoll', $contact['abook_id']));
             if ($interval) {
                 @time_sleep_until(microtime(true) + (double) $interval);
             }
         }
     }
     if ($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) {
         $r = q("SELECT u.ud_addr, u.ud_id, u.ud_last FROM updates AS u INNER JOIN (SELECT ud_addr, max(ud_id) AS ud_id FROM updates WHERE ( ud_flags & %d ) = 0 AND ud_addr != '' AND ( ud_last = '%s' OR ud_last > %s - INTERVAL %s ) GROUP BY ud_addr) AS s ON s.ud_id = u.ud_id ", intval(UPDATE_FLAGS_UPDATED), dbesc(NULL_DATE), db_utcnow(), db_quoteinterval('7 DAY'));
         if ($r) {
             foreach ($r as $rr) {
                 // If they didn't respond when we attempted before, back off to once a day
                 // After 7 days we won't bother anymore
                 if ($rr['ud_last'] != NULL_DATE) {
                     if ($rr['ud_last'] > datetime_convert('UTC', 'UTC', 'now - 1 day')) {
                         continue;
                     }
                 }
                 Master::Summon(array('Onedirsync', $rr['ud_id']));
                 if ($interval) {
                     @time_sleep_until(microtime(true) + (double) $interval);
                 }
             }
         }
     }
     set_config('system', 'lastpoll', datetime_convert());
     //All done - clear the lockfile
     @unlink($lockfile);
     return;
 }
Example #6
0
 public static function run($argc, $argv)
 {
     require_once "datetime.php";
     require_once 'include/items.php';
     if ($argc < 3) {
         return;
     }
     logger('ratenotif: invoked: ' . print_r($argv, true), LOGGER_DEBUG);
     $cmd = $argv[1];
     $item_id = $argv[2];
     if ($cmd === 'rating') {
         $r = q("select * from xlink where xlink_id = %d and xlink_static = 1 limit 1", intval($item_id));
         if (!$r) {
             logger('rating not found');
             return;
         }
         $encoded_item = array('type' => 'rating', 'encoding' => 'zot', 'target' => $r[0]['xlink_link'], 'rating' => intval($r[0]['xlink_rating']), 'rating_text' => $r[0]['xlink_rating_text'], 'signature' => $r[0]['xlink_sig'], 'edited' => $r[0]['xlink_updated']);
     }
     $channel = channelx_by_hash($r[0]['xlink_xchan']);
     if (!$channel) {
         logger('no channel');
         return;
     }
     $primary = get_directory_primary();
     if (!$primary) {
         return;
     }
     $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();
     $x = z_fetch_url($primary . '/regdir');
     if ($x['success']) {
         $j = json_decode($x['body'], true);
         if ($j && $j['success'] && is_array($j['directories'])) {
             foreach ($j['directories'] as $h) {
                 if ($h == z_root()) {
                     continue;
                 }
                 $hash = random_string();
                 $n = zot_build_packet($channel, 'notify', null, null, $hash);
                 queue_insert(array('hash' => $hash, 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], 'posturl' => $h . '/post', 'notify' => $n, 'msg' => json_encode($encoded_item)));
                 $deliver[] = $hash;
                 if (count($deliver) >= $deliveries_per_process) {
                     Master::Summon(array('Deliver', $deliver));
                     $deliver = array();
                     if ($interval) {
                         @time_sleep_until(microtime(true) + (double) $interval);
                     }
                 }
             }
             // catch any stragglers
             if (count($deliver)) {
                 Master::Summon(array('Deliver', $deliver));
             }
         }
     }
     logger('ratenotif: complete.');
     return;
 }
Example #7
0
 public static function run($argc, $argv)
 {
     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' || $cmd === 'single_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 and abook_self = 0", 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;
         }
         // Check for non published items, but allow an exclusion for transmitting hidden file activities
         if (intval($target_item['item_unpublished']) || intval($target_item['item_delayed']) || intval($target_item['item_hidden']) && $target_item['obj_type'] !== ACTIVITY_OBJ_FILE) {
             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');
                     Master::Summon(array('Notifier', '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, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'mail' => $mail, 'single' => $cmd === 'single_mail' || $cmd === 'single_activity' ? true : false, '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;
         }
     }
     // notifier_process can alter the recipient list
     $recipients = $narr['recipients'];
     $env_recips = $narr['env_recips'];
     $packet_recips = $narr['packet_recips'];
     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\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, 'relay_to_owner' => $relay_to_owner, 'uplink' => $uplink, 'cmd' => $cmd, 'mail' => $mail, 'single' => $cmd === 'single_mail' || $cmd === 'single_activity' ? true : false, '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;
         }
         // singleton deliveries by definition 'not got zot'.
         // Single deliveries are other federated networks (plugins) and we're essentially
         // delivering only to those that have this site url in their abook_instance
         // and only from within a sync operation. This means if you post from a clone,
         // and a connection is connected to one of your other clones; assuming that hub
         // is running it will receive a sync packet. On receipt of this sync packet it
         // will invoke a delivery to those connections which are connected to just that
         // hub instance.
         if ($cmd === 'single_mail' || $cmd === 'single_activity') {
             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) {
             Master::Summon(array('Deliver_hooks', $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;
 }