function testdrive_cron($a, $b) { $r = q("select * from account where account_expires_on < %s + INTERVAL %s and\n\t\taccount_expire_notified = '%s' ", db_utcnow(), db_quoteinterval('5 DAY'), dbesc(NULL_DATE)); if ($r) { foreach ($r as $rr) { $uid = $rr['account_default_channel']; if (!$uid) { continue; } $x = q("select * from channel where channel_id = %d limit 1", intval($uid)); if (!$x) { continue; } \Zotlabs\Lib\Enotify::submit(array('type' => NOTIFY_SYSTEM, 'system_type' => 'testdrive_expire', 'from_xchan' => $x[0]['channel_hash'], 'to_xchan' => $x[0]['channel_hash'])); q("update account set account_expire_notified = '%s' where account_id = %d", dbesc(datetime_convert()), intval($rr['account_id'])); } } // give them a 5 day grace period. Then nuke the account. $r = q("select * from account where account_expired = 1 and account_expires < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('5 DAY')); if ($r) { foreach ($r as $rr) { account_remove($rr['account_id']); } } }
function checksites_run($argv, $argc) { cli_startup(); $a = get_app(); logger('checksites: start'); if ($argc > 1 && $argv[1]) { $site_id = $argv[1]; } if ($site_id) { $sql_options = " and site_url = '" . dbesc($argv[1]) . "' "; } $days = intval(get_config('system', 'sitecheckdays')); if ($days < 1) { $days = 30; } $r = q("select * from site where site_dead = 0 and site_update < %s - INTERVAL %s and site_type = %d {$sql_options} ", db_utcnow(), db_quoteinterval($days . ' DAY'), intval(SITE_TYPE_ZOT)); if (!$r) { return; } foreach ($r as $rr) { if (!strcasecmp($rr['site_url'], z_root())) { continue; } $x = ping_site($rr['site_url']); if ($x['success']) { logger('checksites: ' . $rr['site_url']); q("update site set site_update = '%s' where site_url = '%s' ", dbesc(datetime_convert()), dbesc($rr['site_url'])); } else { logger('marking dead site: ' . $x['message']); q("update site set site_dead = 1 where site_url = '%s' ", dbesc($rr['site_url'])); } } return; }
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 */ }
function queue_run($argv, $argc) { cli_startup(); global $a; require_once 'include/items.php'; require_once 'include/bbcode.php'; if (argc() > 1) { $queue_id = argv(1); } else { $queue_id = 0; } logger('queue: start'); // delete all queue items more than 3 days old // but first mark these sites dead if we haven't heard from them in a month $r = q("select outq_posturl from outq where outq_created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('3 DAY')); if ($r) { foreach ($r as $rr) { $site_url = ''; $h = parse_url($rr['outq_posturl']); $desturl = $h['scheme'] . '://' . $h['host'] . ($h['port'] ? ':' . $h['port'] : ''); q("update site set site_dead = 1 where site_dead = 0 and site_url = '%s' and site_update < %s - INTERVAL %s", dbesc($desturl), db_utcnow(), db_quoteinterval('1 MONTH')); } } $r = q("DELETE FROM outq WHERE outq_created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('3 DAY')); if ($queue_id) { $r = q("SELECT * FROM outq WHERE outq_hash = '%s' LIMIT 1", dbesc($queue_id)); } else { // For the first 12 hours we'll try to deliver every 15 minutes // After that, we'll only attempt delivery once per hour. // This currently only handles the default queue drivers ('zot' or '') which we will group by posturl // so that we don't start off a thousand deliveries for a couple of dead hubs. // The zot driver will deliver everything destined for a single hub once contact is made (*if* contact is made). // Other drivers will have to do something different here and may need their own query. // Note: this requires some tweaking as new posts to long dead hubs once a day will keep them in the // "every 15 minutes" category. We probably need to prioritise them when inserted into the queue // or just prior to this query based on recent and long-term delivery history. If we have good reason to believe // the site is permanently down, there's no reason to attempt delivery at all, or at most not more than once // or twice a day. // FIXME: can we sort postgres on outq_priority and maintain the 'distinct' ? // The order by max(outq_priority) might be a dodgy query because of the group by. // The desired result is to return a sequence in the order most likely to be delivered in this run. // If a hub has already been sitting in the queue for a few days, they should be delivered last; // hence every failure should drop them further down the priority list. if (ACTIVE_DBTYPE == DBTYPE_POSTGRES) { $prefix = 'DISTINCT ON (outq_posturl)'; $suffix = 'ORDER BY outq_posturl'; } else { $prefix = ''; $suffix = 'GROUP BY outq_posturl ORDER BY max(outq_priority)'; } $r = q("SELECT {$prefix} * FROM outq WHERE outq_delivered = 0 and (( outq_created > %s - INTERVAL %s and outq_updated < %s - INTERVAL %s ) OR ( outq_updated < %s - INTERVAL %s )) {$suffix}", db_utcnow(), db_quoteinterval('12 HOUR'), db_utcnow(), db_quoteinterval('15 MINUTE'), db_utcnow(), db_quoteinterval('1 HOUR')); } if (!$r) { return; } foreach ($r as $rr) { queue_deliver($rr); } }
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 */ }
/** * @brief Returns content for Admin Summary Page. * * @param App &$a * @return string HTML from parsed admin_summary.tpl */ function admin_page_summary() { // list total user accounts, expirations etc. $accounts = array(); $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN account_expires > %s THEN 1 ELSE NULL END) AS expiring, COUNT(CASE WHEN account_expires < %s AND account_expires > '%s' THEN 1 ELSE NULL END) AS expired, COUNT(CASE WHEN (account_flags & %d)>0 THEN 1 ELSE NULL END) AS blocked FROM account", db_utcnow(), db_utcnow(), dbesc(NULL_DATE), intval(ACCOUNT_BLOCKED)); if ($r) { $accounts['total'] = array('label' => t('# Accounts'), 'val' => $r[0]['total']); $accounts['blocked'] = array('label' => t('# blocked accounts'), 'val' => $r[0]['blocked']); $accounts['expired'] = array('label' => t('# expired accounts'), 'val' => $r[0]['expired']); $accounts['expiring'] = array('label' => t('# expiring accounts'), 'val' => $r[0]['expiring']); } // pending registrations $r = q("SELECT COUNT(id) AS `count` FROM `register` WHERE `uid` != '0'"); $pending = $r[0]['count']; // available channels, primary and clones $channels = array(); $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN channel_primary = 1 THEN 1 ELSE NULL END) AS main, COUNT(CASE WHEN channel_primary = 0 THEN 1 ELSE NULL END) AS clones FROM channel WHERE channel_removed = 0"); if ($r) { $channels['total'] = array('label' => t('# Channels'), 'val' => $r[0]['total']); $channels['main'] = array('label' => t('# primary'), 'val' => $r[0]['main']); $channels['clones'] = array('label' => t('# clones'), 'val' => $r[0]['clones']); } // We can do better, but this is a quick queue status $r = q("SELECT COUNT(outq_delivered) AS total FROM outq WHERE outq_delivered = 0"); $queue = $r ? $r[0]['total'] : 0; $queues = array('label' => t('Message queues'), 'queue' => $queue); // If no plugins active return 0, otherwise list of plugin names $plugins = count(\App::$plugins) == 0 ? count(\App::$plugins) : \App::$plugins; // Could be extended to provide also other alerts to the admin $alertmsg = ''; // annoy admin about upcoming unsupported PHP version if (version_compare(PHP_VERSION, '5.4', '<')) { $alertmsg = 'Your PHP version ' . PHP_VERSION . ' will not be supported with the next major release of $Projectname. You are strongly urged to upgrade to a current version.' . '<br>PHP 5.3 has reached its <a href="http://php.net/eol.php" class="alert-link">End of Life (EOL)</a> in August 2014.' . ' A list about current PHP versions can be found <a href="http://php.net/supported-versions.php" class="alert-link">here</a>.'; } $vmaster = get_repository_version('master'); $vdev = get_repository_version('dev'); $upgrade = version_compare(STD_VERSION, $vmaster) < 0 ? t('Your software should be updated') : ''; $t = get_markup_template('admin_summary.tpl'); return replace_macros($t, array('$title' => t('Administration'), '$page' => t('Summary'), '$adminalertmsg' => $alertmsg, '$queues' => $queues, '$accounts' => array(t('Registered accounts'), $accounts), '$pending' => array(t('Pending registrations'), $pending), '$channels' => array(t('Registered channels'), $channels), '$plugins' => array(t('Active plugins'), $plugins), '$version' => array(t('Version'), STD_VERSION), '$vmaster' => array(t('Repository version (master)'), $vmaster), '$vdev' => array(t('Repository version (dev)'), $vdev), '$upgrade' => $upgrade, '$build' => get_config('system', 'db_version'))); }
function update_channels_active_monthly_stat() { $r = q("select channel_id from channel left join account on account_id = channel_account_id\n\t\t\twhere account_flags = 0 and account_lastlog > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 MONTH')); if ($r) { $s = ''; foreach ($r as $rr) { if ($s) { $s .= ','; } $s .= intval($rr['channel_id']); } $x = q("select uid from item where uid in ( {$s} ) and item_wall = 1 and created > %s - INTERVAL %s group by uid", db_utcnow(), db_quoteinterval('1 MONTH')); if ($x) { $channels_active_monthly_stat = count($x); set_config('system', 'channels_active_monthly_stat', $channels_active_monthly_stat); } else { set_config('system', 'channels_active_monthly_stat', null); } } else { set_config('system', 'channels_active_monthly_stat', null); } }
function get() { $registration_is = ''; $other_sites = ''; if (get_config('system', 'register_policy') == REGISTER_CLOSED) { if (get_config('system', 'directory_mode') == DIRECTORY_MODE_STANDALONE) { notice(t('Registration on this hub is disabled.') . EOL); return; } $mod = new Pubsites(); return $mod->get(); } if (get_config('system', 'register_policy') == REGISTER_APPROVE) { $registration_is = t('Registration on this hub is by approval only.'); $other_sites = t('<a href="pubsites">Register at another affiliated hub.</a>'); } $max_dailies = intval(get_config('system', 'max_daily_registrations')); if ($max_dailies) { $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 day')); if ($r && $r[0]['total'] >= $max_dailies) { logger('max daily registrations exceeded.'); notice(t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') . EOL); return; } } // Configurable terms of service link $tosurl = get_config('system', 'tos_url'); if (!$tosurl) { $tosurl = z_root() . '/help/TermsOfService'; } $toslink = '<a href="' . $tosurl . '" target="_blank">' . t('Terms of Service') . '</a>'; // Configurable whether to restrict age or not - default is based on international legal requirements // This can be relaxed if you are on a restricted server that does not share with public servers if (get_config('system', 'no_age_restriction')) { $label_tos = sprintf(t('I accept the %s for this website'), $toslink); } else { $label_tos = sprintf(t('I am over 13 years of age and accept the %s for this website'), $toslink); } $enable_tos = 1 - intval(get_config('system', 'no_termsofservice')); $email = array('email', t('Your email address'), x($_REQUEST, 'email') ? strip_tags(trim($_REQUEST['email'])) : ""); $password = array('password', t('Choose a password'), x($_REQUEST, 'password') ? trim($_REQUEST['password']) : ""); $password2 = array('password2', t('Please re-enter your password'), x($_REQUEST, 'password2') ? trim($_REQUEST['password2']) : ""); $invite_code = array('invite_code', t('Please enter your invitation code'), x($_REQUEST, 'invite_code') ? strip_tags(trim($_REQUEST['invite_code'])) : ""); $name = array('name', t('Name or caption'), x($_REQUEST, 'name') ? $_REQUEST['name'] : '', t('Examples: "Bob Jameson", "Lisa and her Horses", "Soccer", "Aviation Group"')); $nickhub = '@' . str_replace(array('http://', 'https://', '/'), '', get_config('system', 'baseurl')); $nickname = array('nickname', t('Choose a short nickname'), x($_REQUEST, 'nickname') ? $_REQUEST['nickname'] : '', sprintf(t('Your nickname will be used to create an easy to remember channel address e.g. nickname%s'), $nickhub)); $privacy_role = x($_REQUEST, 'permissions_role') ? $_REQUEST['permissions_role'] : ""; $role = array('permissions_role', t('Channel role and privacy'), $privacy_role ? $privacy_role : 'social', t('Select a channel role with your privacy requirements.') . ' <a href="help/roles" target="_blank">' . t('Read more about roles') . '</a>', get_roles()); $tos = array('tos', $label_tos, '', '', array(t('no'), t('yes'))); $auto_create = UNO || get_config('system', 'auto_channel_create') ? true : false; $default_role = UNO ? 'social' : get_config('system', 'default_permissions_role'); require_once 'include/bbcode.php'; $o = replace_macros(get_markup_template('register.tpl'), array('$title' => t('Registration'), '$reg_is' => $registration_is, '$registertext' => bbcode(get_config('system', 'register_text')), '$other_sites' => $other_sites, '$invitations' => get_config('system', 'invitation_only'), '$invite_desc' => t('Membership on this site is by invitation only.'), '$invite_code' => $invite_code, '$auto_create' => $auto_create, '$name' => $name, '$role' => $role, '$default_role' => $default_role, '$nickname' => $nickname, '$enable_tos' => $enable_tos, '$tos' => $tos, '$email' => $email, '$pass1' => $password, '$pass2' => $password2, '$submit' => t('Register'), '$verify_note' => t('This site may require email verification after submitting this form. If you are returned to a login page, please check your email for instructions.'))); return $o; }
function chatroom_enter($observer_xchan, $room_id, $status, $client) { if (!$room_id || !$observer_xchan) { return; } $r = q("select * from chatroom where cr_id = %d limit 1", intval($room_id)); if (!$r) { notice(t('Room not found.') . EOL); return false; } require_once 'include/security.php'; $sql_extra = permissions_sql($r[0]['cr_uid']); $x = q("select * from chatroom where cr_id = %d and cr_uid = %d {$sql_extra} limit 1", intval($room_id), intval($r[0]['cr_uid'])); if (!$x) { notice(t('Permission denied.') . EOL); return false; } $limit = service_class_fetch($r[0]['cr_uid'], 'chatters_inroom'); if ($limit !== false) { $y = q("select count(*) as total from chatpresence where cp_room = %d", intval($room_id)); if ($y && $y[0]['total'] > $limit) { notice(t('Room is full') . EOL); return false; } } if (intval($x[0]['cr_expire'])) { $r = q("delete from chat where created < %s - INTERVAL %s and chat_room = %d", db_utcnow(), db_quoteinterval(intval($x[0]['cr_expire']) . ' MINUTE'), intval($x[0]['cr_id'])); } $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d limit 1", dbesc($observer_xchan), intval($room_id)); if ($r) { q("update chatpresence set cp_last = '%s' where cp_id = %d and cp_client = '%s'", dbesc(datetime_convert()), intval($r[0]['cp_id']), dbesc($client)); return true; } $r = q("insert into chatpresence ( cp_room, cp_xchan, cp_last, cp_status, cp_client )\n\t\tvalues ( %d, '%s', '%s', '%s', '%s' )", intval($room_id), dbesc($observer_xchan), dbesc(datetime_convert()), dbesc($status), dbesc($client)); return $r; }
function zot_reply_auth_check($data, $encrypted_packet) { $ret = array('success' => false); /* * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications // This was and should be 10 minutes but my hosting provider has time lag between the DB and // the web server. We should probably convert this to webserver time rather than DB time so // that the different clocks won't affect it and allow us to keep the time short. q("delete from verify where type = 'auth' and created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 MINUTE')); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted // in some way if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'hide_online_status')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); }
/** * @brief zot communications and messaging. * * Sender HTTP posts to this endpoint ($site/post typically) with 'data' parameter set to json zot message packet. * This packet is optionally encrypted, which we will discover if the json has an 'iv' element. * $contents => array( 'alg' => 'aes256cbc', 'iv' => initialisation vector, 'key' => decryption key, 'data' => encrypted data); * $contents->iv and $contents->key are random strings encrypted with this site's RSA public key and then base64url encoded. * Currently only 'aes256cbc' is used, but this is extensible should that algorithm prove inadequate. * * Once decrypted, one will find the normal json_encoded zot message packet. * * Defined packet types are: notify, purge, refresh, force_refresh, auth_check, ping, and pickup * * Standard packet: (used by notify, purge, refresh, force_refresh, and auth_check) * \code{.json} * { * "type": "notify", * "sender":{ * "guid":"kgVFf_1...", * "guid_sig":"PT9-TApzp...", * "url":"http:\/\/podunk.edu", * "url_sig":"T8Bp7j5...", * }, * "recipients": { optional recipient array }, * "callback":"\/post", * "version":1, * "secret":"1eaa...", * "secret_sig": "df89025470fac8..." * } * \endcode * * Signature fields are all signed with the sender channel private key and base64url encoded. * Recipients are arrays of guid and guid_sig, which were previously signed with the recipients private * key and base64url encoded and later obtained via channel discovery. Absence of recipients indicates * a public message or visible to all potential listeners on this site. * * "pickup" packet: * The pickup packet is sent in response to a notify packet from another site * \code{.json} * { * "type":"pickup", * "url":"http:\/\/example.com", * "callback":"http:\/\/example.com\/post", * "callback_sig":"teE1_fLI...", * "secret":"1eaa...", * "secret_sig":"O7nB4_..." * } * \endcode * * In the pickup packet, the sig fields correspond to the respective data * element signed with this site's system private key and then base64url encoded. * The "secret" is the same as the original secret from the notify packet. * * If verification is successful, a json structure is returned containing a * success indicator and an array of type 'pickup'. * Each pickup element contains the original notify request and a message field * whose contents are dependent on the message type. * * This JSON array is AES encapsulated using the site public key of the site * that sent the initial zot pickup packet. * Using the above example, this would be example.com. * * \code{.json} * { * "success":1, * "pickup":{ * "notify":{ * "type":"notify", * "sender":{ * "guid":"kgVFf_...", * "guid_sig":"PT9-TApz...", * "url":"http:\/\/z.podunk.edu", * "url_sig":"T8Bp7j5D..." * }, * "callback":"\/post", * "version":1, * "secret":"1eaa661..." * }, * "message":{ * "type":"activity", * "message_id":"*****@*****.**", * "message_top":"*****@*****.**", * "message_parent":"*****@*****.**", * "created":"2012-11-20 04:04:16", * "edited":"2012-11-20 04:04:16", * "title":"", * "body":"Hi Nickordo", * "app":"", * "verb":"post", * "object_type":"", * "target_type":"", * "permalink":"", * "location":"", * "longlat":"", * "owner":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp...", * }, * "author":{ * "name":"Indigo", * "address":"*****@*****.**", * "url":"http:\/\/podunk.edu", * "photo":{ * "mimetype":"image\/jpeg", * "src":"http:\/\/podunk.edu\/photo\/profile\/m\/5" * }, * "guid":"kgVFf_...", * "guid_sig":"PT9-TAp..." * } * } * } * } * \endcode * * Currently defined message types are 'activity', 'mail', 'profile', 'location' * and 'channel_sync', which each have different content schemas. * * Ping packet: * A ping packet does not require any parameters except the type. It may or may * not be encrypted. * * \code{.json} * { * "type": "ping" * } * \endcode * * On receipt of a ping packet a ping response will be returned: * * \code{.json} * { * "success" : 1, * "site" { * "url": "http:\/\/podunk.edu", * "url_sig": "T8Bp7j5...", * "sitekey": "-----BEGIN PUBLIC KEY----- * MIICIjANBgkqhkiG9w0BAQE..." * } * } * \endcode * * The ping packet can be used to verify that a site has not been re-installed, and to * initiate corrective action if it has. The url_sig is signed with the site private key * and base64url encoded - and this should verify with the enclosed sitekey. Failure to * verify indicates the site is corrupt or otherwise unable to communicate using zot. * This return packet is not otherwise verified, so should be compared with other * results obtained from this site which were verified prior to taking action. For instance * if you have one verified result with this signature and key, and other records for this * url which have different signatures and keys, it indicates that the site was re-installed * and corrective action may commence (remove or mark invalid any entries with different * signatures). * If you have no records which match this url_sig and key - no corrective action should * be taken as this packet may have been returned by an imposter. * * @param[in,out] App &$a */ function post_post(&$a) { $encrypted_packet = false; $ret = array('success' => false); $data = json_decode($_REQUEST['data'], true); /* * Many message packets will arrive encrypted. The existence of an 'iv' * element tells us we need to unencapsulate the AES-256-CBC content using * the site private key. */ if ($data && array_key_exists('iv', $data)) { $encrypted_packet = true; $data = crypto_unencapsulate($data, get_config('system', 'prvkey')); logger('mod_zot: decrypt1: ' . $data, LOGGER_DATA); $data = json_decode($data, true); } if (!$data) { // possible Bleichenbacher's attack, just treat it as a // message we have no handler for. It should fail a bit // further along with "no hub". Our public key is public // knowledge. There's no reason why anybody should get the // encryption wrong unless they're fishing or hacking. If // they're developing and made a goof, this can be discovered // in the logs of the destination site. If they're fishing or // hacking, the bottom line is we can't verify their hub. // That's all we're going to tell them. $data = array('type' => 'bogus'); } $msgtype = array_key_exists('type', $data) ? $data['type'] : ''; if ($msgtype === 'ping') { // Useful to get a health check on a remote site. // This will let us know if any important communication details // that we may have stored are no longer valid, regardless of xchan details. logger('POST: got ping send pong now back: ' . z_root(), LOGGER_DEBUG); $ret['success'] = true; $ret['site'] = array(); $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(), get_config('system', 'prvkey'))); $ret['site']['sitekey'] = get_config('system', 'pubkey'); json_return_and_die($ret); } if ($msgtype === 'pickup') { /* * The 'pickup' message arrives with a tracking ID which is associated with a particular outq_hash * First verify that that the returned signatures verify, then check that we have an outbound queue item * with the correct hash. * If everything verifies, find any/all outbound messages in the queue for this hubloc and send them back */ if (!$data['secret'] || !$data['secret_sig']) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); json_return_and_die($ret); } $r = q("select distinct hubloc_sitekey from hubloc where hubloc_url = '%s' and hubloc_callback = '%s' and hubloc_sitekey != '' group by hubloc_sitekey ", dbesc($data['url']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } foreach ($r as $hubsite) { // verify the url_sig // If the server was re-installed at some point, there could be multiple hubs with the same url and callback. // Only one will have a valid key. $forgery = true; $secret_fail = true; $sitekey = $hubsite['hubloc_sitekey']; logger('mod_zot: Checking sitekey: ' . $sitekey, LOGGER_DATA); if (rsa_verify($data['callback'], base64url_decode($data['callback_sig']), $sitekey)) { $forgery = false; } if (rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $sitekey)) { $secret_fail = false; } if (!$forgery && !$secret_fail) { break; } } if ($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } if ($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /* * If we made it to here, the signatures verify, but we still don't know if the tracking ID is valid. * It wouldn't be an error if the tracking ID isn't found, because we may have sent this particular * queue item with another pickup (after the tracking ID for the other pickup was verified). */ $r = q("select outq_posturl from outq where outq_hash = '%s' and outq_posturl = '%s' limit 1", dbesc($data['secret']), dbesc($data['callback'])); if (!$r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); json_return_and_die($ret); } /* * Everything is good if we made it here, so find all messages that are going to this location * and send them all. */ $r = q("select * from outq where outq_posturl = '%s'", dbesc($data['callback'])); if ($r) { logger('mod_zot: successful pickup message received from ' . $data['callback'] . ' ' . count($r) . ' message(s) picked up', LOGGER_DEBUG); $ret['success'] = true; $ret['pickup'] = array(); foreach ($r as $rr) { if ($rr['outq_msg']) { $x = json_decode($rr['outq_msg'], true); if (!$x) { continue; } if (array_key_exists('message_list', $x)) { foreach ($x['message_list'] as $xx) { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => $xx); } } else { $ret['pickup'][] = array('notify' => json_decode($rr['outq_notify'], true), 'message' => $x); } $x = q("delete from outq where outq_hash = '%s'", dbesc($rr['outq_hash'])); } } } $encrypted = crypto_encapsulate(json_encode($ret), $sitekey); json_return_and_die($encrypted); /* pickup: end */ } /* * All other message types require us to verify the sender. This is a generic check, so we * will do it once here and bail if anything goes wrong. */ if (array_key_exists('sender', $data)) { $sender = $data['sender']; } /* Check if the sender is already verified here */ $hubs = zot_gethub($sender, true); if (!$hubs) { /* Have never seen this guid or this guid coming from this location. Check it and register it. */ // (!!) this will validate the sender $result = zot_register_hub($sender); if (!$result['success'] || !($hubs = zot_gethub($sender, true))) { $ret['message'] = 'Hub not available.'; logger('mod_zot: no hub'); json_return_and_die($ret); } } foreach ($hubs as $hub) { $sitekey = $hub['hubloc_sitekey']; if (array_key_exists('sitekey', $sender) && $sender['sitekey']) { /* * This hub has now been proven to be valid. * Any hub with the same URL and a different sitekey cannot be valid. * Get rid of them (mark them deleted). There's a good chance they were re-installs. */ q("update hubloc set hubloc_deleted = 1, hubloc_error = 1 where hubloc_url = '%s' and hubloc_sitekey != '%s' ", dbesc($hub['hubloc_url']), dbesc($sender['sitekey'])); $sitekey = $sender['sitekey']; } // $sender['sitekey'] is a new addition to the protcol to distinguish // hublocs coming from re-installed sites. Older sites will not provide // this field and we have to still mark them valid, since we can't tell // if this hubloc has the same sitekey as the packet we received. // Update our DB to show when we last communicated successfully with this hub // This will allow us to prune dead hubs from using up resources $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d and hubloc_sitekey = '%s' ", dbesc(datetime_convert()), intval($hub['hubloc_id']), dbesc($sitekey)); // a dead hub came back to life - reset any tombstones we might have if (intval($hub['hubloc_error'])) { q("update hubloc set hubloc_error = 0 where hubloc_id = %d and hubloc_sitekey = '%s' ", intval($hub['hubloc_id']), dbesc($sitekey)); if (intval($r[0]['hubloc_orphancheck'])) { q("update hubloc set hubloc_orhpancheck = 0 where hubloc_id = %d and hubloc_sitekey = '%s' ", intval($hub['hubloc_id']), dbesc($sitekey)); } q("update xchan set xchan_orphan = 0 where xchan_orphan = 1 and xchan_hash = '%s'", dbesc($hub['hubloc_hash'])); } $connecting_url = $hub['hubloc_url']; } /** @TODO check which hub is primary and take action if mismatched */ if (array_key_exists('recipients', $data)) { $recipients = $data['recipients']; } if ($msgtype === 'auth_check') { /* * Requestor visits /magic/?dest=somewhere on their own site with a browser * magic redirects them to $destsite/post [with auth args....] * $destsite sends an auth_check packet to originator site * The auth_check packet is handled here by the originator's site * - the browser session is still waiting * inside $destsite/post for everything to verify * If everything checks out we'll return a token to $destsite * and then $destsite will verify the token, authenticate the browser * session and then redirect to the original destination. * If authentication fails, the redirection to the original destination * will still take place but without authentication. */ logger('mod_zot: auth_check', LOGGER_DEBUG); if (!$encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; json_return_and_die($ret); } $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); // garbage collect any old unused notifications // This was and should be 10 minutes but my hosting provider has time lag between the DB and // the web server. We should probably convert this to webserver time rather than DB time so // that the different clocks won't affect it and allow us to keep the time short. q("delete from verify where type = 'auth' and created < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 MINUTE')); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash)); // We created a unique hash in mod/magic.php when we invoked remote auth, and stored it in // the verify table. It is now coming back to us as 'secret' and is signed by a channel at the other end. // First verify their signature. We will have obtained a zot-info packet from them as part of the sender // verification. if (!$y || !rsa_verify($data['secret'], base64url_decode($data['secret_sig']), $y[0]['xchan_pubkey'])) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y, true) . EOL; json_return_and_die($ret); } // There should be exactly one recipient, the original auth requestor $ret['message'] .= 'recipients ' . print_r($recipients, true) . EOL; if ($data['recipients']) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", dbesc($recip_hash)); if (!$c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; json_return_and_die($ret); } $confirm = base64url_encode(rsa_sign($data['secret'] . $recip_hash, $c[0]['channel_prvkey'])); // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token $z = q("select id from verify where channel = %d and type = 'auth' and token = '%s' and meta = '%s' limit 1", intval($c[0]['channel_id']), dbesc($data['secret']), dbesc($data['sender']['url'])); if (!$z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; json_return_and_die($ret); } $r = q("delete from verify where id = %d", intval($z[0]['id'])); $u = q("select account_service_class from account where account_id = %d limit 1", intval($c[0]['channel_account_id'])); logger('mod_zot: auth_check: success', LOGGER_DEBUG); $ret['success'] = true; $ret['confirm'] = $confirm; if ($u && $u[0]['account_service_class']) { $ret['service_class'] = $u[0]['account_service_class']; } // Set "do not track" flag if this site or this channel's profile is restricted // in some way if (intval(get_config('system', 'block_public'))) { $ret['DNT'] = true; } if (!perm_is_allowed($c[0]['channel_id'], '', 'view_profile')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'do_not_track')) { $ret['DNT'] = true; } if (get_pconfig($c[0]['channel_id'], 'system', 'hide_online_status')) { $ret['DNT'] = true; } json_return_and_die($ret); } json_return_and_die($ret); } if ($msgtype === 'request') { // request a particular post/conversation by message_id $x = zot_process_message_request($data); json_return_and_die($x); } if ($msgtype === 'purge') { if ($recipients) { // basically this means "unfriend" foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); if ($r) { $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1", intval($r[0]['channel_id']), dbesc(make_xchan_hash($sender['guid'], $sender['guid_sig']))); if ($r) { contact_remove($r[0]['channel_id'], $r[0]['abook_id']); } } } } else { // Unfriend everybody - basically this means the channel has committed suicide $arr = $data['sender']; $sender_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); require_once 'include/Contact.php'; remove_all_xchan_resources($sender_hash); $ret['success'] = true; json_return_and_die($ret); } } if ($msgtype === 'refresh' || $msgtype === 'force_refresh') { // remote channel info (such as permissions or photo or something) // has been updated. Grab a fresh copy and sync it. // The difference between refresh and force_refresh is that // force_refresh unconditionally creates a directory update record, // even if no changes were detected upon processing. if ($recipients) { // This would be a permissions update, typically for one connection foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel \n\t\t\t\t\tleft join xchan on channel_hash = xchan_hash\n\t\t\t\t\twhere channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig'])); $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), $r[0], $msgtype === 'force_refresh' ? true : false); } } else { // system wide refresh $x = zot_refresh(array('xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], 'hubloc_url' => $sender['url']), null, $msgtype === 'force_refresh' ? true : false); } $ret['success'] = true; json_return_and_die($ret); } if ($msgtype === 'notify') { logger('notify received from ' . $connecting_url); $async = get_config('system', 'queued_fetch'); if ($async) { // add to receive queue // qreceive_add($data); } else { $x = zot_fetch($data); $ret['delivery_report'] = $x; } $ret['success'] = true; json_return_and_die($ret); } // catchall json_return_and_die($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, 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; }
function post() { // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. if (!local_channel() && !remote_channel() && !x($_REQUEST, 'commenter')) { return; } require_once 'include/security.php'; $uid = local_channel(); $channel = null; $observer = null; /** * Is this a reply to something? */ $parent = x($_REQUEST, 'parent') ? intval($_REQUEST['parent']) : 0; $parent_mid = x($_REQUEST, 'parent_mid') ? trim($_REQUEST['parent_mid']) : ''; $remote_xchan = x($_REQUEST, 'remote_xchan') ? trim($_REQUEST['remote_xchan']) : false; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($remote_xchan)); if ($r) { $remote_observer = $r[0]; } else { $remote_xchan = $remote_observer = false; } $profile_uid = x($_REQUEST, 'profile_uid') ? intval($_REQUEST['profile_uid']) : 0; require_once 'include/channel.php'; $sys = get_sys_channel(); if ($sys && $profile_uid && $sys['channel_id'] == $profile_uid && is_site_admin()) { $uid = intval($sys['channel_id']); $channel = $sys; $observer = $sys; } if (x($_REQUEST, 'dropitems')) { require_once 'include/items.php'; $arr_drop = explode(',', $_REQUEST['dropitems']); drop_items($arr_drop); $json = array('success' => 1); echo json_encode($json); killme(); } call_hooks('post_local_start', $_REQUEST); // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); $api_source = x($_REQUEST, 'api_source') && $_REQUEST['api_source'] ? true : false; $consensus = intval($_REQUEST['consensus']); // 'origin' (if non-zero) indicates that this network is where the message originated, // for the purpose of relaying comments to other conversation members. // If using the API from a device (leaf node) you must set origin to 1 (default) or leave unset. // If the API is used from another network with its own distribution // and deliveries, you may wish to set origin to 0 or false and allow the other // network to relay comments. // If you are unsure, it is prudent (and important) to leave it unset. $origin = $api_source && array_key_exists('origin', $_REQUEST) ? intval($_REQUEST['origin']) : 1; // To represent message-ids on other networks - this will create an iconfig record $namespace = $api_source && array_key_exists('namespace', $_REQUEST) ? strip_tags($_REQUEST['namespace']) : ''; $remote_id = $api_source && array_key_exists('remote_id', $_REQUEST) ? strip_tags($_REQUEST['remote_id']) : ''; $owner_hash = null; $message_id = x($_REQUEST, 'message_id') && $api_source ? strip_tags($_REQUEST['message_id']) : ''; $created = x($_REQUEST, 'created') ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['created']) : datetime_convert(); $post_id = x($_REQUEST, 'post_id') ? intval($_REQUEST['post_id']) : 0; $app = x($_REQUEST, 'source') ? strip_tags($_REQUEST['source']) : ''; $return_path = x($_REQUEST, 'return') ? $_REQUEST['return'] : ''; $preview = x($_REQUEST, 'preview') ? intval($_REQUEST['preview']) : 0; $categories = x($_REQUEST, 'category') ? escape_tags($_REQUEST['category']) : ''; $webpage = x($_REQUEST, 'webpage') ? intval($_REQUEST['webpage']) : 0; $pagetitle = x($_REQUEST, 'pagetitle') ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''; $layout_mid = x($_REQUEST, 'layout_mid') ? escape_tags($_REQUEST['layout_mid']) : ''; $plink = x($_REQUEST, 'permalink') ? escape_tags($_REQUEST['permalink']) : ''; $obj_type = x($_REQUEST, 'obj_type') ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE; // allow API to bulk load a bunch of imported items with sending out a bunch of posts. $nopush = x($_REQUEST, 'nopush') ? intval($_REQUEST['nopush']) : 0; /* * Check service class limits */ if ($uid && !x($_REQUEST, 'parent') && !x($_REQUEST, 'post_id')) { $ret = $this->item_check_service_class($uid, $_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE ? true : false); if (!$ret['success']) { notice(t($ret['message']) . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } } if ($pagetitle) { require_once 'library/urlify/URLify.php'; $pagetitle = strtolower(\URLify::transliterate($pagetitle)); } $item_flags = $item_restrict = 0; $route = ''; $parent_item = null; $parent_contact = null; $thr_parent = ''; $parid = 0; $r = false; if ($parent || $parent_mid) { if (!x($_REQUEST, 'type')) { $_REQUEST['type'] = 'net-comment'; } if ($obj_type == ACTIVITY_OBJ_POST) { $obj_type = ACTIVITY_OBJ_COMMENT; } if ($parent) { $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($parent)); } elseif ($parent_mid && $uid) { // This is coming from an API source, and we are logged in $r = q("SELECT * FROM `item` WHERE `mid` = '%s' AND `uid` = %d LIMIT 1", dbesc($parent_mid), intval($uid)); } // if this isn't the real parent of the conversation, find it if ($r !== false && count($r)) { $parid = $r[0]['parent']; $parent_mid = $r[0]['mid']; if ($r[0]['id'] != $r[0]['parent']) { $r = q("SELECT * FROM `item` WHERE `id` = `parent` AND `parent` = %d LIMIT 1", intval($parid)); } } if ($r === false || !count($r)) { notice(t('Unable to locate original post.') . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } // can_comment_on_post() needs info from the following xchan_query // This may be from the discover tab which means we need to correct the effective uid xchan_query($r, true, $r[0]['uid'] == local_channel() ? 0 : local_channel()); $parent_item = $r[0]; $parent = $r[0]['id']; // multi-level threading - preserve the info but re-parent to our single level threading $thr_parent = $parent_mid; $route = $parent_item['route']; } if (!$observer) { $observer = \App::get_observer(); } if ($parent) { logger('mod_item: item_post parent=' . $parent); $can_comment = false; if (array_key_exists('owner', $parent_item) && intval($parent_item['owner']['abook_self'])) { $can_comment = perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_comments'); } else { $can_comment = can_comment_on_post($observer['xchan_hash'], $parent_item); } if (!$can_comment) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } } else { if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], $webpage ? 'write_pages' : 'post_wall')) { notice(t('Permission denied.') . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } } // is this an edited post? $orig_post = null; if ($namespace && $remote_id) { // It wasn't an internally generated post - see if we've got an item matching this remote service id $i = q("select iid from iconfig where cat = 'system' and k = '%s' and v = '%s' limit 1", dbesc($namespace), dbesc($remote_id)); if ($i) { $post_id = $i[0]['iid']; } } $iconfig = null; if ($post_id) { $i = q("SELECT * FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1", intval($profile_uid), intval($post_id)); if (!count($i)) { killme(); } $orig_post = $i[0]; $iconfig = q("select * from iconfig where iid = %d", intval($post_id)); } if (!$channel) { if ($uid && $uid == $profile_uid) { $channel = \App::get_channel(); } else { // posting as yourself but not necessarily to a channel you control $r = q("select * from channel left join account on channel_account_id = account_id where channel_id = %d LIMIT 1", intval($profile_uid)); if ($r) { $channel = $r[0]; } } } if (!$channel) { logger("mod_item: no channel."); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } $owner_xchan = null; $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash'])); if ($r && count($r)) { $owner_xchan = $r[0]; } else { logger("mod_item: no owner."); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } $walltowall = false; $walltowall_comment = false; if ($remote_xchan) { $observer = $remote_observer; } if ($observer) { logger('mod_item: post accepted from ' . $observer['xchan_name'] . ' for ' . $owner_xchan['xchan_name'], LOGGER_DEBUG); // wall-to-wall detection. // For top-level posts, if the author and owner are different it's a wall-to-wall // For comments, We need to additionally look at the parent and see if it's a wall post that originated locally. if ($observer['xchan_name'] != $owner_xchan['xchan_name']) { if ($parent_item && ($parent_item['item_wall'] && $parent_item['item_origin'])) { $walltowall_comment = true; $walltowall = true; } if (!$parent) { $walltowall = true; } } } $acl = new \Zotlabs\Access\AccessList($channel); $public_policy = x($_REQUEST, 'public_policy') ? escape_tags($_REQUEST['public_policy']) : map_scope($channel['channel_r_stream'], true); if ($webpage) { $public_policy = ''; } if ($public_policy) { $private = 1; } if ($orig_post) { $private = 0; // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. if ($webpage) { $acl->set_from_array($_REQUEST); } else { $acl->set($orig_post); $public_policy = $orig_post['public_policy']; $private = $orig_post['item_private']; } if ($private || $public_policy || $acl->is_private()) { $private = 1; } $location = $orig_post['location']; $coord = $orig_post['coord']; $verb = $orig_post['verb']; $app = $orig_post['app']; $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $item_flags = $orig_post['item_flags']; $item_origin = $orig_post['item_origin']; $item_unseen = $orig_post['item_unseen']; $item_starred = $orig_post['item_starred']; $item_uplink = $orig_post['item_uplink']; $item_consensus = $orig_post['item_consensus']; $item_wall = $orig_post['item_wall']; $item_thread_top = $orig_post['item_thread_top']; $item_notshown = $orig_post['item_notshown']; $item_nsfw = $orig_post['item_nsfw']; $item_relay = $orig_post['item_relay']; $item_mentionsme = $orig_post['item_mentionsme']; $item_nocomment = $orig_post['item_nocomment']; $item_obscured = $orig_post['item_obscured']; $item_verified = $orig_post['item_verified']; $item_retained = $orig_post['item_retained']; $item_rss = $orig_post['item_rss']; $item_deleted = $orig_post['item_deleted']; $item_type = $orig_post['item_type']; $item_hidden = $orig_post['item_hidden']; $item_unpublished = $orig_post['item_unpublished']; $item_delayed = $orig_post['item_delayed']; $item_pending_remove = $orig_post['item_pending_remove']; $item_blocked = $orig_post['item_blocked']; $postopts = $orig_post['postopts']; $created = $orig_post['created']; $mid = $orig_post['mid']; $parent_mid = $orig_post['parent_mid']; $plink = $orig_post['plink']; } else { if (!$walltowall) { if (array_key_exists('contact_allow', $_REQUEST) || array_key_exists('group_allow', $_REQUEST) || array_key_exists('contact_deny', $_REQUEST) || array_key_exists('group_deny', $_REQUEST)) { $acl->set_from_array($_REQUEST); } elseif (!$api_source) { // if no ACL has been defined and we aren't using the API, the form // didn't send us any parameters. This means there's no ACL or it has // been reset to the default audience. // If $api_source is set and there are no ACL parameters, we default // to the channel permissions which were set in the ACL contructor. $acl->set(array('allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '')); } } $location = notags(trim($_REQUEST['location'])); $coord = notags(trim($_REQUEST['coord'])); $verb = notags(trim($_REQUEST['verb'])); $title = escape_tags(trim($_REQUEST['title'])); $body = trim($_REQUEST['body']); $body .= trim($_REQUEST['attachment']); $postopts = ''; $private = intval($acl->is_private() || $public_policy); // If this is a comment, set the permissions from the parent. if ($parent_item) { $private = 0; $acl->set($parent_item); $private = intval($acl->is_private() || $parent_item['item_private']); $public_policy = $parent_item['public_policy']; $owner_hash = $parent_item['owner_xchan']; } if (!strlen($body)) { if ($preview) { killme(); } info(t('Empty post discarded.') . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } } $expires = NULL_DATE; if (feature_enabled($profile_uid, 'content_expire')) { if (x($_REQUEST, 'expire')) { $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']); if ($expires <= datetime_convert()) { $expires = NULL_DATE; } } } $mimetype = notags(trim($_REQUEST['mimetype'])); if (!$mimetype) { $mimetype = 'text/bbcode'; } if ($preview) { $body = z_input_filter($profile_uid, $body, $mimetype); } // Verify ability to use html or php!!! $execflag = false; if ($mimetype !== 'text/bbcode') { $z = q("select account_id, account_roles, channel_pageflags from account left join channel on channel_account_id = account_id where channel_id = %d limit 1", intval($profile_uid)); if ($z && ($z[0]['account_roles'] & ACCOUNT_ROLE_ALLOWCODE || $z[0]['channel_pageflags'] & PAGE_ALLOWCODE)) { if ($uid && get_account_id() == $z[0]['account_id']) { $execflag = true; } else { notice(t('Executable content type not permitted to this channel.') . EOL); if (x($_REQUEST, 'return')) { goaway(z_root() . "/" . $return_path); } killme(); } } } $gacl = $acl->get(); $str_contact_allow = $gacl['allow_cid']; $str_group_allow = $gacl['allow_gid']; $str_contact_deny = $gacl['deny_cid']; $str_group_deny = $gacl['deny_gid']; if ($mimetype === 'text/bbcode') { require_once 'include/text.php'; // Markdown doesn't work correctly. Do not re-enable unless you're willing to fix it and support it. // Sample that will probably give you grief - you must preserve the linebreaks // and provide the correct markdown interpretation and you cannot allow unfiltered HTML // Markdown // ======== // // **bold** abcde // fghijkl // *italic* // <img src="javascript:alert('hacked');" /> // if($uid && $uid == $profile_uid && feature_enabled($uid,'markdown')) { // require_once('include/bb2diaspora.php'); // $body = escape_tags(trim($body)); // $body = str_replace("\n",'<br />', $body); // $body = preg_replace_callback('/\[share(.*?)\]/ism','\share_shield',$body); // $body = diaspora2bb($body,true); // $body = preg_replace_callback('/\[share(.*?)\]/ism','\share_unshield',$body); // } // BBCODE alert: the following functions assume bbcode input // and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.) // we may need virtual or template classes to implement the possible alternatives // Work around doubled linefeeds in Tinymce 3.5b2 // First figure out if it's a status post that would've been // created using tinymce. Otherwise leave it alone. $plaintext = true; // $plaintext = ((feature_enabled($profile_uid,'richtext')) ? false : true); // if((! $parent) && (! $api_source) && (! $plaintext)) { // $body = fix_mce_lf($body); // } // If we're sending a private top-level message with a single @-taggable channel as a recipient, @-tag it, if our pconfig is set. if (!$parent && get_pconfig($profile_uid, 'system', 'tagifonlyrecip') && substr_count($str_contact_allow, '<') == 1 && $str_group_allow == '' && $str_contact_deny == '' && $str_group_deny == '') { $x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc(str_replace(array('<', '>'), array('', ''), $str_contact_allow)), intval($profile_uid)); if ($x && $x[0]['abook_their_perms'] & PERMS_W_TAGWALL) { $body .= "\n\n@group+" . $x[0]['abook_id'] . "\n"; } } /** * fix naked links by passing through a callback to see if this is a hubzilla site * (already known to us) which will get a zrl, otherwise link with url, add bookmark tag to both. * First protect any url inside certain bbcode tags so we don't double link it. */ $body = preg_replace_callback('/\\[code(.*?)\\[\\/(code)\\]/ism', '\\red_escape_codeblock', $body); $body = preg_replace_callback('/\\[url(.*?)\\[\\/(url)\\]/ism', '\\red_escape_codeblock', $body); $body = preg_replace_callback('/\\[zrl(.*?)\\[\\/(zrl)\\]/ism', '\\red_escape_codeblock', $body); $body = preg_replace_callback("/([^\\]\\='" . '"' . "\\/]|^|\\#\\^)(https?\\:\\/\\/[a-zA-Z0-9\\:\\/\\-\\?\\&\\;\\.\\=\\@\\_\\~\\#\\%\$\\!\\+\\,]+)/ism", '\\red_zrl_callback', $body); $body = preg_replace_callback('/\\[\\$b64zrl(.*?)\\[\\/(zrl)\\]/ism', '\\red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64url(.*?)\\[\\/(url)\\]/ism', '\\red_unescape_codeblock', $body); $body = preg_replace_callback('/\\[\\$b64code(.*?)\\[\\/(code)\\]/ism', '\\red_unescape_codeblock', $body); // fix any img tags that should be zmg $body = preg_replace_callback('/\\[img(.*?)\\](.*?)\\[\\/img\\]/ism', '\\red_zrlify_img_callback', $body); $body = bb_translate_video($body); /** * Fold multi-line [code] sequences */ $body = preg_replace('/\\[\\/code\\]\\s*\\[code\\]/ism', "\n", $body); $body = scale_external_images($body, false); // Look for tags and linkify them $results = linkify_tags($a, $body, $uid ? $uid : $profile_uid); if ($results) { // Set permissions based on tag replacements set_linkified_perms($results, $str_contact_allow, $str_group_allow, $profile_uid, $parent_item, $private); $post_tags = array(); foreach ($results as $result) { $success = $result['success']; if ($success['replaced']) { $post_tags[] = array('uid' => $profile_uid, 'ttype' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url']); } } } /** * * When a photo was uploaded into the message using the (profile wall) ajax * uploader, The permissions are initially set to disallow anybody but the * owner from seeing it. This is because the permissions may not yet have been * set for the post. If it's private, the photo permissions should be set * appropriately. But we didn't know the final permissions on the post until * now. So now we'll look for links of uploaded photos and attachments that are in the * post and set them to the same permissions as the post itself. * * If the post was end-to-end encrypted we can't find images and attachments in the body, * use our media_str input instead which only contains these elements - but only do this * when encrypted content exists because the photo/attachment may have been removed from * the post and we should keep it private. If it's encrypted we have no way of knowing * so we'll set the permissions regardless and realise that the media may not be * referenced in the post. * * What is preventing us from being able to upload photos into comments is dealing with * the photo and attachment permissions, since we don't always know who was in the * distribution for the top level post. * * We might be able to provide this functionality with a lot of fiddling: * - if the top level post is public (make the photo public) * - if the top level post was written by us or a wall post that belongs to us (match the top level post) * - if the top level post has privacy mentions, add those to the permissions. * - otherwise disallow the photo *or* make the photo public. This is the part that gets messy. */ if (!$preview) { $this->fix_attached_photo_permissions($profile_uid, $owner_xchan['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); $this->fix_attached_file_permissions($channel, $observer['xchan_hash'], strpos($body, '[/crypt]') ? $_POST['media_str'] : $body, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); } $attachments = ''; $match = false; if (preg_match_all('/(\\[attachment\\](.*?)\\[\\/attachment\\])/', $body, $match)) { $attachments = array(); $i = 0; foreach ($match[2] as $mtch) { $attach_link = ''; $hash = substr($mtch, 0, strpos($mtch, ',')); $rev = intval(substr($mtch, strpos($mtch, ','))); $r = attach_by_hash_nodata($hash, $rev); if ($r['success']) { $attachments[] = array('href' => z_root() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision']); } $ext = substr($r['data']['filename'], strrpos($r['data']['filename'], '.')); if (strpos($r['data']['filetype'], 'audio/') !== false) { $attach_link = '[audio]' . z_root() . '/attach/' . $r['data']['hash'] . '/' . $r['data']['revision'] . ($ext ? $ext : '') . '[/audio]'; } elseif (strpos($r['data']['filetype'], 'video/') !== false) { $attach_link = '[video]' . z_root() . '/attach/' . $r['data']['hash'] . '/' . $r['data']['revision'] . ($ext ? $ext : '') . '[/video]'; } $body = str_replace($match[1][$i], $attach_link, $body); $i++; } } } // BBCODE end alert if (strlen($categories)) { $cats = explode(',', $categories); foreach ($cats as $cat) { $post_tags[] = array('uid' => $profile_uid, 'ttype' => TERM_CATEGORY, 'otype' => TERM_OBJ_POST, 'term' => trim($cat), 'url' => $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat))); } } if ($orig_post) { // preserve original tags $t = q("select * from term where oid = %d and otype = %d and uid = %d and ttype in ( %d, %d, %d )", intval($orig_post['id']), intval(TERM_OBJ_POST), intval($profile_uid), intval(TERM_UNKNOWN), intval(TERM_FILE), intval(TERM_COMMUNITYTAG)); if ($t) { foreach ($t as $t1) { $post_tags[] = array('uid' => $profile_uid, 'ttype' => $t1['type'], 'otype' => TERM_OBJ_POST, 'term' => $t1['term'], 'url' => $t1['url']); } } } $item_unseen = local_channel() != $profile_uid ? 1 : 0; $item_wall = $post_type === 'wall' || $post_type === 'wall-comment' ? 1 : 0; $item_origin = $origin ? 1 : 0; $item_consensus = $consensus ? 1 : 0; // determine if this is a wall post if ($parent) { $item_wall = $parent_item['item_wall']; } else { if (!$webpage) { $item_wall = 1; } } if ($moderated) { $item_blocked = ITEM_MODERATED; } if (!strlen($verb)) { $verb = ACTIVITY_POST; } $notify_type = $parent ? 'comment-new' : 'wall-new'; if (!$mid) { $mid = $message_id ? $message_id : item_message_id(); } if (!$parent_mid) { $parent_mid = $mid; } if ($parent_item) { $parent_mid = $parent_item['mid']; } // Fallback so that we alway have a thr_parent if (!$thr_parent) { $thr_parent = $mid; } $datarray = array(); $item_thread_top = !$parent ? 1 : 0; if (!$plink && $item_thread_top) { $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $mid; } $datarray['aid'] = $channel['channel_account_id']; $datarray['uid'] = $profile_uid; $datarray['owner_xchan'] = $owner_hash ? $owner_hash : $owner_xchan['xchan_hash']; $datarray['author_xchan'] = $observer['xchan_hash']; $datarray['created'] = $created; $datarray['edited'] = $orig_post ? datetime_convert() : $created; $datarray['expires'] = $expires; $datarray['commented'] = $orig_post ? datetime_convert() : $created; $datarray['received'] = $orig_post ? datetime_convert() : $created; $datarray['changed'] = $orig_post ? datetime_convert() : $created; $datarray['mid'] = $mid; $datarray['parent_mid'] = $parent_mid; $datarray['mimetype'] = $mimetype; $datarray['title'] = $title; $datarray['body'] = $body; $datarray['app'] = $app; $datarray['location'] = $location; $datarray['coord'] = $coord; $datarray['verb'] = $verb; $datarray['obj_type'] = $obj_type; $datarray['allow_cid'] = $str_contact_allow; $datarray['allow_gid'] = $str_group_allow; $datarray['deny_cid'] = $str_contact_deny; $datarray['deny_gid'] = $str_group_deny; $datarray['item_private'] = $private; $datarray['item_wall'] = $item_wall; $datarray['attach'] = $attachments; $datarray['thr_parent'] = $thr_parent; $datarray['postopts'] = $postopts; $datarray['item_unseen'] = $item_unseen; $datarray['item_wall'] = $item_wall; $datarray['item_origin'] = $item_origin; $datarray['item_type'] = $webpage; $datarray['item_thread_top'] = $item_thread_top; $datarray['item_unseen'] = $item_unseen; $datarray['item_starred'] = $item_starred; $datarray['item_uplink'] = $item_uplink; $datarray['item_consensus'] = $item_consensus; $datarray['item_notshown'] = $item_notshown; $datarray['item_nsfw'] = $item_nsfw; $datarray['item_relay'] = $item_relay; $datarray['item_mentionsme'] = $item_mentionsme; $datarray['item_nocomment'] = $item_nocomment; $datarray['item_obscured'] = $item_obscured; $datarray['item_verified'] = $item_verified; $datarray['item_retained'] = $item_retained; $datarray['item_rss'] = $item_rss; $datarray['item_deleted'] = $item_deleted; $datarray['item_hidden'] = $item_hidden; $datarray['item_unpublished'] = $item_unpublished; $datarray['item_delayed'] = $item_delayed; $datarray['item_pending_remove'] = $item_pending_remove; $datarray['item_blocked'] = $item_blocked; $datarray['layout_mid'] = $layout_mid; $datarray['public_policy'] = $public_policy; $datarray['comment_policy'] = map_scope($channel['channel_w_comment']); $datarray['term'] = $post_tags; $datarray['plink'] = $plink; $datarray['route'] = $route; if ($iconfig) { $datarray['iconfig'] = $iconfig; } // preview mode - prepare the body for display and send it via json if ($preview) { require_once 'include/conversation.php'; $datarray['owner'] = $owner_xchan; $datarray['author'] = $observer; $datarray['attach'] = json_encode($datarray['attach']); $o = conversation($a, array($datarray), 'search', false, 'preview'); // logger('preview: ' . $o, LOGGER_DEBUG); echo json_encode(array('preview' => $o)); killme(); } if ($orig_post) { $datarray['edit'] = true; } // suppress duplicates, *unless* you're editing an existing post. This could get picked up // as a duplicate if you're editing it very soon after posting it initially and you edited // some attribute besides the content, such as title or categories. if (feature_enabled($profile_uid, 'suppress_duplicates') && !$orig_post) { $z = q("select created from item where uid = %d and created > %s - INTERVAL %s and body = '%s' limit 1", intval($profile_uid), db_utcnow(), db_quoteinterval('2 MINUTE'), dbesc($body)); if ($z) { $datarray['cancel'] = 1; notice(t('Duplicate post suppressed.') . EOL); logger('Duplicate post. Faking plugin cancel.'); } } call_hooks('post_local', $datarray); if (x($datarray, 'cancel')) { logger('mod_item: post cancelled by plugin or duplicate suppressed.'); if ($return_path) { goaway(z_root() . "/" . $return_path); } $json = array('cancel' => 1); $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; echo json_encode($json); killme(); } if (mb_strlen($datarray['title']) > 255) { $datarray['title'] = mb_substr($datarray['title'], 0, 255); } if (array_key_exists('item_private', $datarray) && $datarray['item_private']) { $datarray['body'] = trim(z_input_filter($datarray['uid'], $datarray['body'], $datarray['mimetype'])); if ($uid) { if ($channel['channel_hash'] === $datarray['author_xchan']) { $datarray['sig'] = base64url_encode(rsa_sign($datarray['body'], $channel['channel_prvkey'])); $datarray['item_verified'] = 1; } } } if ($webpage) { Zlib\IConfig::Set($datarray, 'system', webpage_to_namespace($webpage), $pagetitle ? $pagetitle : substr($datarray['mid'], 0, 16), true); } elseif ($namespace) { Zlib\IConfig::Set($datarray, 'system', $namespace, $remote_id ? $remote_id : substr($datarray['mid'], 0, 16), true); } if ($orig_post) { $datarray['id'] = $post_id; $x = item_store_update($datarray, $execflag); if (!$parent) { $r = q("select * from item where id = %d", intval($post_id)); if ($r) { xchan_query($r); $sync_item = fetch_post_tags($r); build_sync_packet($profile_uid, array('item' => array(encode_item($sync_item[0], true)))); } } if (!$nopush) { \Zotlabs\Daemon\Master::Summon(array('Notifier', 'edit_post', $post_id)); } if (x($_REQUEST, 'return') && strlen($return_path)) { logger('return: ' . $return_path); goaway(z_root() . "/" . $return_path); } killme(); } else { $post_id = 0; } $post = item_store($datarray, $execflag); $post_id = $post['item_id']; if ($post_id) { logger('mod_item: saved item ' . $post_id); if ($parent) { // only send comment notification if this is a wall-to-wall comment, // otherwise it will happen during delivery if ($datarray['owner_xchan'] != $datarray['author_xchan'] && intval($parent_item['item_wall'])) { Zlib\Enotify::submit(array('type' => NOTIFY_COMMENT, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => z_root() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, 'parent_mid' => $parent_item['mid'])); } } else { $parent = $post_id; if ($datarray['owner_xchan'] != $datarray['author_xchan'] && $datarray['item_type'] == ITEM_TYPE_POST) { Zlib\Enotify::submit(array('type' => NOTIFY_WALL, 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, 'link' => z_root() . '/display/' . $datarray['mid'], 'verb' => ACTIVITY_POST, 'otype' => 'item')); } if ($uid && $uid == $profile_uid && is_item_normal($datarray)) { q("update channel set channel_lastpost = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($uid)); } } // photo comments turn the corresponding item visible to the profile wall // This way we don't see every picture in your new photo album posted to your wall at once. // They will show up as people comment on them. if (intval($parent_item['item_hidden'])) { $r = q("UPDATE item SET item_hidden = 0 WHERE id = %d", intval($parent_item['id'])); } } else { logger('mod_item: unable to retrieve post that was just stored.'); notice(t('System error. Post not saved.') . EOL); goaway(z_root() . "/" . $return_path); // NOTREACHED } if ($parent && $parent != $post_id) { // Store the comment signature information in case we need to relay to Diaspora //$ditem = $datarray; //$ditem['author'] = $observer; //store_diaspora_comment_sig($ditem,$channel,$parent_item, $post_id, (($walltowall_comment) ? 1 : 0)); } else { $r = q("select * from item where id = %d", intval($post_id)); if ($r) { xchan_query($r); $sync_item = fetch_post_tags($r); build_sync_packet($profile_uid, array('item' => array(encode_item($sync_item[0], true)))); } } $datarray['id'] = $post_id; $datarray['llink'] = z_root() . '/display/' . $channel['channel_address'] . '/' . $post_id; call_hooks('post_local_end', $datarray); if (!$nopush) { \Zotlabs\Daemon\Master::Summon(array('Notifier', $notify_type, $post_id)); } logger('post_complete'); // figure out how to return, depending on from whence we came if ($api_source) { return $post; } if ($return_path) { goaway(z_root() . "/" . $return_path); } $json = array('success' => 1); if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) { $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; } logger('post_json: ' . print_r($json, true), LOGGER_DEBUG); echo json_encode($json); killme(); // NOTREACHED }
public static function run($argc, $argv) { logger('onepoll: start'); if ($argc > 1 && intval($argv[1])) { $contact_id = intval($argv[1]); } if (!$contact_id) { logger('onepoll: no contact'); return; } $d = datetime_convert(); $contacts = q("SELECT abook.*, xchan.*, account.*\n\t\t\tFROM abook LEFT JOIN account on abook_account = account_id left join xchan on xchan_hash = abook_xchan \n\t\t\twhere abook_id = %d\n\t\t\tand abook_pending = 0 and abook_archived = 0 and abook_blocked = 0 and abook_ignored = 0\n\t\t\tAND (( account_flags = %d ) OR ( account_flags = %d )) limit 1", intval($contact_id), intval(ACCOUNT_OK), intval(ACCOUNT_UNVERIFIED)); if (!$contacts) { logger('onepoll: abook_id not found: ' . $contact_id); return; } $contact = $contacts[0]; $t = $contact['abook_updated']; $importer_uid = $contact['abook_channel']; $r = q("SELECT * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", intval($importer_uid)); if (!$r) { return; } $importer = $r[0]; logger("onepoll: poll: ({$contact['id']}) IMPORTER: {$importer['xchan_name']}, CONTACT: {$contact['xchan_name']}"); $last_update = $contact['abook_updated'] === $contact['abook_created'] || $contact['abook_updated'] <= NULL_DATE ? datetime_convert('UTC', 'UTC', 'now - 7 days') : datetime_convert('UTC', 'UTC', $contact['abook_updated'] . ' - 2 days'); if ($contact['xchan_network'] === 'rss') { logger('onepoll: processing feed ' . $contact['xchan_name'], LOGGER_DEBUG); handle_feed($importer['channel_id'], $contact_id, $contact['xchan_hash']); q("update abook set abook_connected = '%s' where abook_id = %d", dbesc(datetime_convert()), intval($contact['abook_id'])); return; } if ($contact['xchan_network'] !== 'zot') { return; } // update permissions $x = zot_refresh($contact, $importer); $responded = false; $updated = datetime_convert(); $connected = datetime_convert(); if (!$x) { // mark for death by not updating abook_connected, this is caught in include/poller.php q("update abook set abook_updated = '%s' where abook_id = %d", dbesc($updated), intval($contact['abook_id'])); } else { q("update abook set abook_updated = '%s', abook_connected = '%s' where abook_id = %d", dbesc($updated), dbesc($connected), intval($contact['abook_id'])); $responded = true; } if (!$responded) { return; } if ($contact['xchan_connurl']) { $fetch_feed = true; $x = null; // They haven't given us permission to see their stream $can_view_stream = intval(get_abconfig($importer_uid, $contact['abook_xchan'], 'their_perms', 'view_stream')); if (!$can_view_stream) { $fetch_feed = false; } // we haven't given them permission to send us their stream $can_send_stream = intval(get_abconfig($importer_uid, $contact['abook_xchan'], 'my_perms', 'send_stream')); if (!$can_send_stream) { $fetch_feed = false; } if ($fetch_feed) { $feedurl = str_replace('/poco/', '/zotfeed/', $contact['xchan_connurl']); $feedurl .= '?f=&mindate=' . urlencode($last_update); $x = z_fetch_url($feedurl); logger('feed_update: ' . print_r($x, true), LOGGER_DATA); } if ($x && $x['success']) { $total = 0; logger('onepoll: feed update ' . $contact['xchan_name'] . ' ' . $feedurl); $j = json_decode($x['body'], true); if ($j['success'] && $j['messages']) { foreach ($j['messages'] as $message) { $results = process_delivery(array('hash' => $contact['xchan_hash']), get_item_elements($message), array(array('hash' => $importer['xchan_hash'])), false); logger('onepoll: feed_update: process_delivery: ' . print_r($results, true), LOGGER_DATA); $total++; } logger("onepoll: {$total} messages processed"); } } } // update the poco details for this connection if ($contact['xchan_connurl']) { $r = q("SELECT xlink_id from xlink \n\t\t\t\twhere xlink_xchan = '%s' and xlink_updated > %s - INTERVAL %s and xlink_static = 0 limit 1", intval($contact['xchan_hash']), db_utcnow(), db_quoteinterval('1 DAY')); if (!$r) { poco_load($contact['xchan_hash'], $contact['xchan_connurl']); } } return; }
/** * @brief Create a birthday event for any connections with a birthday in the next 1-2 weeks. * * Update the year so that we don't create another event until next year. */ function update_birthdays() { require_once 'include/event.php'; require_once 'include/permissions.php'; $r = q("SELECT * FROM abook left join xchan on abook_xchan = xchan_hash \n\t\tWHERE abook_dob > %s + interval %s and abook_dob < %s + interval %s", db_utcnow(), db_quoteinterval('7 day'), db_utcnow(), db_quoteinterval('14 day')); if ($r) { foreach ($r as $rr) { if (!perm_is_allowed($rr['abook_channel'], $rr['xchan_hash'], 'send_stream')) { continue; } $ev = array(); $ev['uid'] = $rr['abook_channel']; $ev['account'] = $rr['abook_account']; $ev['event_xchan'] = $rr['xchan_hash']; $ev['dtstart'] = datetime_convert('UTC', 'UTC', $rr['abook_dob']); $ev['dtend'] = datetime_convert('UTC', 'UTC', $rr['abook_dob'] . ' + 1 day '); $ev['adjust'] = intval(feature_enabled($rr['abook_channel'], 'smart_birthdays')); $ev['summary'] = sprintf(t('%1$s\'s birthday'), $rr['xchan_name']); $ev['description'] = sprintf(t('Happy Birthday %1$s'), '[zrl=' . $rr['xchan_url'] . ']' . $rr['xchan_name'] . '[/zrl]'); $ev['etype'] = 'birthday'; $z = event_store_event($ev); if ($z) { $item_id = event_store_item($ev, $z); q("update abook set abook_dob = '%s' where abook_id = %d", dbesc(intval($rr['abook_dob']) + 1 . substr($rr['abook_dob'], 4)), intval($rr['abook_id'])); } } } }
public static function run($argc, $argv) { cli_startup(); // perform final cleanup on previously delete items $r = q("select id from item where item_deleted = 1 and item_pending_remove = 0 and changed < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('10 DAY')); if ($r) { foreach ($r as $rr) { drop_item($rr['id'], false, DROPITEM_PHASE2); } } // physically remove anything that has been deleted for more than two months /** @FIXME - this is a wretchedly inefficient query */ $r = q("delete from item where item_pending_remove = 1 and changed < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('36 DAY')); /** @FIXME make this optional as it could have a performance impact on large sites */ if (intval(get_config('system', 'optimize_items'))) { q("optimize table item"); } logger('expire: start', LOGGER_DEBUG); $site_expire = get_config('system', 'default_expire_days'); logger('site_expire: ' . $site_expire); $r = q("SELECT channel_id, channel_system, channel_address, channel_expire_days from channel where true"); if ($r) { foreach ($r as $rr) { // expire the sys channel separately if (intval($rr['channel_system'])) { continue; } // service class default (if non-zero) over-rides the site default $service_class_expire = service_class_fetch($rr['channel_id'], 'expire_days'); if (intval($service_class_expire)) { $channel_expire = $service_class_expire; } else { $channel_expire = $site_expire; } if (intval($channel_expire) && intval($channel_expire) < intval($rr['channel_expire_days']) || intval($rr['channel_expire_days'] == 0)) { $expire_days = $channel_expire; } else { $expire_days = $rr['channel_expire_days']; } // if the site or service class expiration is non-zero and less than person expiration, use that logger('Expire: ' . $rr['channel_address'] . ' interval: ' . $expire_days, LOGGER_DEBUG); item_expire($rr['channel_id'], $expire_days); } } $x = get_sys_channel(); if ($x) { // this should probably just fetch the channel_expire_days from the sys channel, // but there's no convenient way to set it. $expire_days = get_config('system', 'sys_expire_days'); if ($expire_days === false) { $expire_days = 30; } if (intval($site_expire) && intval($site_expire) < intval($expire_days)) { $expire_days = $site_expire; } logger('Expire: sys interval: ' . $expire_days, LOGGER_DEBUG); if ($expire_days) { item_expire($x['channel_id'], $expire_days); } logger('Expire: sys: done', LOGGER_DEBUG); } }
function purge($type, $interval) { q("delete from verify where type = '%s' and created < %s - INTERVAL %s", dbesc($type), db_utcnow(), db_quoteinterval($interval)); }
function statistics_json_cron($a, $b) { logger('statistics_json_cron: cron_start'); $r = q("select count(channel_id) as total_users from channel left join account on account_id = channel_account_id\n\t\twhere account_flags = 0 "); if ($r) { $total_users = $r[0]['total_users']; } $r = q("select channel_id from channel left join account on account_id = channel_account_id\n\t\twhere account_flags = 0 and account_lastlog > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('6 MONTH')); if ($r) { $s = ''; foreach ($r as $rr) { if ($s) { $s .= ','; } $s .= intval($rr['channel_id']); } $x = q("select uid from item where uid in ( {$s} ) and item_wall != 0 and created > %s - INTERVAL %s group by uid", db_utcnow(), db_quoteinterval('6 MONTH')); if ($x) { $active_users_halfyear = count($x); } } $r = q("select channel_id from channel left join account on account_id = channel_account_id\n\t\twhere account_flags = 0 and account_lastlog > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 MONTH')); if ($r) { $s = ''; foreach ($r as $rr) { if ($s) { $s .= ','; } $s .= intval($rr['channel_id']); } $x = q("select uid from item where uid in ( {$s} ) and item_wall != 0 and created > %s - INTERVAL %s group by uid", db_utcnow(), db_quoteinterval('1 MONTH')); if ($x) { $active_users_monthly = count($x); } } set_config('statistics_json', 'total_users', $total_users); set_config('statistics_json', 'active_users_halfyear', $active_users_halfyear); set_config('statistics_json', 'active_users_monthly', $active_users_monthly); $posts = q("SELECT COUNT(*) AS local_posts FROM `item` WHERE item_wall != 0 "); if (!is_array($posts)) { $local_posts = -1; } else { $local_posts = $posts[0]["local_posts"]; } set_config('statistics_json', 'local_posts', $local_posts); $wordpress = false; $r = q("select * from addon where hidden = 0 and name = 'wppost'"); if ($r) { $wordpress = true; } set_config('statistics_json', 'wordpress', intval($wordpress)); $twitter = false; $r = q("select * from addon where hidden = 0 and name = 'twitter'"); if ($r) { $twitter = true; } set_config('statistics_json', 'twitter', intval($twitter)); // Now trying to register $url = "http://the-federation.info/register/" . $a->get_hostname(); $ret = z_fetch_url($url); logger('statistics_json_cron: registering answer: ' . print_r($ret, true), LOGGER_DEBUG); logger('statistics_json_cron: cron_end'); }
function register_content(&$a) { $registration_is = ''; $other_sites = ''; if (get_config('system', 'register_policy') == REGISTER_CLOSED) { require_once 'mod/pubsites.php'; return pubsites_content($a); } if (get_config('system', 'register_policy') == REGISTER_APPROVE) { $registration_is = t('Registration on this site/hub is by approval only.'); $other_sites = t('<a href="pubsites">Register at another affiliated site/hub</a>'); } $max_dailies = intval(get_config('system', 'max_daily_registrations')); if ($max_dailies) { $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 day')); if ($r && $r[0]['total'] >= $max_dailies) { logger('max daily registrations exceeded.'); notice(t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') . EOL); return; } } // Configurable terms of service link $tosurl = get_config('system', 'tos_url'); if (!$tosurl) { $tosurl = $a->get_baseurl() . '/help/TermsOfService'; } $toslink = '<a href="' . $tosurl . '" >' . t('Terms of Service') . '</a>'; // Configurable whether to restrict age or not - default is based on international legal requirements // This can be relaxed if you are on a restricted server that does not share with public servers if (get_config('system', 'no_age_restriction')) { $label_tos = sprintf(t('I accept the %s for this website'), $toslink); } else { $label_tos = sprintf(t('I am over 13 years of age and accept the %s for this website'), $toslink); } $enable_tos = 1 - intval(get_config('system', 'no_termsofservice')); $email = x($_REQUEST, 'email') ? strip_tags(trim($_REQUEST['email'])) : ""; $password = x($_REQUEST, 'password') ? trim($_REQUEST['password']) : ""; $password2 = x($_REQUEST, 'password2') ? trim($_REQUEST['password2']) : ""; $invite_code = x($_REQUEST, 'invite_code') ? strip_tags(trim($_REQUEST['invite_code'])) : ""; require_once 'include/bbcode.php'; $o = replace_macros(get_markup_template('register.tpl'), array('$title' => t('Registration'), '$reg_is' => $registration_is, '$registertext' => bbcode(get_config('system', 'register_text')), '$other_sites' => $other_sites, '$invitations' => get_config('system', 'invitation_only'), '$invite_desc' => t('Membership on this site is by invitation only.'), '$label_invite' => t('Please enter your invitation code'), '$invite_code' => $invite_code, '$label_email' => t('Your email address'), '$label_pass1' => t('Choose a password'), '$label_pass2' => t('Please re-enter your password'), '$label_tos' => $label_tos, '$enable_tos' => $enable_tos, '$email' => $email, '$pass1' => $password, '$pass2' => $password2, '$submit' => t('Register'))); return $o; }
function update_suggestions() { $dirmode = get_config('system', 'directory_mode'); if ($dirmode === false) { $dirmode = DIRECTORY_MODE_NORMAL; } if ($dirmode == DIRECTORY_MODE_PRIMARY || $dirmode == DIRECTORY_MODE_STANDALONE) { $url = z_root() . '/sitelist'; } else { $directory = find_upstream_directory($dirmode); $url = $directory['url'] . '/sitelist'; } if (!$url) { return; } $ret = z_fetch_url($url); if ($ret['success']) { // We will grab fresh data once a day via the poller. Remove anything over a week old because // the targets may have changed their preferences and don't want to be suggested - and they // may have simply gone away. $r = q("delete from xlink where xlink_xchan = '' and xlink_updated < %s - INTERVAL %s and xlink_static = 0", db_utcnow(), db_quoteinterval('7 DAY')); $j = json_decode($ret['body'], true); if ($j && $j['success']) { foreach ($j['entries'] as $host) { poco_load('', $host['url'] . '/poco'); } } } }
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; }
function poller_run($argv, $argc) { cli_startup(); $a = get_app(); $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'); // run queue delivery process in the background proc_run('php', "include/queue.php"); // 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 != '%s' and expires < %s \n\t\tand item_deleted = 0 ", dbesc(NULL_DATE), db_utcnow()); if ($r) { require_once 'include/items.php'; foreach ($r as $rr) { drop_item($rr['id'], false); } } // 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) { proc_run('php', 'include/directory.php', $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) { proc_run('php', 'include/notifier.php', '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')); $dirmode = get_config('system', 'directory_mode'); /** * Cron Daily * * Actions in the following block are executed once per day, not on every poller run * */ if ($d2 != $d1 && $h1 == $h2) { require_once 'include/dir_fns.php'; check_upstream_directory(); call_hooks('cron_daily', datetime_convert()); $d3 = intval(datetime_convert('UTC', 'UTC', 'now', 'N')); if ($d3 == 7) { /** * 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(); require_once 'include/Contact.php'; 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 proc_run('php', 'include/checksites.php'); // update searchable doc indexes proc_run('php', 'include/importdoc.php'); /** * End Cron Weekly */ } update_birthdays(); //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 any read notifications over a month old q("delete from notify where seen = 1 and date < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('30 DAY')); // expire old delivery reports $keep_reports = intval(get_config('system', 'expire_delivery_reports')); if ($keep_reports === 0) { $keep_reports = 30; } 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? if ($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) { require_once 'include/dir_fns.php'; sync_directories($dirmode); } set_config('system', 'last_expire_day', $d2); proc_run('php', 'include/expire.php'); proc_run('php', 'include/cli_suggest.php'); require_once 'include/hubloc.php'; remove_obsolete_hublocs(); /** * End 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\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\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')) { proc_run('php', 'include/externals.php'); } $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(); // TODO check to see if there are any cronhooks before wasting a process if (!$restart) { proc_run('php', 'include/cronhooks.php'); } // 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\tLEFT JOIN account on abook_account = account_id\n\t\twhere abook_self = 0\n\t\t{$sql_extra} \n\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) { proc_run('php', 'include/onepoll.php', $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; } proc_run('php', 'include/onepoll.php', $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; } } proc_run('php', 'include/onedirsync.php', $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; }
function item_expire($uid, $days) { if (!$uid || $days < 1) { return; } // $expire_network_only = save your own wall posts // and just expire conversations started by others // do not enable this until we can pass bulk delete messages through zot // $expire_network_only = get_pconfig($uid,'expire','network_only'); $expire_network_only = 1; $sql_extra = intval($expire_network_only) ? " AND item_wall = 0 " : ""; $expire_limit = get_config('system', 'expire_limit'); if (!intval($expire_limit)) { $expire_limit = 5000; } $item_normal = item_normal(); $r = q("SELECT id FROM item\n\t\tWHERE uid = %d\n\t\tAND created < %s - INTERVAL %s\n\t\tAND item_retained = 0\n\t\tAND item_thread_top = 1\n\t\tAND resource_type = ''\n\t\tAND item_starred = 0\n\t\t{$sql_extra} {$item_normal} LIMIT {$expire_limit} ", intval($uid), db_utcnow(), db_quoteinterval(intval($days) . ' DAY')); if (!$r) { return; } $r = fetch_post_tags($r, true); foreach ($r as $item) { // don't expire filed items $terms = get_terms_oftype($item['term'], TERM_FILE); if ($terms) { retain_item($item['id']); continue; } drop_item($item['id'], false); } // Zotlabs\Daemon\Master::Summon(array('Notifier','expire',$uid)); }
function register_content(&$a) { $registration_is = ''; $other_sites = ''; if (get_config('system', 'register_policy') == REGISTER_CLOSED) { if (get_config('system', 'directory_mode') == DIRECTORY_MODE_STANDALONE) { notice(t('Registration on this site is disabled.') . EOL); return; } require_once 'mod/pubsites.php'; return pubsites_content($a); } if (get_config('system', 'register_policy') == REGISTER_APPROVE) { $registration_is = t('Registration on this site/hub is by approval only.'); $other_sites = t('<a href="pubsites">Register at another affiliated site/hub</a>'); } $max_dailies = intval(get_config('system', 'max_daily_registrations')); if ($max_dailies) { $r = q("select count(account_id) as total from account where account_created > %s - INTERVAL %s", db_utcnow(), db_quoteinterval('1 day')); if ($r && $r[0]['total'] >= $max_dailies) { logger('max daily registrations exceeded.'); notice(t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.') . EOL); return; } } // Configurable terms of service link $tosurl = get_config('system', 'tos_url'); if (!$tosurl) { $tosurl = $a->get_baseurl() . '/help/TermsOfService'; } $toslink = '<a href="' . $tosurl . '" >' . t('Terms of Service') . '</a>'; // Configurable whether to restrict age or not - default is based on international legal requirements // This can be relaxed if you are on a restricted server that does not share with public servers if (get_config('system', 'no_age_restriction')) { $label_tos = sprintf(t('I accept the %s for this website'), $toslink); } else { $label_tos = sprintf(t('I am over 13 years of age and accept the %s for this website'), $toslink); } $enable_tos = 1 - intval(get_config('system', 'no_termsofservice')); $email = x($_REQUEST, 'email') ? strip_tags(trim($_REQUEST['email'])) : ""; $password = x($_REQUEST, 'password') ? trim($_REQUEST['password']) : ""; $password2 = x($_REQUEST, 'password2') ? trim($_REQUEST['password2']) : ""; $invite_code = x($_REQUEST, 'invite_code') ? strip_tags(trim($_REQUEST['invite_code'])) : ""; $name = x($_REQUEST, 'name') ? escape_tags(trim($_REQUEST['name'])) : ""; $nickname = x($_REQUEST, 'nickname') ? strip_tags(trim($_REQUEST['nickname'])) : ""; $privacy_role = x($_REQUEST, 'permissions_role') ? $_REQUEST['permissions_role'] : ""; $auto_create = get_config('system', 'auto_channel_create'); $default_role = get_config('system', 'default_permissions_role'); require_once 'include/bbcode.php'; $o = replace_macros(get_markup_template('register.tpl'), array('$title' => t('Registration'), '$reg_is' => $registration_is, '$registertext' => bbcode(get_config('system', 'register_text')), '$other_sites' => $other_sites, '$invitations' => get_config('system', 'invitation_only'), '$invite_desc' => t('Membership on this site is by invitation only.'), '$label_invite' => t('Please enter your invitation code'), '$invite_code' => $invite_code, '$auto_create' => $auto_create, '$label_name' => t('Channel Name'), '$help_name' => t('Enter your name'), '$label_nick' => t('Choose a short nickname'), '$nick_desc' => t('Your nickname will be used to create an easily remembered channel address (like an email address) which you can share with others.'), '$name' => $name, '$help_role' => t('Please choose a channel type (such as social networking or community forum) and privacy requirements so we can select the best permissions for you'), '$role' => array('permissions_role', t('Channel Type'), $privacy_role ? $privacy_role : 'social', '<a href="help/roles" target="_blank">' . t('Read more about roles') . '</a>', get_roles()), '$default_role' => $default_role, '$nickname' => $nickname, '$submit' => t('Create'), '$label_email' => t('Your email address'), '$label_pass1' => t('Choose a password'), '$label_pass2' => t('Please re-enter your password'), '$label_tos' => $label_tos, '$enable_tos' => $enable_tos, '$email' => $email, '$pass1' => $password, '$pass2' => $password2, '$submit' => t('Register'))); return $o; }
function init() { $result = array(); $notifs = array(); $result['notify'] = 0; $result['home'] = 0; $result['network'] = 0; $result['intros'] = 0; $result['mail'] = 0; $result['register'] = 0; $result['events'] = 0; $result['events_today'] = 0; $result['birthdays'] = 0; $result['birthdays_today'] = 0; $result['all_events'] = 0; $result['all_events_today'] = 0; $result['notice'] = array(); $result['info'] = array(); $t0 = dba_timer(); header("content-type: application/json"); $vnotify = false; $item_normal = item_normal(); if (local_channel()) { $vnotify = get_pconfig(local_channel(), 'system', 'vnotify'); $evdays = intval(get_pconfig(local_channel(), 'system', 'evdays')); $ob_hash = get_observer_hash(); } // if unset show all visual notification types if ($vnotify === false) { $vnotify = -1; } if ($evdays < 1) { $evdays = 3; } /** * If you have several windows open to this site and switch to a different channel * in one of them, the others may get into a confused state showing you a page or options * on that page which were only valid under the old identity. You session has changed. * Therefore we send a notification of this fact back to the browser where it is picked up * in javascript and which reloads the page it is on so that it is valid under the context * of the now current channel. */ $result['invalid'] = intval($_GET['uid']) && intval($_GET['uid']) != local_channel() ? 1 : 0; /** * Send all system messages (alerts) to the browser. * Some are marked as informational and some represent * errors or serious notifications. These typically * will popup on the current page (no matter what page it is) */ if (x($_SESSION, 'sysmsg')) { foreach ($_SESSION['sysmsg'] as $m) { $result['notice'][] = array('message' => $m); } unset($_SESSION['sysmsg']); } if (x($_SESSION, 'sysmsg_info')) { foreach ($_SESSION['sysmsg_info'] as $m) { $result['info'][] = array('message' => $m); } unset($_SESSION['sysmsg_info']); } if (!($vnotify & VNOTIFY_INFO)) { $result['info'] = array(); } if (!($vnotify & VNOTIFY_ALERT)) { $result['notice'] = array(); } if (\App::$install) { echo json_encode($result); killme(); } /** * Update chat presence indication (if applicable) */ if (get_observer_hash() && !$result['invalid']) { $r = q("select cp_id, cp_room from chatpresence where cp_xchan = '%s' and cp_client = '%s' and cp_room = 0 limit 1", dbesc(get_observer_hash()), dbesc($_SERVER['REMOTE_ADDR'])); $basic_presence = false; if ($r) { $basic_presence = true; q("update chatpresence set cp_last = '%s' where cp_id = %d", dbesc(datetime_convert()), intval($r[0]['cp_id'])); } if (!$basic_presence) { q("insert into chatpresence ( cp_xchan, cp_last, cp_status, cp_client)\n\t\t\t\t\tvalues( '%s', '%s', '%s', '%s' ) ", dbesc(get_observer_hash()), dbesc(datetime_convert()), dbesc('online'), dbesc($_SERVER['REMOTE_ADDR'])); } } /** * Chatpresence continued... if somebody hasn't pinged recently, they've most likely left the page * and shouldn't count as online anymore. We allow an expection for bots. */ q("delete from chatpresence where cp_last < %s - INTERVAL %s and cp_client != 'auto' ", db_utcnow(), db_quoteinterval('3 MINUTE')); if (!local_channel() || $result['invalid']) { echo json_encode($result); killme(); } /** * Everything following is only permitted under the context of a locally authenticated site member. */ /** * Handle "mark all xyz notifications read" requests. */ // mark all items read if (x($_REQUEST, 'markRead') && local_channel()) { switch ($_REQUEST['markRead']) { case 'network': $r = q("update item set item_unseen = 0 where item_unseen = 1 and uid = %d", intval(local_channel())); break; case 'home': $r = q("update item set item_unseen = 0 where item_unseen = 1 and item_wall = 1 and uid = %d", intval(local_channel())); break; case 'messages': $r = q("update mail set mail_seen = 1 where mail_seen = 0 and channel_id = %d ", intval(local_channel())); break; case 'all_events': $r = q("update event set `ignore` = 1 where `ignore` = 0 and uid = %d AND start < '%s' AND start > '%s' ", intval(local_channel()), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))); break; case 'notify': $r = q("update notify set seen = 1 where uid = %d", intval(local_channel())); break; default: break; } } if (x($_REQUEST, 'markItemRead') && local_channel()) { $r = q("update item set item_unseen = 0 where parent = %d and uid = %d", intval($_REQUEST['markItemRead']), intval(local_channel())); } /** * URL ping/something will return detail for "something", e.g. a json list with which to populate a notification * dropdown menu. */ if (argc() > 1 && argv(1) === 'notify') { $t = q("select count(*) as total from notify where uid = %d and seen = 0", intval(local_channel())); if ($t && intval($t[0]['total']) > 49) { $z = q("select * from notify where uid = %d\n\t\t\t\t\tand seen = 0 order by date desc limit 50", intval(local_channel())); } else { $z1 = q("select * from notify where uid = %d\n\t\t\t\t\tand seen = 0 order by date desc limit 50", intval(local_channel())); $z2 = q("select * from notify where uid = %d\n\t\t\t\t\tand seen = 1 order by date desc limit %d", intval(local_channel()), intval(50 - intval($t[0]['total']))); $z = array_merge($z1, $z2); } if (count($z)) { foreach ($z as $zz) { $notifs[] = array('notify_link' => z_root() . '/notify/view/' . $zz['id'], 'name' => $zz['name'], 'url' => $zz['url'], 'photo' => $zz['photo'], 'when' => relative_date($zz['date']), 'hclass' => $zz['seen'] ? 'notify-seen' : 'notify-unseen', 'message' => strip_tags(bbcode($zz['msg']))); } } echo json_encode(array('notify' => $notifs)); killme(); } if (argc() > 1 && argv(1) === 'messages') { $channel = \App::get_channel(); $t = q("select mail.*, xchan.* from mail left join xchan on xchan_hash = from_xchan \n\t\t\t\twhere channel_id = %d and mail_seen = 0 and mail_deleted = 0 \n\t\t\t\tand from_xchan != '%s' order by created desc limit 50", intval(local_channel()), dbesc($channel['channel_hash'])); if ($t) { foreach ($t as $zz) { $notifs[] = array('notify_link' => z_root() . '/mail/' . $zz['id'], 'name' => $zz['xchan_name'], 'url' => $zz['xchan_url'], 'photo' => $zz['xchan_photo_s'], 'when' => relative_date($zz['created']), 'hclass' => intval($zz['mail_seen']) ? 'notify-seen' : 'notify-unseen', 'message' => t('sent you a private message')); } } echo json_encode(array('notify' => $notifs)); killme(); } if (argc() > 1 && (argv(1) === 'network' || argv(1) === 'home')) { $result = array(); $r = q("SELECT * FROM item\n\t\t\t\tWHERE item_unseen = 1 and uid = %d {$item_normal}\n\t\t\t\tand author_xchan != '%s' ORDER BY created DESC limit 300", intval(local_channel()), dbesc($ob_hash)); if ($r) { xchan_query($r); foreach ($r as $item) { if (argv(1) === 'home' && !intval($item['item_wall'])) { continue; } $result[] = format_notification($item); } } // logger('ping (network||home): ' . print_r($result, true), LOGGER_DATA); echo json_encode(array('notify' => $result)); killme(); } if (argc() > 1 && argv(1) === 'intros') { $result = array(); $r = q("SELECT * FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ORDER BY abook_created DESC LIMIT 50", intval(local_channel())); if ($r) { foreach ($r as $rr) { $result[] = array('notify_link' => z_root() . '/connections/ifpending', 'name' => $rr['xchan_name'], 'url' => $rr['xchan_url'], 'photo' => $rr['xchan_photo_s'], 'when' => relative_date($rr['abook_created']), 'hclass' => 'notify-unseen', 'message' => t('added your channel')); } } logger('ping (intros): ' . print_r($result, true), LOGGER_DATA); echo json_encode(array('notify' => $result)); killme(); } if (argc() > 1 && argv(1) === 'all_events') { $bd_format = t('g A l F d'); // 8 AM Friday January 18 $result = array(); $r = q("SELECT * FROM event left join xchan on event_xchan = xchan_hash\n\t\t\t\tWHERE `event`.`uid` = %d AND start < '%s' AND start > '%s' and `ignore` = 0\n\t\t\t\tand type in ( 'event', 'birthday' )\n\t\t\t\tORDER BY `start` DESC LIMIT 1000", intval(local_channel()), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))); if ($r) { foreach ($r as $rr) { if ($rr['adjust']) { $md = datetime_convert('UTC', date_default_timezone_get(), $rr['start'], 'Y/m'); } else { $md = datetime_convert('UTC', 'UTC', $rr['start'], 'Y/m'); } $strt = datetime_convert('UTC', $rr['adjust'] ? date_default_timezone_get() : 'UTC', $rr['start']); $today = substr($strt, 0, 10) === datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d') ? true : false; $when = day_translate(datetime_convert('UTC', $rr['adjust'] ? date_default_timezone_get() : 'UTC', $rr['start'], $bd_format)) . ($today ? ' ' . t('[today]') : ''); $result[] = array('notify_link' => z_root() . '/events', 'name' => $rr['xchan_name'], 'url' => $rr['xchan_url'], 'photo' => $rr['xchan_photo_s'], 'when' => $when, 'hclass' => 'notify-unseen', 'message' => t('posted an event')); } } logger('ping (all_events): ' . print_r($result, true), LOGGER_DATA); echo json_encode(array('notify' => $result)); killme(); } /** * Normal ping - just the counts, no detail */ if ($vnotify & VNOTIFY_SYSTEM) { $t = q("select count(*) as total from notify where uid = %d and seen = 0", intval(local_channel())); if ($t) { $result['notify'] = intval($t[0]['total']); } } $t1 = dba_timer(); if ($vnotify & (VNOTIFY_NETWORK | VNOTIFY_CHANNEL)) { $r = q("SELECT id, item_wall FROM item\n\t\t\t\tWHERE item_unseen = 1 and uid = %d\n\t\t\t\t{$item_normal}\n\t\t\t\tand author_xchan != '%s'", intval(local_channel()), dbesc($ob_hash)); if ($r) { $arr = array('items' => $r); call_hooks('network_ping', $arr); foreach ($r as $it) { if (intval($it['item_wall'])) { $result['home']++; } else { $result['network']++; } } } } if (!($vnotify & VNOTIFY_NETWORK)) { $result['network'] = 0; } if (!($vnotify & VNOTIFY_CHANNEL)) { $result['home'] = 0; } $t2 = dba_timer(); if ($vnotify & VNOTIFY_INTRO) { $intr = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ", intval(local_channel())); $t3 = dba_timer(); if ($intr) { $result['intros'] = intval($intr[0]['total']); } } $t4 = dba_timer(); $channel = \App::get_channel(); if ($vnotify & VNOTIFY_MAIL) { $mails = q("SELECT count(id) as total from mail\n\t\t\t\tWHERE channel_id = %d AND mail_seen = 0 and from_xchan != '%s' ", intval(local_channel()), dbesc($channel['channel_hash'])); if ($mails) { $result['mail'] = intval($mails[0]['total']); } } if ($vnotify & VNOTIFY_REGISTER) { if (\App::$config['system']['register_policy'] == REGISTER_APPROVE && is_site_admin()) { $regs = q("SELECT count(account_id) as total from account where (account_flags & %d) > 0", intval(ACCOUNT_PENDING)); if ($regs) { $result['register'] = intval($regs[0]['total']); } } } $t5 = dba_timer(); if ($vnotify & (VNOTIFY_EVENT | VNOTIFY_EVENTTODAY | VNOTIFY_BIRTHDAY)) { $events = q("SELECT type, start, adjust FROM `event`\n\t\t\t\tWHERE `event`.`uid` = %d AND start < '%s' AND start > '%s' and `ignore` = 0\n\t\t\t\tand type in ( 'event', 'birthday' )\n\t\t\t\tORDER BY `start` ASC ", intval(local_channel()), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')), dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))); if ($events) { $result['all_events'] = count($events); if ($result['all_events']) { $str_now = datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d'); foreach ($events as $x) { $bd = false; if ($x['type'] === 'birthday') { $result['birthdays']++; $bd = true; } else { $result['events']++; } if (datetime_convert('UTC', intval($x['adjust']) ? date_default_timezone_get() : 'UTC', $x['start'], 'Y-m-d') === $str_now) { $result['all_events_today']++; if ($bd) { $result['birthdays_today']++; } else { $result['events_today']++; } } } } } } if (!($vnotify & VNOTIFY_EVENT)) { $result['all_events'] = $result['events'] = 0; } if (!($vnotify & VNOTIFY_EVENTTODAY)) { $result['all_events_today'] = $result['events_today'] = 0; } if (!($vnotify & VNOTIFY_BIRTHDAY)) { $result['birthdays'] = 0; } $x = json_encode($result); $t6 = dba_timer(); // logger('ping timer: ' . sprintf('%01.4f %01.4f %01.4f %01.4f %01.4f %01.4f',$t6 - $t5, $t5 - $t4, $t4 - $t3, $t3 - $t2, $t2 - $t1, $t1 - $t0)); echo $x; killme(); }
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 random_profile() { $randfunc = db_getfunc('rand'); $checkrandom = get_config('randprofile', 'check'); // False by default $retryrandom = intval(get_config('randprofile', 'retry')); if ($retryrandom == 0) { $retryrandom = 5; } for ($i = 0; $i < $retryrandom; $i++) { $r = q("select xchan_url from xchan left join hubloc on hubloc_hash = xchan_hash where hubloc_connected > %s - interval %s order by {$randfunc} limit 1", db_utcnow(), db_quoteinterval('30 day')); if (!$r) { return ''; } // Couldn't get a random channel if ($checkrandom) { $x = z_fetch_url($r[0]['xchan_url']); if ($x['success']) { return $r[0]['xchan_url']; } else { logger('Random channel turned out to be bad.'); } } else { return $r[0]['xchan_url']; } } 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(); $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']; } } 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 abook_self = 0\n\t\t\tand not (hubloc_flags & %d) > 0 and not (hubloc_status & %d) > 0 limit 1", intval($item_id), 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) { $perm_update = array('sender' => $s[0], 'recipient' => $r[0], 'success' => false, 'deliveries' => ''); call_hooks('permissions_update', $perm_update); if ($perm_update['success'] && $perm_update['deliveries']) { $deliveries[] = $perm_update['deliveries']; } if (!$perm_update['success']) { // 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) { if (in_array($hh['hubloc_url'], $dead_hubs)) { logger('skipping dead hub: ' . $hh['hubloc_url'], LOGGER_DEBUG); continue; } $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) { $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\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('')); $deliveries[] = $hash; } } } } if ($deliveries) { do_delivery($deliveries); } } } 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_wall = 1\n\t\t\tAND item_deleted = 1 AND `changed` > %s - INTERVAL %s", intval($item_id), 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 (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_publish'])) { 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']}"); 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 && 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); logger('notifier: top_level_post: ' . ($top_level_post ? 'true' : 'false'), LOGGER_DATA); // 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 (intval($parent_item['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 (intval($target_item['item_deleted']) && !intval($target_item['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) { $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); } } } } 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. $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'); 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); 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); foreach ($dhubs as $hub) { if ($hub['hubloc_network'] !== 'zot') { $narr = 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, 'queued' => array()); call_hooks('notifier_hub', $narr); if ($narr['queued']) { foreach ($narr['queued'] as $pq) { $deliveries[] = $pq; } } 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))); // only create delivery reports for normal undeleted items if (array_key_exists('postopts', $target_item) && !$target_item['item_deleted']) { 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 item_expire($uid, $days) { if (!$uid || $days < 1) { return; } // $expire_network_only = save your own wall posts // and just expire conversations started by others // do not enable this until we can pass bulk delete messages through zot // $expire_network_only = get_pconfig($uid,'expire','network_only'); $expire_network_only = 1; $expire_limit = get_config('system', 'expire_limit'); if (!intval($expire_limit)) { $expire_limit = 5000; } $sql_extra = intval($expire_network_only) ? " AND (item_flags & " . intval(ITEM_WALL) . ") = 0 " : ""; $r = q("SELECT * FROM `item`\n\t\tWHERE `uid` = %d\n\t\tAND `created` < %s - INTERVAL %s\n\t\tAND `id` = `parent`\n\t\t{$sql_extra}\n\t\tAND ( item_flags & %d ) = 0\n\t\tAND ( item_restrict = 0 ) LIMIT {$expire_limit} ", intval($uid), db_utcnow(), db_quoteinterval(intval($days) . ' DAY'), intval(ITEM_RETAINED)); if (!$r) { return; } $r = fetch_post_tags($r, true); foreach ($r as $item) { // don't expire filed items $terms = get_terms_oftype($item['term'], TERM_FILE); if ($terms) { retain_item($item['id']); continue; } // Only expire posts, not photos and photo comments if ($item['resource_type'] === 'photo') { retain_item($item['id']); continue; } if ($item['item_flags'] & ITEM_STARRED) { retain_item($item['id']); continue; } drop_item($item['id'], false); } // proc_run('php',"include/notifier.php","expire","$uid"); }
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; }