function pubsub_init(&$a) { $nick = argc() > 1 ? escape_tags(trim(argv(1))) : ''; $contact_id = argc() > 2 ? intval(argv(2)) : 0; if ($_SERVER['REQUEST_METHOD'] === 'GET') { $hub_mode = x($_GET, 'hub_mode') ? notags(trim($_GET['hub_mode'])) : ''; $hub_topic = x($_GET, 'hub_topic') ? notags(trim($_GET['hub_topic'])) : ''; $hub_challenge = x($_GET, 'hub_challenge') ? notags(trim($_GET['hub_challenge'])) : ''; $hub_lease = x($_GET, 'hub_lease_seconds') ? notags(trim($_GET['hub_lease_seconds'])) : ''; $hub_verify = x($_GET, 'hub_verify_token') ? notags(trim($_GET['hub_verify_token'])) : ''; logger('pubsub: Subscription from ' . $_SERVER['REMOTE_ADDR']); logger('pubsub: data: ' . print_r($_GET, true), LOGGER_DATA); $subscribe = $hub_mode === 'subscribe' ? 1 : 0; $channel = channelx_by_nick($nick); if (!$channel) { http_status_exit(404, 'not found.'); } $connections = abook_connections($channel['channel_id'], ' and abook_id = ' . $contact_id); if ($connections) { $xchan = $connections[0]; } else { logger('connection ' . $contact_id . ' not found.'); http_status_exit(404, 'not found.'); } if ($hub_verify) { $verify = get_abconfig($channel['channel_id'], $xchan['xchan_hash'], 'pubsubhubbub', 'verify_token'); if ($verify != $hub_verify) { logger('hub verification failed.'); http_status_exit(404, 'not found.'); } } $feed_url = z_root() . '/feed/' . $channel['channel_address']; if ($hub_topic) { if (!link_compare($hub_topic, $feed_url)) { logger('hub topic ' . $hub_topic . ' != ' . $feed_url); // should abort but let's humour them. } } $contact = $r[0]; // We must initiate an unsubscribe request with a verify_token. // Don't allow outsiders to unsubscribe us. if ($hub_mode === 'unsubscribe') { if (!strlen($hub_verify)) { logger('pubsub: bogus unsubscribe'); http_status_exit(403, 'permission denied.'); } logger('pubsub: unsubscribe success'); } if ($hub_mode) { set_abconfig($channel['channel_id'], $xchan['xchan_hash'], 'pubsubhubbub', 'subscribed', intval($subscribe)); } header($_SERVER["SERVER_PROTOCOL"] . ' 200 ' . 'OK'); echo $hub_challenge; killme(); } }
function pubsub_init(&$a) { $nick = $a->argc > 1 ? notags(trim($a->argv[1])) : ''; $contact_id = $a->argc > 2 ? intval($a->argv[2]) : 0; if ($_SERVER['REQUEST_METHOD'] === 'GET') { $hub_mode = x($_GET, 'hub_mode') ? notags(trim($_GET['hub_mode'])) : ''; $hub_topic = x($_GET, 'hub_topic') ? notags(trim($_GET['hub_topic'])) : ''; $hub_challenge = x($_GET, 'hub_challenge') ? notags(trim($_GET['hub_challenge'])) : ''; $hub_lease = x($_GET, 'hub_lease_seconds') ? notags(trim($_GET['hub_lease_seconds'])) : ''; $hub_verify = x($_GET, 'hub_verify_token') ? notags(trim($_GET['hub_verify_token'])) : ''; logger('pubsub: Subscription from ' . $_SERVER['REMOTE_ADDR']); logger('pubsub: data: ' . print_r($_GET, true), LOGGER_DATA); $subscribe = $hub_mode === 'subscribe' ? 1 : 0; $r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nick)); if (!count($r)) { logger('pubsub: local account not found: ' . $nick); hub_return(false, ''); } $owner = $r[0]; $sql_extra = strlen($hub_verify) ? sprintf(" AND `hub-verify` = '%s' ", dbesc($hub_verify)) : ''; $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d \n\t\t\tAND `blocked` = 0 AND `pending` = 0 {$sql_extra} LIMIT 1", intval($contact_id), intval($owner['uid'])); if (!count($r)) { logger('pubsub: contact ' . $contact_id . ' not found.'); hub_return(false, ''); } if ($hub_topic) { if (!link_compare($hub_topic, $r[0]['poll'])) { logger('pubsub: hub topic ' . $hub_topic . ' != ' . $r[0]['poll']); // should abort but let's humour them. } } $contact = $r[0]; // We must initiate an unsubscribe request with a verify_token. // Don't allow outsiders to unsubscribe us. if ($hub_mode === 'unsubscribe') { if (!strlen($hub_verify)) { logger('pubsub: bogus unsubscribe'); hub_return(false, ''); } logger('pubsub: unsubscribe success'); } if ($hub_mode) { $r = q("UPDATE `contact` SET `subhub` = %d WHERE `id` = %d", intval($subscribe), intval($contact['id'])); } hub_return(true, $hub_challenge); } }
function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. if (get_config('system', 'block_public')) { http_status_exit(403); } // Subscription request from subscriber // https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4 // Example from GNU Social: // [hub_mode] => subscribe // [hub_callback] => http://status.local/main/push/callback/1 // [hub_verify] => sync // [hub_verify_token] => af11... // [hub_secret] => af11... // [hub_topic] => http://friendica.local/dfrn_poll/sazius if ($_SERVER['REQUEST_METHOD'] === 'POST') { $hub_mode = push_post_var('hub_mode'); $hub_callback = push_post_var('hub_callback'); $hub_verify = push_post_var('hub_verify'); $hub_verify_token = push_post_var('hub_verify_token'); $hub_secret = push_post_var('hub_secret'); $hub_topic = push_post_var('hub_topic'); // check for valid hub_mode if ($hub_mode === 'subscribe') { $subscribe = 1; } else { if ($hub_mode === 'unsubscribe') { $subscribe = 0; } else { logger("pubsubhubbub: invalid hub_mode={$hub_mode}, ignoring."); http_status_exit(404); } } logger("pubsubhubbub: {$hub_mode} request from " . $_SERVER['REMOTE_ADDR']); // get the nick name from the topic, a bit hacky but needed $nick = substr(strrchr($hub_topic, "/"), 1); if (!$nick) { logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.'); http_status_exit(404); } // fetch user from database given the nickname $owner = channelx_by_nick($nick); if (!$owner) { logger('pubsubhubbub: local account not found: ' . $nick); http_status_exit(404); } if (!perm_is_allowed($owner['channel_id'], '', 'view_stream')) { logger('pubsubhubbub: local channel ' . $nick . 'has chosen to hide wall, ignoring.'); http_status_exit(403); } // sanity check that topic URLs are the same if (!link_compare($hub_topic, z_root() . '/feed/' . $nick)) { logger('pubsubhubbub: not a valid hub topic ' . $hub_topic); http_status_exit(404); } // do subscriber verification according to the PuSH protocol $hub_challenge = random_string(40); $params = 'hub.mode=' . ($subscribe == 1 ? 'subscribe' : 'unsubscribe') . '&hub.topic=' . urlencode($hub_topic) . '&hub.challenge=' . $hub_challenge . '&hub.lease_seconds=604800' . '&hub.verify_token=' . $hub_verify_token; // lease time is hard coded to one week (in seconds) // we don't actually enforce the lease time because GNU // Social/StatusNet doesn't honour it (yet) $x = z_fetch_url($hub_callback . "?" . $params); if (!$x['success']) { logger("pubsubhubbub: subscriber verification at {$hub_callback} " . "returned {$ret}, ignoring."); http_status_exit(404); } // check that the correct hub_challenge code was echoed back if (trim($x['body']) !== $hub_challenge) { logger("pubsubhubbub: subscriber did not echo back " . "hub.challenge, ignoring."); logger("\"{$hub_challenge}\" != \"" . trim($x['body']) . "\""); http_status_exit(404); } // fetch the old subscription if it exists $orig = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); // delete old subscription if it exists q("DELETE FROM push_subscriber WHERE callback_url = '%s' and topic = '%s'", dbesc($hub_callback), dbesc($hub_topic)); if ($subscribe) { $last_update = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); // if we are just updating an old subscription, keep the // old values for last_update if ($orig) { $last_update = $orig[0]['last_update']; } // subscribe means adding the row to the table q("INSERT INTO push_subscriber ( callback_url, topic, last_update, secret) values ('%s', '%s', '%s', '%s') ", dbesc($hub_callback), dbesc($hub_topic), dbesc($last_update), dbesc($hub_secret)); logger("pubsubhubbub: successfully subscribed [{$hub_callback}]."); } else { logger("pubsubhubbub: successfully unsubscribed [{$hub_callback}]."); // we do nothing here, since the row was already deleted } http_status_exit(202); } killme(); }
function render_content(&$a, $items, $mode, $update, $preview = false) { require_once 'include/bbcode.php'; require_once 'mod/proxy.php'; $ssl_state = local_user() ? true : false; $profile_owner = 0; $page_writeable = false; $previewing = $preview ? ' preview ' : ''; if ($mode === 'network') { $profile_owner = local_user(); $page_writeable = true; } if ($mode === 'profile') { $profile_owner = $a->profile['profile_uid']; $page_writeable = can_write_wall($a, $profile_owner); } if ($mode === 'notes') { $profile_owner = local_user(); $page_writeable = true; } if ($mode === 'display') { $profile_owner = $a->profile['uid']; $page_writeable = can_write_wall($a, $profile_owner); } if ($mode === 'community') { $profile_owner = 0; $page_writeable = false; } if ($update) { $return_url = $_SESSION['return_url']; } else { $return_url = $_SESSION['return_url'] = $a->query_string; } load_contact_links(local_user()); $cb = array('items' => $items, 'mode' => $mode, 'update' => $update, 'preview' => $preview); call_hooks('conversation_start', $cb); $items = $cb['items']; $cmnt_tpl = get_markup_template('comment_item.tpl'); $tpl = 'wall_item.tpl'; $wallwall = 'wallwall_item.tpl'; $hide_comments_tpl = get_markup_template('hide_comments.tpl'); $alike = array(); $dlike = array(); // array with html for each thread (parent+comments) $threads = array(); $threadsid = -1; if ($items && count($items)) { if ($mode === 'network-new' || $mode === 'search' || $mode === 'community') { // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display //$tpl = get_markup_template('search_item.tpl'); $tpl = 'search_item.tpl'; foreach ($items as $item) { $threadsid++; $comment = ''; $owner_url = ''; $owner_photo = ''; $owner_name = ''; $sparkle = ''; if ($mode === 'search' || $mode === 'community') { if ((activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)) && $item['id'] != $item['parent']) { continue; } $nickname = $item['nickname']; } else { $nickname = $a->user['nickname']; } // prevent private email from leaking. if ($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) { continue; } $profile_name = strlen($item['author-name']) ? $item['author-name'] : $item['name']; if ($item['author-link'] && !$item['author-name']) { $profile_name = $item['author-link']; } $sp = false; $profile_link = best_link_url($item, $sp); if ($profile_link === 'mailbox') { $profile_link = ''; } if ($sp) { $sparkle = ' sparkle'; } else { $profile_link = zrl($profile_link); } $normalised = normalise_link(strlen($item['author-link']) ? $item['author-link'] : $item['url']); if ($normalised != 'mailbox' && x($a->contacts[$normalised])) { $profile_avatar = $a->contacts[$normalised]['thumb']; } else { $profile_avatar = strlen($item['author-avatar']) ? $a->get_cached_avatar_image($item['author-avatar']) : $item['thumb']; } $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); call_hooks('render_location', $locate); $location = strlen($locate['html']) ? $locate['html'] : render_location_dummy($locate); localize_item($item); if ($mode === 'network-new') { $dropping = true; } else { $dropping = false; } $drop = array('dropping' => $dropping, 'select' => t('Select'), 'delete' => t('Delete')); $star = false; $isstarred = "unstarred"; $lock = false; $likebuttons = false; $shareable = false; $body = prepare_body($item, true); if ($a->theme['template_engine'] === 'internal') { $name_e = template_escape($profile_name); $title_e = template_escape($item['title']); $body_e = template_escape($body); $text_e = strip_tags(template_escape($body)); $location_e = template_escape($location); $owner_name_e = template_escape($owner_name); } else { $name_e = $profile_name; $title_e = $item['title']; $body_e = $body; $text_e = strip_tags($body); $location_e = $location; $owner_name_e = $owner_name; } //$tmp_item = replace_macros($tpl,array( $tmp_item = array('template' => $tpl, 'id' => $preview ? 'P0' : $item['item_id'], 'linktitle' => sprintf(t('View %s\'s profile @ %s'), $profile_name, strlen($item['author-link']) ? $item['author-link'] : $item['url']), 'profile_url' => $profile_link, 'item_photo_menu' => item_photo_menu($item), 'name' => $name_e, 'sparkle' => $sparkle, 'lock' => $lock, 'thumb' => proxy_url($profile_avatar), 'title' => $title_e, 'body' => $body_e, 'text' => $text_e, 'ago' => $item['app'] ? sprintf(t('%s from %s'), relative_date($item['created']), $item['app']) : relative_date($item['created']), 'location' => $location_e, 'indent' => '', 'owner_name' => $owner_name_e, 'owner_url' => $owner_url, 'owner_photo' => proxy_url($owner_photo), 'plink' => get_plink($item), 'edpost' => false, 'isstarred' => $isstarred, 'star' => $star, 'drop' => $drop, 'vote' => $likebuttons, 'like' => '', 'dislike' => '', 'comment' => '', 'conv' => $preview ? '' : array('href' => $a->get_baseurl($ssl_state) . '/display/' . $item['guid'], 'title' => t('View in context')), 'previewing' => $previewing, 'wait' => t('Please wait')); $arr = array('item' => $item, 'output' => $tmp_item); call_hooks('display_item', $arr); $threads[$threadsid]['id'] = $item['item_id']; $threads[$threadsid]['items'] = array($arr['output']); } } else { // Normal View // Figure out how many comments each parent has // (Comments all have gravity of 6) // Store the result in the $comments array $comments = array(); foreach ($items as $item) { if (intval($item['gravity']) == 6 && $item['id'] != $item['parent']) { if (!x($comments, $item['parent'])) { $comments[$item['parent']] = 1; } else { $comments[$item['parent']] += 1; } } elseif (!x($comments, $item['parent'])) { $comments[$item['parent']] = 0; } // avoid notices later on } // map all the like/dislike activities for each parent item // Store these in the $alike and $dlike arrays foreach ($items as $item) { like_puller($a, $item, $alike, 'like'); like_puller($a, $item, $dlike, 'dislike'); } $comments_collapsed = false; $comments_seen = 0; $comment_lastcollapsed = false; $comment_firstcollapsed = false; $blowhard = 0; $blowhard_count = 0; foreach ($items as $item) { $comment = ''; $template = $tpl; $commentww = ''; $sparkle = ''; $owner_url = $owner_photo = $owner_name = ''; // We've already parsed out like/dislike for special treatment. We can ignore them now if ((activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)) && $item['id'] != $item['parent']) { continue; } $toplevelpost = $item['id'] == $item['parent'] ? true : false; // Take care of author collapsing and comment collapsing // (author collapsing is currently disabled) // If a single author has more than 3 consecutive top-level posts, squash the remaining ones. // If there are more than two comments, squash all but the last 2. if ($toplevelpost) { $item_writeable = $item['writable'] || $item['self'] ? true : false; $comments_seen = 0; $comments_collapsed = false; $comment_lastcollapsed = false; $comment_firstcollapsed = false; $threadsid++; $threads[$threadsid]['id'] = $item['item_id']; $threads[$threadsid]['private'] = $item['private']; $threads[$threadsid]['items'] = array(); } else { // prevent private email reply to public conversation from leaking. if ($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) { continue; } $comments_seen++; $comment_lastcollapsed = false; $comment_firstcollapsed = false; } $override_comment_box = $page_writeable && $item_writeable ? true : false; $show_comment_box = $page_writeable && $item_writeable && $comments_seen == $comments[$item['parent']] ? true : false; if ($comments[$item['parent']] > 2 && $comments_seen <= $comments[$item['parent']] - 2 && $item['gravity'] == 6) { if (!$comments_collapsed) { $threads[$threadsid]['num_comments'] = sprintf(tt('%d comment', '%d comments', $comments[$item['parent']]), $comments[$item['parent']]); $threads[$threadsid]['hidden_comments_num'] = $comments[$item['parent']]; $threads[$threadsid]['hidden_comments_text'] = tt('comment', 'comments', $comments[$item['parent']]); $threads[$threadsid]['hide_text'] = t('show more'); $comments_collapsed = true; $comment_firstcollapsed = true; } } if ($comments[$item['parent']] > 2 && $comments_seen == $comments[$item['parent']] - 1) { $comment_lastcollapsed = true; } $redirect_url = $a->get_baseurl($ssl_state) . '/redir/' . $item['cid']; $lock = $item['private'] == 1 || $item['uid'] == local_user() && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) ? t('Private Message') : false; // Top-level wall post not written by the wall owner (wall-to-wall) // First figure out who owns it. $osparkle = ''; if ($toplevelpost && !$item['self'] && $mode !== 'profile') { if ($item['wall']) { // On the network page, I am the owner. On the display page it will be the profile owner. // This will have been stored in $a->page_contact by our calling page. // Put this person as the wall owner of the wall-to-wall notice. $owner_url = zrl($a->page_contact['url']); $owner_photo = $a->page_contact['thumb']; $owner_name = $a->page_contact['name']; $template = $wallwall; $commentww = 'ww'; } if (!$item['wall'] && $item['owner-link']) { $owner_linkmatch = $item['owner-link'] && link_compare($item['owner-link'], $item['author-link']); $alias_linkmatch = $item['alias'] && link_compare($item['alias'], $item['author-link']); $owner_namematch = $item['owner-name'] && $item['owner-name'] == $item['author-name']; if (!$owner_linkmatch && !$alias_linkmatch && !$owner_namematch) { // The author url doesn't match the owner (typically the contact) // and also doesn't match the contact alias. // The name match is a hack to catch several weird cases where URLs are // all over the park. It can be tricked, but this prevents you from // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn // well that it's the same Bob Smith. // But it could be somebody else with the same name. It just isn't highly likely. $owner_url = $item['owner-link']; $owner_photo = $item['owner-avatar']; $owner_name = $item['owner-name']; $template = $wallwall; $commentww = 'ww'; // If it is our contact, use a friendly redirect link if (link_compare($item['owner-link'], $item['url']) && $item['network'] === NETWORK_DFRN) { $owner_url = $redirect_url; $osparkle = ' sparkle'; } else { $owner_url = zrl($owner_url); } } } } $likebuttons = ''; $shareable = $profile_owner == local_user() && $item['private'] != 1 ? true : false; if ($page_writeable) { /* if($toplevelpost) { */ $likebuttons = array('like' => array(t("I like this (toggle)"), t("like")), 'dislike' => array(t("I don't like this (toggle)"), t("dislike"))); if ($shareable) { $likebuttons['share'] = array(t('Share this'), t('share')); } /* } */ $qc = $qcomment = null; if (in_array('qcomment', $a->plugins)) { $qc = local_user() ? get_pconfig(local_user(), 'qcomment', 'words') : null; $qcomment = $qc ? explode("\n", $qc) : null; } if ($show_comment_box || $show_comment_box == false && $override_comment_box == false && $item['last-child']) { $comment = replace_macros($cmnt_tpl, array('$return_path' => '', '$jsreload' => $mode === 'display' ? $_SESSION['return_url'] : '', '$type' => $mode === 'profile' ? 'wall-comment' : 'net-comment', '$id' => $item['item_id'], '$parent' => $item['parent'], '$qcomment' => $qcomment, '$profile_uid' => $profile_owner, '$mylink' => $a->contact['url'], '$mytitle' => t('This is you'), '$myphoto' => $a->contact['thumb'], '$comment' => t('Comment'), '$submit' => t('Submit'), '$edbold' => t('Bold'), '$editalic' => t('Italic'), '$eduline' => t('Underline'), '$edquote' => t('Quote'), '$edcode' => t('Code'), '$edimg' => t('Image'), '$edurl' => t('Link'), '$edvideo' => t('Video'), '$preview' => t('Preview'), '$sourceapp' => t($a->sourcename), '$ww' => $mode === 'network' ? $commentww : '', '$rand_num' => random_digits(12))); } } if (local_user() && link_compare($a->contact['url'], $item['author-link'])) { $edpost = array($a->get_baseurl($ssl_state) . "/editpost/" . $item['id'], t("Edit")); } else { $edpost = false; } $drop = ''; $dropping = false; if (intval($item['contact-id']) && $item['contact-id'] == remote_user() || $item['uid'] == local_user()) { $dropping = true; } $drop = array('dropping' => $dropping, 'select' => t('Select'), 'delete' => t('Delete')); $star = false; $filer = false; $isstarred = "unstarred"; if ($profile_owner == local_user()) { if ($toplevelpost) { $isstarred = $item['starred'] ? "starred" : "unstarred"; $star = array('do' => t("add star"), 'undo' => t("remove star"), 'toggle' => t("toggle star status"), 'classdo' => $item['starred'] ? "hidden" : "", 'classundo' => $item['starred'] ? "" : "hidden", 'starred' => t('starred'), 'tagger' => t("add tag"), 'classtagger' => ""); } $filer = t("save to folder"); } $photo = $item['photo']; $thumb = $item['thumb']; // Post was remotely authored. $diff_author = link_compare($item['url'], $item['author-link']) ? false : true; $profile_name = strlen($item['author-name']) && $diff_author ? $item['author-name'] : $item['name']; if ($item['author-link'] && !$item['author-name']) { $profile_name = $item['author-link']; } $sp = false; $profile_link = best_link_url($item, $sp); if ($profile_link === 'mailbox') { $profile_link = ''; } if ($sp) { $sparkle = ' sparkle'; } else { $profile_link = zrl($profile_link); } $normalised = normalise_link(strlen($item['author-link']) ? $item['author-link'] : $item['url']); if ($normalised != 'mailbox' && x($a->contacts, $normalised)) { $profile_avatar = $a->contacts[$normalised]['thumb']; } else { $profile_avatar = strlen($item['author-avatar']) && $diff_author ? $item['author-avatar'] : $a->get_cached_avatar_image($thumb); } $like = x($alike, $item['uri']) ? format_like($alike[$item['uri']], $alike[$item['uri'] . '-l'], 'like', $item['uri']) : ''; $dislike = x($dlike, $item['uri']) ? format_like($dlike[$item['uri']], $dlike[$item['uri'] . '-l'], 'dislike', $item['uri']) : ''; $locate = array('location' => $item['location'], 'coord' => $item['coord'], 'html' => ''); call_hooks('render_location', $locate); $location = strlen($locate['html']) ? $locate['html'] : render_location_dummy($locate); $indent = $toplevelpost ? '' : ' comment'; $shiny = ""; if (strcmp(datetime_convert('UTC', 'UTC', $item['created']), datetime_convert('UTC', 'UTC', 'now - 12 hours')) > 0) { $shiny = 'shiny'; } // localize_item($item); $tags = array(); foreach (explode(',', $item['tag']) as $tag) { $tag = trim($tag); if ($tag != "") { $tags[] = bbcode($tag); } } // Build the HTML $body = prepare_body($item, true); //$tmp_item = replace_macros($template, if ($a->theme['template_engine'] === 'internal') { $body_e = template_escape($body); $text_e = strip_tags(template_escape($body)); $name_e = template_escape($profile_name); $title_e = template_escape($item['title']); $location_e = template_escape($location); $owner_name_e = template_escape($owner_name); } else { $body_e = $body; $text_e = strip_tags($body); $name_e = $profile_name; $title_e = $item['title']; $location_e = $location; $owner_name_e = $owner_name; } $tmp_item = array('comment_firstcollapsed' => $comment_firstcollapsed, 'comment_lastcollapsed' => $comment_lastcollapsed, 'template' => $template, 'type' => implode("", array_slice(explode("/", $item['verb']), -1)), 'tags' => $tags, 'body' => $body_e, 'text' => $text_e, 'id' => $item['item_id'], 'linktitle' => sprintf(t('View %s\'s profile @ %s'), $profile_name, strlen($item['author-link']) ? $item['author-link'] : $item['url']), 'olinktitle' => sprintf(t('View %s\'s profile @ %s'), $profile_name, strlen($item['owner-link']) ? $item['owner-link'] : $item['url']), 'to' => t('to'), 'wall' => t('Wall-to-Wall'), 'vwall' => t('via Wall-To-Wall:'), 'profile_url' => $profile_link, 'item_photo_menu' => item_photo_menu($item), 'name' => $name_e, 'thumb' => proxy_url($profile_avatar), 'osparkle' => $osparkle, 'sparkle' => $sparkle, 'title' => $title_e, 'ago' => $item['app'] ? sprintf(t('%s from %s'), relative_date($item['created']), $item['app']) : relative_date($item['created']), 'lock' => $lock, 'location' => $location_e, 'indent' => $indent, 'shiny' => $shiny, 'owner_url' => $owner_url, 'owner_photo' => proxy_url($owner_photo), 'owner_name' => $owner_name_e, 'plink' => get_plink($item), 'edpost' => $edpost, 'isstarred' => $isstarred, 'star' => $star, 'filer' => $filer, 'drop' => $drop, 'vote' => $likebuttons, 'like' => $like, 'dislike' => $dislike, 'comment' => $comment, 'previewing' => $previewing, 'wait' => t('Please wait')); $arr = array('item' => $item, 'output' => $tmp_item); call_hooks('display_item', $arr); $threads[$threadsid]['items'][] = $arr['output']; } } } return $threads; }
/** * Check if we are a wall to wall item and set the relevant properties */ protected function check_wall_to_wall() { $a = $this->get_app(); $conv = $this->get_conversation(); $this->wall_to_wall = false; if ($this->is_toplevel()) { if ($conv->get_mode() !== 'profile') { if ($this->get_data_value('wall') and !$this->get_data_value('self')) { // On the network page, I am the owner. On the display page it will be the profile owner. // This will have been stored in $a->page_contact by our calling page. // Put this person as the wall owner of the wall-to-wall notice. $this->owner_url = zrl($a->page_contact['url']); $this->owner_photo = $a->page_contact['thumb']; $this->owner_name = $a->page_contact['name']; $this->wall_to_wall = true; } else { if ($this->get_data_value('owner-link')) { $owner_linkmatch = $this->get_data_value('owner-link') && link_compare($this->get_data_value('owner-link'), $this->get_data_value('author-link')); $alias_linkmatch = $this->get_data_value('alias') && link_compare($this->get_data_value('alias'), $this->get_data_value('author-link')); $owner_namematch = $this->get_data_value('owner-name') && $this->get_data_value('owner-name') == $this->get_data_value('author-name'); if (!$owner_linkmatch && !$alias_linkmatch && !$owner_namematch) { // The author url doesn't match the owner (typically the contact) // and also doesn't match the contact alias. // The name match is a hack to catch several weird cases where URLs are // all over the park. It can be tricked, but this prevents you from // seeing "Bob Smith to Bob Smith via Wall-to-wall" and you know darn // well that it's the same Bob Smith. // But it could be somebody else with the same name. It just isn't highly likely. $this->owner_photo = $this->get_data_value('owner-avatar'); $this->owner_name = $this->get_data_value('owner-name'); $this->wall_to_wall = true; // If it is our contact, use a friendly redirect link if (link_compare($this->get_data_value('owner-link'), $this->get_data_value('url')) && $this->get_data_value('network') === NETWORK_DFRN) { $this->owner_url = $this->get_redirect_url(); } else { $this->owner_url = zrl($this->get_data_value('owner-link')); } } } } } } if (!$this->wall_to_wall) { $this->set_template('wall'); $this->owner_url = ''; $this->owner_photo = ''; $this->owner_name = ''; } }
function like_puller($a, $item, &$arr, $mode) { $url = ''; $sparkle = ''; $verb = $mode === 'like' ? ACTIVITY_LIKE : ACTIVITY_DISLIKE; if (activity_match($item['verb'], $verb) && $item['id'] != $item['parent']) { $url = $item['author-link']; if (local_user() && local_user() == $item['uid'] && $item['network'] === 'dfrn' && !$item['self'] && link_compare($item['author-link'], $item['url'])) { $url = $a->get_baseurl(true) . '/redir/' . $item['contact-id']; $sparkle = ' class="sparkle" '; } else { $url = zrl($url); } if (!$item['thr-parent']) { $item['thr-parent'] = $item['parent-uri']; } if (!(isset($arr[$item['thr-parent'] . '-l']) && is_array($arr[$item['thr-parent'] . '-l']))) { $arr[$item['thr-parent'] . '-l'] = array(); } if (!isset($arr[$item['thr-parent']])) { $arr[$item['thr-parent']] = 1; } else { $arr[$item['thr-parent']]++; } $arr[$item['thr-parent'] . '-l'][] = '<a href="' . $url . '"' . $sparkle . '>' . $item['author-name'] . '</a>'; } return; }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); return; } $signed_data = $guid . ';' . $type; $key = $msg['key']; /* How Diaspora performs relayable_retraction signature checking: - If an item has been sent by the item author to the top-level post owner to relay on to the rest of the contacts on the top-level post, the top-level post owner checks the author_signature, then creates a parent_author_signature before relaying the item on - If an item has been relayed on by the top-level post owner, the contacts who receive it check only the parent_author_signature. Basically, they trust that the top-level post owner has already verified the authenticity of anything he/she sends out - In either case, the signature that get checked is the signature created by the person who sent the salmon */ if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: top-level post owner verification failed'); return; } } else { $sig_decode = base64_decode($sig); if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg, true)); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['id'])); delete_thread($r[0]['id'], $r[0]['parent-uri']); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select origin from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if (count($p)) { if ($p[0]['origin'] && !$parent_author_signature) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), dbesc($sig), dbesc($diaspora_handle)); // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); proc_run('php', 'include/notifier.php', 'drop', $r[0]['id']); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
function pubsubhubbub_init(&$a) { // PuSH subscription must be considered "public" so just block it // if public access isn't enabled. if (get_config('system', 'block_public')) { http_status_exit(403); } // Subscription request from subscriber // https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4 // Example from GNU Social: // [hub_mode] => subscribe // [hub_callback] => http://status.local/main/push/callback/1 // [hub_verify] => sync // [hub_verify_token] => af11... // [hub_secret] => af11... // [hub_topic] => http://friendica.local/dfrn_poll/sazius if ($_SERVER['REQUEST_METHOD'] === 'POST') { $hub_mode = post_var('hub_mode'); $hub_callback = post_var('hub_callback'); $hub_verify = post_var('hub_verify'); $hub_verify_token = post_var('hub_verify_token'); $hub_secret = post_var('hub_secret'); $hub_topic = post_var('hub_topic'); // check for valid hub_mode if ($hub_mode === 'subscribe') { $subscribe = 1; } else { if ($hub_mode === 'unsubscribe') { $subscribe = 0; } else { logger("pubsubhubbub: invalid hub_mode={$hub_mode}, ignoring."); http_status_exit(404); } } logger("pubsubhubbub: {$hub_mode} request from " . $_SERVER['REMOTE_ADDR']); // get the nick name from the topic, a bit hacky but needed $nick = substr(strrchr($hub_topic, "/"), 1); if (!$nick) { logger('pubsubhubbub: bad hub_topic=$hub_topic, ignoring.'); http_status_exit(404); } // fetch user from database given the nickname $r = q("SELECT * FROM `user` WHERE `nickname` = '%s'" . " AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1", dbesc($nick)); if (!count($r)) { logger('pubsubhubbub: local account not found: ' . $nick); http_status_exit(404); } $owner = $r[0]; // abort if user's wall is supposed to be private if ($r[0]['hidewall']) { logger('pubsubhubbub: local user ' . $nick . 'has chosen to hide wall, ignoring.'); http_status_exit(403); } // get corresponding row from contact table $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND NOT `blocked`" . " AND NOT `pending` AND `self` LIMIT 1", intval($owner['uid'])); if (!count($r)) { logger('pubsubhubbub: contact not found.'); http_status_exit(404); } $contact = $r[0]; // sanity check that topic URLs are the same if (!link_compare($hub_topic, $contact['poll'])) { logger('pubsubhubbub: hub topic ' . $hub_topic . ' != ' . $contact['poll']); http_status_exit(404); } // do subscriber verification according to the PuSH protocol $hub_challenge = random_string(40); $params = 'hub.mode=' . ($subscribe == 1 ? 'subscribe' : 'unsubscribe') . '&hub.topic=' . urlencode($hub_topic) . '&hub.challenge=' . $hub_challenge . '&hub.lease_seconds=604800' . '&hub.verify_token=' . $hub_verify_token; // lease time is hard coded to one week (in seconds) // we don't actually enforce the lease time because GNU // Social/StatusNet doesn't honour it (yet) $body = fetch_url($hub_callback . "?" . $params); $ret = $a->get_curl_code(); // give up if the HTTP return code wasn't a success (2xx) if ($ret < 200 || $ret > 299) { logger("pubsubhubbub: subscriber verification at {$hub_callback} " . "returned {$ret}, ignoring."); http_status_exit(404); } // check that the correct hub_challenge code was echoed back if (trim($body) !== $hub_challenge) { logger("pubsubhubbub: subscriber did not echo back " . "hub.challenge, ignoring."); logger("\"{$hub_challenge}\" != \"" . trim($body) . "\""); http_status_exit(404); } // fetch the old subscription if it exists $r = q("SELECT * FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); // delete old subscription if it exists q("DELETE FROM `push_subscriber` WHERE `callback_url` = '%s'", dbesc($hub_callback)); if ($subscribe) { $last_update = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); $push_flag = 0; // if we are just updating an old subscription, keep the // old values for push and last_update if (count($r)) { $last_update = $r[0]['last_update']; $push_flag = $r[0]['push']; } // subscribe means adding the row to the table q("INSERT INTO `push_subscriber` (`uid`, `callback_url`, " . "`topic`, `nickname`, `push`, `last_update`, `secret`) values " . "(%d, '%s', '%s', '%s', %d, '%s', '%s')", intval($owner['uid']), dbesc($hub_callback), dbesc($hub_topic), dbesc($nick), intval($push_flag), dbesc($last_update), dbesc($hub_secret)); logger("pubsubhubbub: successfully subscribed [{$hub_callback}]."); } else { logger("pubsubhubbub: successfully unsubscribed [{$hub_callback}]."); // we do nothing here, since the row was already deleted } http_status_exit(202); } killme(); }
function notifier_run($argv, $argc) { global $a, $db; if (is_null($a)) { $a = new App(); } if (is_null($db)) { @(include ".htconfig.php"); require_once "dba.php"; $db = new dba($db_host, $db_user, $db_pass, $db_data); unset($db_host, $db_user, $db_pass, $db_data); } require_once "session.php"; require_once "datetime.php"; require_once 'include/items.php'; require_once 'include/bbcode.php'; load_config('config'); load_config('system'); load_hooks(); if ($argc < 3) { return; } $a->set_baseurl(get_config('system', 'url')); logger('notifier: invoked: ' . print_r($argv, true)); $cmd = $argv[1]; switch ($cmd) { case 'mail': default: $item_id = intval($argv[2]); if (!$item_id) { return; } break; } $expire = false; $mail = false; $fsuggest = false; $top_level = false; $recipients = array(); $url_recipients = array(); $normal_mode = true; if ($cmd === 'mail') { $normal_mode = false; $mail = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!count($message)) { return; } $uid = $message[0]['uid']; $recipients[] = $message[0]['contact-id']; $item = $message[0]; } elseif ($cmd === 'expire') { $normal_mode = false; $expire = true; $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1 \n\t\t\tAND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 10 MINUTE", intval($item_id)); $uid = $item_id; $item_id = 0; if (!count($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]; } else { // find ancestors $r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!count($r) || !intval($r[0]['parent'])) { return; } $target_item = $r[0]; $parent_id = intval($r[0]['parent']); $uid = $r[0]['uid']; $updated = $r[0]['edited']; if (!$parent_id) { return; } $items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer` \n\t\t\tFROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d ORDER BY `id` ASC", intval($parent_id)); if (!count($items)) { return; } // avoid race condition with deleting entries if ($items[0]['deleted']) { foreach ($items as $item) { $item['deleted'] = 1; } } if (count($items) == 1 && $items[0]['id'] === $target_item['id'] && $items[0]['uri'] === $items[0]['parent-uri']) { logger('notifier: top level post'); $top_level = true; } } $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`, \n\t\t`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`, \n\t\t`user`.`page-flags`, `user`.`prvnets`\n\t\tFROM `contact` LEFT JOIN `user` ON `user`.`uid` = `contact`.`uid` \n\t\tWHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1", intval($uid)); if (!count($r)) { return; } $owner = $r[0]; $walltowall = $top_level && $owner['id'] != $items[0]['contact-id'] ? true : false; $hub = get_config('system', 'huburl'); // If this is a public conversation, notify the feed hub $public_message = true; // fill this in with a single salmon slap if applicable $slap = ''; if (!($mail || $fsuggest)) { require_once 'include/group.php'; $parent = $items[0]; // This is IMPORTANT!!!! // We will only send a "notify owner to relay" or followup message if the referenced post // originated on our system by virtue of having our hostname somewhere // in the URI, AND it was a comment (not top_level) AND the parent originated elsewhere. // if $parent['wall'] == 1 we will already have the parent message in our array // and we will relay the whole lot. // expire sends an entire group of expire messages and cannot be forwarded. // However the conversation owner will be a part of the conversation and will // be notified during this run. // Other DFRN conversation members will be alerted during polled updates. // Diaspora members currently are not notified of expirations, and other networks have // either limited or no ability to process deletions. We should at least fix Diaspora // by stringing togther an array of retractions and sending them onward. $localhost = $a->get_hostname(); if (strpos($localhost, ':')) { $localhost = substr($localhost, 0, strpos($localhost, ':')); } /** * * Be VERY CAREFUL if you make any changes to the following several lines. Seemingly innocuous changes * have been known to cause runaway conditions which affected several servers, along with * permissions issues. * */ $relay_to_owner = false; if (!$top_level && $parent['wall'] == 0 && !$expire && stristr($target_item['uri'], $localhost)) { $relay_to_owner = true; } if ($cmd === 'uplink' && intval($parent['forum_mode']) && !$top_level) { $relay_to_owner = true; } // until the 'origin' flag has been in use for several months // we will just use it as a fallback test // later we will be able to use it as the primary test of whether or not to relay. if (!$target_item['origin']) { $relay_to_owner = false; } if ($parent['origin']) { $relay_to_owner = false; } if ($relay_to_owner) { logger('notifier: followup', LOGGER_DEBUG); // local followup to remote post $followup = true; $public_message = false; // not public $conversant_str = dbesc($parent['contact-id']); } else { $followup = false; // don't send deletions onward for other people's stuff if ($target_item['deleted'] && !intval($target_item['wall'])) { logger('notifier: ignoring delete notification for non-wall item'); return; } if (strlen($parent['allow_cid']) || strlen($parent['allow_gid']) || strlen($parent['deny_cid']) || strlen($parent['deny_gid'])) { $public_message = false; // private recipients, not public } $allow_people = expand_acl($parent['allow_cid']); $allow_groups = expand_groups(expand_acl($parent['allow_gid'])); $deny_people = expand_acl($parent['deny_cid']); $deny_groups = expand_groups(expand_acl($parent['deny_gid'])); // if our parent is a forum, uplink to the origonal author causing // a delivery fork if (intval($parent['forum_mode']) && !$top_level && $cmd !== 'uplink') { proc_run('php', 'include/notifier', 'uplink', $item_id); } $conversants = array(); foreach ($items as $item) { $recipients[] = $item['contact-id']; $conversants[] = $item['contact-id']; // pull out additional tagged people to notify (if public message) if ($public_message && strlen($item['inform'])) { $people = explode(',', $item['inform']); foreach ($people as $person) { if (substr($person, 0, 4) === 'cid:') { $recipients[] = intval(substr($person, 4)); $conversants[] = intval(substr($person, 4)); } else { $url_recipients[] = substr($person, 4); } } } } logger('notifier: url_recipients' . print_r($url_recipients, true)); $conversants = array_unique($conversants); $recipients = array_unique(array_merge($recipients, $allow_people, $allow_groups)); $deny = array_unique(array_merge($deny_people, $deny_groups)); $recipients = array_diff($recipients, $deny); $conversant_str = dbesc(implode(', ', $conversants)); } $r = q("SELECT * FROM `contact` WHERE `id` IN ( {$conversant_str} ) AND `blocked` = 0 AND `pending` = 0"); if (count($r)) { $contacts = $r; } } $feed_template = get_markup_template('atom_feed.tpl'); $mail_template = get_markup_template('atom_mail.tpl'); $atom = ''; $slaps = array(); $hubxml = feed_hublinks(); $birthday = feed_birthday($owner['uid'], $owner['timezone']); if (strlen($birthday)) { $birthday = '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>'; } $atom .= replace_macros($feed_template, array('$version' => xmlify(FRIENDICA_VERSION), '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname']), '$feed_title' => xmlify($owner['name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00', ATOM_TIME)), '$hub' => $hubxml, '$salmon' => '', '$name' => xmlify($owner['name']), '$profile_page' => xmlify($owner['url']), '$photo' => xmlify($owner['photo']), '$thumb' => xmlify($owner['thumb']), '$picdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['avatar-date'] . '+00:00', ATOM_TIME)), '$uridate' => xmlify(datetime_convert('UTC', 'UTC', $owner['uri-date'] . '+00:00', ATOM_TIME)), '$namdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['name-date'] . '+00:00', ATOM_TIME)), '$birthday' => $birthday)); if ($mail) { $public_message = false; // mail is not public $body = fix_private_photos($item['body'], $owner['uid']); $atom .= replace_macros($mail_template, array('$name' => xmlify($owner['name']), '$profile_page' => xmlify($owner['url']), '$thumb' => xmlify($owner['thumb']), '$item_id' => xmlify($item['uri']), '$subject' => xmlify($item['title']), '$created' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', ATOM_TIME)), '$content' => xmlify($body), '$parent_id' => xmlify($item['parent-uri']))); } elseif ($fsuggest) { $public_message = false; // suggestions are not public $sugg_template = get_markup_template('atom_suggest.tpl'); $atom .= replace_macros($sugg_template, array('$name' => xmlify($item['name']), '$url' => xmlify($item['url']), '$photo' => xmlify($item['photo']), '$request' => xmlify($item['request']), '$note' => xmlify($item['note']))); // We don't need this any more q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); } else { if ($followup) { foreach ($items as $item) { // there is only one item if (!$item['parent']) { continue; } if ($item['id'] == $item_id) { logger('notifier: followup: item: ' . print_r($item, true), LOGGER_DATA); $slap = atom_entry($item, 'html', $owner, $owner, false); $atom .= atom_entry($item, 'text', $owner, $owner, false); } } } else { foreach ($items as $item) { if (!$item['parent']) { continue; } // private emails may be in included in public conversations. Filter them. if ($public_message && $item['private']) { continue; } $contact = get_item_contact($item, $contacts); if (!$contact) { continue; } if ($normal_mode) { // we only need the current item, but include the parent because without it // older sites without a corresponding dfrn_notify change may do the wrong thing. if ($item_id == $item['id'] || $item['id'] == $item['parent']) { $atom .= atom_entry($item, 'text', $contact, $owner, true); } } else { $atom .= atom_entry($item, 'text', $contact, $owner, true); } if ($top_level && $public_message && $item['author-link'] === $item['owner-link'] && !$expire) { $slaps[] = atom_entry($item, 'html', $contact, $owner, true); } } } } $atom .= '</feed>' . "\r\n"; logger('notifier: ' . $atom, LOGGER_DATA); logger('notifier: slaps: ' . print_r($slaps, true), LOGGER_DATA); // If this is a public message and pubmail is set on the parent, include all your email contacts $mail_disabled = function_exists('imap_open') && !get_config('system', 'imap_disabled') ? 0 : 1; if (!$mail_disabled) { if (!strlen($target_item['allow_cid']) && !strlen($target_item['allow_gid']) && !strlen($target_item['deny_cid']) && !strlen($target_item['deny_gid']) && intval($target_item['pubmail'])) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `network` = '%s'", intval($uid), dbesc(NETWORK_MAIL)); if (count($r)) { foreach ($r as $rr) { $recipients[] = $rr['id']; } } } } if ($followup) { $recip_str = $parent['contact-id']; } else { $recip_str = implode(', ', $recipients); } $r = q("SELECT * FROM `contact` WHERE `id` IN ( %s ) AND `blocked` = 0 AND `pending` = 0 ", dbesc($recip_str)); require_once 'include/salmon.php'; $interval = get_config('system', 'delivery_interval') === false ? 2 : intval(get_config('system', 'delivery_interval')); // delivery loop if (count($r)) { foreach ($r as $contact) { if (!$mail && !$fsuggest && !$followup && !$contact['self']) { if ($contact['network'] === NETWORK_DIASPORA && $public_message) { continue; } q("insert into deliverq ( `cmd`,`item`,`contact` ) values ('%s', %d, %d )", dbesc($cmd), intval($item_id), intval($contact['id'])); } } foreach ($r as $contact) { if ($contact['self']) { continue; } // potentially more than one recipient. Start a new process and space them out a bit. // we will deliver single recipient types of message and email receipients here. if (!$mail && !$fsuggest && !$followup) { proc_run('php', 'include/delivery.php', $cmd, $item_id, $contact['id']); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } continue; } $deliver_status = 0; logger("main delivery by notifier: followup={$followup} mail={$mail} fsuggest={$fsuggest}"); switch ($contact['network']) { case NETWORK_DFRN: // perform local delivery if we are on the same site $basepath = implode('/', array_slice(explode('/', $contact['url']), 0, 3)); if (link_compare($basepath, $a->get_baseurl())) { $nickname = basename($contact['url']); if ($contact['issued-id']) { $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id'])); } else { $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id'])); } $x = q("SELECT\t`contact`.*, `contact`.`uid` AS `importer_uid`, \n\t\t\t\t\t\t\t`contact`.`pubkey` AS `cpubkey`, \n\t\t\t\t\t\t\t`contact`.`prvkey` AS `cprvkey`, \n\t\t\t\t\t\t\t`contact`.`thumb` AS `thumb`, \n\t\t\t\t\t\t\t`contact`.`url` as `url`,\n\t\t\t\t\t\t\t`contact`.`name` as `senderName`,\n\t\t\t\t\t\t\t`user`.* \n\t\t\t\t\t\t\tFROM `contact` \n\t\t\t\t\t\t\tLEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` \n\t\t\t\t\t\t\tWHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0\n\t\t\t\t\t\t\tAND `contact`.`network` = '%s' AND `user`.`nickname` = '%s'\n\t\t\t\t\t\t\t{$sql_extra}\n\t\t\t\t\t\t\tAND `user`.`account_expired` = 0 LIMIT 1", dbesc(NETWORK_DFRN), dbesc($nickname)); if (count($x)) { require_once 'library/simplepie/simplepie.inc'; logger('mod-delivery: local delivery'); local_delivery($x[0], $atom); break; } } logger('notifier: dfrndelivery: ' . $contact['name']); $deliver_status = dfrn_deliver($owner, $contact, $atom); logger('notifier: dfrn_delivery returns ' . $deliver_status); if ($deliver_status == -1) { logger('notifier: delivery failed: queuing message'); // queue message for redelivery add_to_queue($contact['id'], NETWORK_DFRN, $atom); } break; case NETWORK_OSTATUS: // Do not send to otatus if we are not configured to send to public networks if ($owner['prvnets']) { break; } if (get_config('system', 'ostatus_disabled') || get_config('system', 'dfrn_only')) { break; } if ($followup && $contact['notify']) { logger('notifier: slapdelivery: ' . $contact['name']); $deliver_status = slapper($owner, $contact['notify'], $slap); if ($deliver_status == -1) { // queue message for redelivery add_to_queue($contact['id'], NETWORK_OSTATUS, $slap); } } else { // only send salmon if public - e.g. if it's ok to notify // a public hub, it's ok to send a salmon if (count($slaps) && $public_message && !$expire) { logger('notifier: slapdelivery: ' . $contact['name']); foreach ($slaps as $slappy) { if ($contact['notify']) { $deliver_status = slapper($owner, $contact['notify'], $slappy); if ($deliver_status == -1) { // queue message for redelivery add_to_queue($contact['id'], NETWORK_OSTATUS, $slappy); } } } } } break; case NETWORK_MAIL: if (get_config('system', 'dfrn_only')) { break; } // WARNING: does not currently convert to RFC2047 header encodings, etc. $addr = $contact['addr']; if (!strlen($addr)) { break; } if ($cmd === 'wall-new' || $cmd === 'comment-new') { $it = null; if ($cmd === 'wall-new') { $it = $items[0]; } else { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($argv[2]), intval($uid)); if (count($r)) { $it = $r[0]; } } if (!$it) { break; } $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid)); if (!count($local_user)) { break; } $reply_to = ''; $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid)); if ($r1 && $r1[0]['reply_to']) { $reply_to = $r1[0]['reply_to']; } $subject = $it['title'] ? $it['title'] : t("(no subject)"); $headers = 'From: ' . $local_user[0]['username'] . ' <' . $local_user[0]['email'] . '>' . "\n"; if ($reply_to) { $headers .= 'Reply-to: ' . $reply_to . "\n"; } $headers .= 'Message-id: <' . $it['uri'] . '>' . "\n"; if ($it['uri'] !== $it['parent-uri']) { $header .= 'References: <' . $it['parent-uri'] . '>' . "\n"; if (!strlen($it['title'])) { $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' LIMIT 1", dbesc($it['parent-uri'])); if (count($r)) { $subtitle = $r[0]['title']; if ($subtitle) { if (strncasecmp($subtitle, 'RE:', 3)) { $subject = $subtitle; } else { $subject = 'Re: ' . $subtitle; } } } } } $headers .= 'MIME-Version: 1.0' . "\n"; $headers .= 'Content-Type: text/html; charset=UTF-8' . "\n"; $headers .= 'Content-Transfer-Encoding: 8bit' . "\n\n"; $html = prepare_body($it); $message = '<html><body>' . $html . '</body></html>'; logger('notifier: email delivery to ' . $addr); mail($addr, $subject, $message, $headers); } break; case NETWORK_DIASPORA: require_once 'include/diaspora.php'; if (get_config('system', 'dfrn_only') || !get_config('system', 'diaspora_enabled')) { break; } if ($mail) { diaspora_send_mail($item, $owner, $contact); break; } if (!$normal_mode) { break; } // special handling for followup to public post // all other public posts processed as public batches further below if ($public_message) { if ($followup) { diaspora_send_followup($target_item, $owner, $contact, true); } break; } if (!$contact['pubkey']) { break; } if ($target_item['verb'] === ACTIVITY_DISLIKE) { // unsupported break; } elseif ($target_item['deleted'] && $target_item['verb'] !== ACTIVITY_LIKE) { // diaspora delete, diaspora_send_retraction($target_item, $owner, $contact); break; } elseif ($followup) { // send comments, likes and retractions of likes to owner to relay diaspora_send_followup($target_item, $owner, $contact); break; } elseif ($target_item['parent'] != $target_item['id']) { // we are the relay - send comments, likes and unlikes to our conversants diaspora_send_relay($target_item, $owner, $contact); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall diaspora_send_status($target_item, $owner, $contact); break; } break; case NETWORK_FEED: case NETWORK_FACEBOOK: if (get_config('system', 'dfrn_only')) { break; } default: break; } } } // send additional slaps to mentioned remote tags (@foo@example.com) if ($slap && count($url_recipients) && ($followup || $top_level) && $public_message && !$expire) { if (!get_config('system', 'dfrn_only')) { foreach ($url_recipients as $url) { if ($url) { logger('notifier: urldelivery: ' . $url); $deliver_status = slapper($owner, $url, $slap); // TODO: redeliver/queue these items on failure, though there is no contact record } } } } if ($public_message) { $r1 = q("SELECT DISTINCT(`batch`), `id`, `name`,`network` FROM `contact` WHERE `network` = '%s' \n\t\t\tAND `uid` = %d AND `rel` != %d group by `batch` ORDER BY rand() ", dbesc(NETWORK_DIASPORA), intval($owner['uid']), intval(CONTACT_IS_SHARING)); $r2 = q("SELECT `id`, `name`,`network` FROM `contact` \n\t\t\tWHERE `network` = '%s' AND `uid` = %d AND `blocked` = 0 AND `pending` = 0\n\t\t\tAND `rel` != %d order by rand() ", dbesc(NETWORK_DFRN), intval($owner['uid']), intval(CONTACT_IS_SHARING)); $r = array_merge($r2, $r1); if (count($r)) { logger('pubdeliver: ' . print_r($r, true), LOGGER_DEBUG); // throw everything into the queue in case we get killed foreach ($r as $rr) { if (!$mail && !$fsuggest && !$followup) { q("insert into deliverq ( `cmd`,`item`,`contact` ) values ('%s', %d, %d )", dbesc($cmd), intval($item_id), intval($rr['id'])); } } foreach ($r as $rr) { // except for Diaspora batch jobs // Don't deliver to folks who have already been delivered to if ($rr['network'] !== NETWORK_DIASPORA && in_array($rr['id'], $conversants)) { logger('notifier: already delivered id=' . $rr['id']); continue; } if (!$mail && !$fsuggest && !$followup) { logger('notifier: delivery agent: ' . $rr['name'] . ' ' . $rr['id']); proc_run('php', 'include/delivery.php', $cmd, $item_id, $rr['id']); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } } } if (strlen($hub)) { $hubs = explode(',', $hub); if (count($hubs)) { foreach ($hubs as $h) { $h = trim($h); if (!strlen($h)) { continue; } $params = 'hub.mode=publish&hub.url=' . urlencode($a->get_baseurl() . '/dfrn_poll/' . $owner['nickname']); post_url($h, $params); logger('pubsub: publish: ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code()); if (count($hubs) > 1) { sleep(7); } // try and avoid multiple hubs responding at precisely the same time } } } } if ($normal_mode) { call_hooks('notifier_normal', $target_item); } call_hooks('notifier_end', $target_item); return; }
function delivery_run(&$argv, &$argc) { global $a, $db; if (is_null($a)) { $a = new App(); } if (is_null($db)) { @(include ".htconfig.php"); require_once "include/dba.php"; $db = new dba($db_host, $db_user, $db_pass, $db_data); unset($db_host, $db_user, $db_pass, $db_data); } require_once "include/session.php"; require_once "include/datetime.php"; require_once 'include/items.php'; require_once 'include/bbcode.php'; require_once 'include/diaspora.php'; require_once 'include/email.php'; load_config('config'); load_config('system'); load_hooks(); if ($argc < 3) { return; } $a->set_baseurl(get_config('system', 'url')); logger('delivery: invoked: ' . print_r($argv, true), LOGGER_DEBUG); $cmd = $argv[1]; $item_id = intval($argv[2]); for ($x = 3; $x < $argc; $x++) { $contact_id = intval($argv[$x]); // Some other process may have delivered this item already. $r = q("select * from deliverq where cmd = '%s' and item = %d and contact = %d limit 1", dbesc($cmd), dbesc($item_id), dbesc($contact_id)); if (!count($r)) { continue; } $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. Delivery deferred to next queue run.'); return; } } // It's ours to deliver. Remove it from the queue. q("delete from deliverq where cmd = '%s' and item = %d and contact = %d", dbesc($cmd), dbesc($item_id), dbesc($contact_id)); if (!$item_id || !$contact_id) { continue; } $expire = false; $top_level = false; $recipients = array(); $url_recipients = array(); $normal_mode = true; $recipients[] = $contact_id; if ($cmd === 'expire') { $normal_mode = false; $expire = true; $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1 \n\t\t\t\tAND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 30 MINUTE", intval($item_id)); $uid = $item_id; $item_id = 0; if (!count($items)) { continue; } } else { // find ancestors $r = q("SELECT * FROM `item` WHERE `id` = %d and visible = 1 and moderated = 0 LIMIT 1", intval($item_id)); if (!count($r) || !intval($r[0]['parent'])) { continue; } $target_item = $r[0]; $parent_id = intval($r[0]['parent']); $uid = $r[0]['uid']; $updated = $r[0]['edited']; // POSSIBLE CLEANUP --> The following seems superfluous. We've already checked for "if (! intval($r[0]['parent']))" a few lines up if (!$parent_id) { continue; } $items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer` \n\t\t\t\tFROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d and visible = 1 and moderated = 0 ORDER BY `id` ASC", intval($parent_id)); if (!count($items)) { continue; } $icontacts = null; $contacts_arr = array(); foreach ($items as $item) { if (!in_array($item['contact-id'], $contacts_arr)) { $contacts_arr[] = intval($item['contact-id']); } } if (count($contacts_arr)) { $str_contacts = implode(',', $contacts_arr); $icontacts = q("SELECT * FROM `contact` \n\t\t\t\t\tWHERE `id` IN ( {$str_contacts} ) "); } if (!($icontacts && count($icontacts))) { continue; } // avoid race condition with deleting entries if ($items[0]['deleted']) { foreach ($items as $item) { $item['deleted'] = 1; } } if (count($items) == 1 && $items[0]['uri'] === $items[0]['parent-uri']) { logger('delivery: top level post'); $top_level = true; } } $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`, \n\t\t\t`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`, \n\t\t\t`user`.`page-flags`, `user`.`prvnets`\n\t\t\tFROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` \n\t\t\tWHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1", intval($uid)); if (!count($r)) { continue; } $owner = $r[0]; $walltowall = $top_level && $owner['id'] != $items[0]['contact-id'] ? true : false; $public_message = true; // fill this in with a single salmon slap if applicable $slap = ''; require_once 'include/group.php'; $parent = $items[0]; // This is IMPORTANT!!!! // We will only send a "notify owner to relay" or followup message if the referenced post // originated on our system by virtue of having our hostname somewhere // in the URI, AND it was a comment (not top_level) AND the parent originated elsewhere. // if $parent['wall'] == 1 we will already have the parent message in our array // and we will relay the whole lot. // expire sends an entire group of expire messages and cannot be forwarded. // However the conversation owner will be a part of the conversation and will // be notified during this run. // Other DFRN conversation members will be alerted during polled updates. // Diaspora members currently are not notified of expirations, and other networks have // either limited or no ability to process deletions. We should at least fix Diaspora // by stringing togther an array of retractions and sending them onward. $localhost = $a->get_hostname(); if (strpos($localhost, ':')) { $localhost = substr($localhost, 0, strpos($localhost, ':')); } /** * * Be VERY CAREFUL if you make any changes to the following line. Seemingly innocuous changes * have been known to cause runaway conditions which affected several servers, along with * permissions issues. * */ if (!$top_level && $parent['wall'] == 0 && !$expire && stristr($target_item['uri'], $localhost)) { logger('relay denied for delivery agent.'); /* no relay allowed for direct contact delivery */ continue; } if (strlen($parent['allow_cid']) || strlen($parent['allow_gid']) || strlen($parent['deny_cid']) || strlen($parent['deny_gid'])) { $public_message = false; // private recipients, not public } $r = q("SELECT * FROM `contact` WHERE `id` = %d AND `blocked` = 0 AND `pending` = 0", intval($contact_id)); if (count($r)) { $contact = $r[0]; } $hubxml = feed_hublinks(); logger('notifier: slaps: ' . print_r($slaps, true), LOGGER_DATA); require_once 'include/salmon.php'; if ($contact['self']) { continue; } $deliver_status = 0; switch ($contact['network']) { case NETWORK_DFRN: logger('notifier: dfrndelivery: ' . $contact['name']); $feed_template = get_markup_template('atom_feed.tpl'); $mail_template = get_markup_template('atom_mail.tpl'); $atom = ''; $birthday = feed_birthday($owner['uid'], $owner['timezone']); if (strlen($birthday)) { $birthday = '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>'; } $atom .= replace_macros($feed_template, array('$version' => xmlify(FRIENDICA_VERSION), '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname']), '$feed_title' => xmlify($owner['name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00', ATOM_TIME)), '$hub' => $hubxml, '$salmon' => '', '$name' => xmlify($owner['name']), '$profile_page' => xmlify($owner['url']), '$photo' => xmlify($owner['photo']), '$thumb' => xmlify($owner['thumb']), '$picdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['avatar-date'] . '+00:00', ATOM_TIME)), '$uridate' => xmlify(datetime_convert('UTC', 'UTC', $owner['uri-date'] . '+00:00', ATOM_TIME)), '$namdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['name-date'] . '+00:00', ATOM_TIME)), '$birthday' => $birthday, '$community' => $owner['page-flags'] == PAGE_COMMUNITY ? '<dfrn:community>1</dfrn:community>' : '')); foreach ($items as $item) { if (!$item['parent']) { continue; } // private emails may be in included in public conversations. Filter them. if ($public_message && $item['private'] == 1) { continue; } $item_contact = get_item_contact($item, $icontacts); if (!$item_contact) { continue; } if ($normal_mode) { if ($item_id == $item['id'] || $item['id'] == $item['parent']) { $atom .= atom_entry($item, 'text', null, $owner, true, $top_level ? $contact['id'] : 0); } } else { $atom .= atom_entry($item, 'text', null, $owner, true); } } $atom .= '</feed>' . "\r\n"; logger('notifier: ' . $atom, LOGGER_DATA); $basepath = implode('/', array_slice(explode('/', $contact['url']), 0, 3)); // perform local delivery if we are on the same site if (link_compare($basepath, $a->get_baseurl())) { $nickname = basename($contact['url']); if ($contact['issued-id']) { $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id'])); } else { $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id'])); } $x = q("SELECT\t`contact`.*, `contact`.`uid` AS `importer_uid`,\n\t\t\t\t\t\t`contact`.`pubkey` AS `cpubkey`,\n\t\t\t\t\t\t`contact`.`prvkey` AS `cprvkey`,\n\t\t\t\t\t\t`contact`.`thumb` AS `thumb`,\n\t\t\t\t\t\t`contact`.`url` as `url`,\n\t\t\t\t\t\t`contact`.`name` as `senderName`,\n\t\t\t\t\t\t`user`.*\n\t\t\t\t\t\tFROM `contact`\n\t\t\t\t\t\tINNER JOIN `user` ON `contact`.`uid` = `user`.`uid`\n\t\t\t\t\t\tWHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0\n\t\t\t\t\t\tAND `contact`.`network` = '%s' AND `user`.`nickname` = '%s'\n\t\t\t\t\t\t{$sql_extra}\n\t\t\t\t\t\tAND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 LIMIT 1", dbesc(NETWORK_DFRN), dbesc($nickname)); if ($x && count($x)) { $write_flag = $x[0]['rel'] && $x[0]['rel'] != CONTACT_IS_SHARING ? true : false; if (($owner['page-flags'] == PAGE_COMMUNITY || $write_flag) && !$x[0]['writable']) { q("update contact set writable = 1 where id = %d", intval($x[0]['id'])); $x[0]['writable'] = 1; } $ssl_policy = get_config('system', 'ssl_policy'); fix_contact_ssl_policy($x[0], $ssl_policy); // If we are setup as a soapbox we aren't accepting input from this person if ($x[0]['page-flags'] == PAGE_SOAPBOX) { break; } require_once 'library/simplepie/simplepie.inc'; logger('mod-delivery: local delivery'); local_delivery($x[0], $atom); break; } } if (!was_recently_delayed($contact['id'])) { $deliver_status = dfrn_deliver($owner, $contact, $atom); } else { $deliver_status = -1; } logger('notifier: dfrn_delivery returns ' . $deliver_status); if ($deliver_status == -1) { logger('notifier: delivery failed: queuing message'); add_to_queue($contact['id'], NETWORK_DFRN, $atom); } break; case NETWORK_OSTATUS: // Do not send to otatus if we are not configured to send to public networks if ($owner['prvnets']) { break; } if (get_config('system', 'ostatus_disabled') || get_config('system', 'dfrn_only')) { break; } // only send salmon if public - e.g. if it's ok to notify // a public hub, it's ok to send a salmon if ($public_message && !$expire) { $slaps = array(); foreach ($items as $item) { if (!$item['parent']) { continue; } // private emails may be in included in public conversations. Filter them. if ($public_message && $item['private'] == 1) { continue; } $item_contact = get_item_contact($item, $icontacts); if (!$item_contact) { continue; } if ($top_level && $public_message && $item['author-link'] === $item['owner-link'] && !$expire) { $slaps[] = atom_entry($item, 'html', null, $owner, true); } } logger('notifier: slapdelivery: ' . $contact['name']); foreach ($slaps as $slappy) { if ($contact['notify']) { if (!was_recently_delayed($contact['id'])) { $deliver_status = slapper($owner, $contact['notify'], $slappy); } else { $deliver_status = -1; } if ($deliver_status == -1) { // queue message for redelivery add_to_queue($contact['id'], NETWORK_OSTATUS, $slappy); } } } } break; case NETWORK_MAIL: case NETWORK_MAIL2: if (get_config('system', 'dfrn_only')) { break; } // WARNING: does not currently convert to RFC2047 header encodings, etc. $addr = $contact['addr']; if (!strlen($addr)) { break; } if ($cmd === 'wall-new' || $cmd === 'comment-new') { $it = null; if ($cmd === 'wall-new') { $it = $items[0]; } else { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($argv[2]), intval($uid)); if (count($r)) { $it = $r[0]; } } if (!$it) { break; } $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid)); if (!count($local_user)) { break; } $reply_to = ''; $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid)); if ($r1 && $r1[0]['reply_to']) { $reply_to = $r1[0]['reply_to']; } $subject = $it['title'] ? email_header_encode($it['title'], 'UTF-8') : t("(no subject)"); // only expose our real email address to true friends if ($contact['rel'] == CONTACT_IS_FRIEND && !$contact['blocked']) { if ($reply_to) { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . $reply_to . '>' . "\n"; $headers .= 'Sender: ' . $local_user[0]['email'] . "\n"; } else { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . $local_user[0]['email'] . '>' . "\n"; } } else { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . t('noreply') . '@' . $a->get_hostname() . '>' . "\n"; } //if($reply_to) // $headers .= 'Reply-to: ' . $reply_to . "\n"; $headers .= 'Message-Id: <' . iri2msgid($it['uri']) . '>' . "\n"; //logger("Mail: uri: ".$it['uri']." parent-uri ".$it['parent-uri'], LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DATA); if ($it['uri'] !== $it['parent-uri']) { $headers .= "References: <" . iri2msgid($it["parent-uri"]) . ">"; // If Threading is enabled, write down the correct parent if ($it["thr-parent"] != "" and $it["thr-parent"] != $it["parent-uri"]) { $headers .= " <" . iri2msgid($it["thr-parent"]) . ">"; } $headers .= "\n"; if (!$it['title']) { $r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); if (count($r) and $r[0]['title'] != '') { $subject = $r[0]['title']; } else { $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); if (count($r) and $r[0]['title'] != '') { $subject = $r[0]['title']; } } } if (strncasecmp($subject, 'RE:', 3)) { $subject = 'Re: ' . $subject; } } email_send($addr, $subject, $headers, $it); } break; case NETWORK_DIASPORA: if ($public_message) { $loc = 'public batch ' . $contact['batch']; } else { $loc = $contact['name']; } logger('delivery: diaspora batch deliver: ' . $loc); if (get_config('system', 'dfrn_only') || !get_config('system', 'diaspora_enabled') || !$normal_mode) { break; } if (!$contact['pubkey'] && !$public_message) { break; } if ($target_item['verb'] === ACTIVITY_DISLIKE) { // unsupported break; } elseif ($target_item['deleted'] && $target_item['uri'] === $target_item['parent-uri']) { // top-level retraction logger('delivery: diaspora retract: ' . $loc); diaspora_send_retraction($target_item, $owner, $contact, $public_message); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants logger('delivery: diaspora relay: ' . $loc); diaspora_send_relay($target_item, $owner, $contact, $public_message); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall logger('delivery: diaspora status: ' . $loc); diaspora_send_status($target_item, $owner, $contact, $public_message); break; } logger('delivery: diaspora unknown mode: ' . $contact['name']); break; case NETWORK_FEED: case NETWORK_FACEBOOK: if (get_config('system', 'dfrn_only')) { break; } case NETWORK_PUMPIO: if (get_config('system', 'dfrn_only')) { break; } default: break; } } return; }
function api_item_get_user(&$a, $item) { // The author is our direct contact, in a conversation with us. if (link_compare($item['url'], $item['author-link'])) { return api_get_user($a, $item['cid']); } else { // The author may be a contact of ours, but is replying to somebody else. // Figure out if we know him/her. $normalised = normalise_link(strlen($item['author-link']) ? $item['author-link'] : $item['url']); if ($normalised != 'mailbox' && x($a->contacts[$normalised])) { return api_get_user($a, $a->contacts[$normalised]['id']); } } // We don't know this person directly. list($nick, $name) = array_map("trim", explode("(", $item['author-name'])); $name = str_replace(")", "", $name); $ret = array('uid' => 0, 'id' => 0, 'name' => $name, 'screen_name' => $nick, 'location' => '', 'profile_image_url' => $item['author-avatar'], 'url' => $item['author-link'], 'contact_url' => 0, 'protected' => false, 'friends_count' => 0, 'created_at' => '', 'utc_offset' => 0, 'time_zone' => '', 'geo_enabled' => false, 'statuses_count' => 0, 'lang' => 'en', 'description' => '', 'followers_count' => 0, 'favourites_count' => 0, 'contributors_enabled' => false, 'follow_request_sent' => false, 'profile_background_color' => 'cfe8f6', 'profile_text_color' => '000000', 'profile_link_color' => 'FF8500', 'profile_sidebar_fill_color' => 'AD0066', 'profile_sidebar_border_color' => 'AD0066', 'profile_background_image_url' => '', 'profile_background_tile' => false, 'profile_use_background_image' => false, 'notifications' => false, 'verified' => true, 'followers' => '', 'status' => array()); return $ret; }
/** * @param App $a * @param object $b * @return mixed */ function fbpost_post_hook(&$a, &$b) { logger('fbpost_post_hook: Facebook post invoked', LOGGER_DEBUG); if ($b['deleted'] || $b['created'] !== $b['edited']) { return; } logger('fbpost_post_hook: Facebook post first check successful', LOGGER_DEBUG); // if post comes from facebook don't send it back if ($b['extid'] == NETWORK_FACEBOOK) { return; } if ($b['app'] == "Facebook" and $b['verb'] != ACTIVITY_LIKE) { return; } logger('fbpost_post_hook: Facebook post accepted', LOGGER_DEBUG); /** * Post to Facebook stream */ require_once 'include/group.php'; require_once 'include/html2plain.php'; $reply = false; $likes = false; $deny_arr = array(); $allow_arr = array(); $toplevel = $b['id'] == $b['parent'] ? true : false; $linking = get_pconfig($b['uid'], 'facebook', 'no_linking') ? 0 : 1; if (!$toplevel && $linking) { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($b['parent']), intval($b['uid'])); //$r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", // dbesc($b['parent-uri']), // intval($b['uid']) //); // is it a reply to a facebook post? // A reply to a toplevel post is only allowed for "real" facebook posts if (count($r) && substr($r[0]['uri'], 0, 4) === 'fb::') { $reply = substr($r[0]['uri'], 4); } elseif (count($r) && substr($r[0]['extid'], 0, 4) === 'fb::' and $r[0]['id'] != $r[0]['parent']) { $reply = substr($r[0]['extid'], 4); } else { return; } $u = q("SELECT * FROM user where uid = %d limit 1", intval($b['uid'])); if (!count($u)) { return; } // only accept comments from the item owner. Other contacts are unknown to FB. if (!link_compare($b['author-link'], $a->get_baseurl() . '/profile/' . $u[0]['nickname'])) { return; } logger('fbpost_post_hook: facebook reply id=' . $reply); } if (strstr($b['postopts'], 'facebook') || $b['private'] || $reply) { if ($b['private'] && $reply === false) { $allow_people = expand_acl($b['allow_cid']); $allow_groups = expand_groups(expand_acl($b['allow_gid'])); $deny_people = expand_acl($b['deny_cid']); $deny_groups = expand_groups(expand_acl($b['deny_gid'])); $recipients = array_unique(array_merge($allow_people, $allow_groups)); $deny = array_unique(array_merge($deny_people, $deny_groups)); $allow_str = dbesc(implode(', ', $recipients)); if ($allow_str) { logger("fbpost_post_hook: private post to: " . $allow_str, LOGGER_DEBUG); $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( {$allow_str} ) AND `network` = 'face'"); if (count($r)) { foreach ($r as $rr) { $allow_arr[] = $rr['notify']; } } } $deny_str = dbesc(implode(', ', $deny)); if ($deny_str) { $r = q("SELECT `notify` FROM `contact` WHERE `id` IN ( {$deny_str} ) AND `network` = 'face'"); if (count($r)) { foreach ($r as $rr) { $deny_arr[] = $rr['notify']; } } } if (count($deny_arr) && !count($allow_arr)) { // One or more FB folks were denied access but nobody on FB was specifically allowed access. // This might cause the post to be open to public on Facebook, but only to selected members // on another network. Since this could potentially leak a post to somebody who was denied, // we will skip posting it to Facebook with a slightly vague but relevant message that will // hopefully lead somebody to this code comment for a better explanation of what went wrong. notice(t('Post to Facebook cancelled because of multi-network access permission conflict.') . EOL); return; } // if it's a private message but no Facebook members are allowed or denied, skip Facebook post if (!count($allow_arr) && !count($deny_arr)) { return; } } if ($b['verb'] == ACTIVITY_LIKE) { $likes = true; logger('fbpost_post_hook: liking ' . print_r($b, true), LOGGER_DEBUG); } $appid = get_config('facebook', 'appid'); $secret = get_config('facebook', 'appsecret'); if ($appid && $secret) { logger('fbpost_post_hook: have appid+secret'); $fb_token = get_pconfig($b['uid'], 'facebook', 'access_token'); // post to facebook if it's a public post and we've ticked the 'post to Facebook' box, // or it's a private message with facebook participants // or it's a reply or likes action to an existing facebook post if ($fb_token && ($toplevel || $b['private'] || $reply)) { logger('fbpost_post_hook: able to post'); require_once 'library/facebook.php'; require_once 'include/bbcode.php'; $msg = $b['body']; logger('fbpost_post_hook: original msg=' . $msg, LOGGER_DATA); if ($toplevel) { require_once "include/plaintext.php"; $msgarr = plaintext($a, $b, 0, false, 9); $msg = $msgarr["text"]; $link = $msgarr["url"]; $linkname = $msgarr["title"]; if ($msgarr["type"] != "video") { $image = $msgarr["image"]; } // Fallback - if message is empty if (!strlen($msg)) { $msg = $linkname; } if (!strlen($msg)) { $msg = $link; } if (!strlen($msg)) { $msg = $image; } } else { require_once "include/bbcode.php"; require_once "include/html2plain.php"; $msg = bb_CleanPictureLinks($msg); $msg = bbcode($msg, false, false, 2, true); $msg = trim(html2plain($msg, 0)); $link = ""; $image = ""; $linkname = ""; } // If there is nothing to post then exit if (!strlen($msg)) { return; } logger('fbpost_post_hook: msg=' . $msg, LOGGER_DATA); $video = ""; if ($likes) { $postvars = array('access_token' => $fb_token); } else { // message, picture, link, name, caption, description, source, place, tags //if(trim($link) != "") // if (@exif_imagetype($link) != 0) { // $image = $link; // $link = ""; // } $postvars = array('access_token' => $fb_token, 'message' => $msg); if (trim($image) != "") { $postvars['picture'] = $image; } if (trim($link) != "") { $postvars['link'] = $link; if (stristr($link, 'youtube') || stristr($link, 'youtu.be') || stristr($link, 'vimeo')) { $video = $link; } } if (trim($linkname) != "") { $postvars['name'] = $linkname; } } if ($b['private'] && $toplevel) { $postvars['privacy'] = '{"value": "CUSTOM", "friends": "SOME_FRIENDS"'; if (count($allow_arr)) { $postvars['privacy'] .= ',"allow": "' . implode(',', $allow_arr) . '"'; } if (count($deny_arr)) { $postvars['privacy'] .= ',"deny": "' . implode(',', $deny_arr) . '"'; } $postvars['privacy'] .= '}'; } $post_to_page = get_pconfig($b['uid'], 'facebook', 'post_to_page'); $page_access_token = get_pconfig($b['uid'], 'facebook', 'page_access_token'); if (intval($post_to_page) != 0 and $page_access_token != "") { $target = $post_to_page; } else { $target = "me"; } if ($reply) { $url = 'https://graph.facebook.com/' . $reply . '/' . ($likes ? 'likes' : 'comments'); } else { if ($video != "" or $image == "" and $link != "") { // If it is a link to a video or a link without a preview picture then post it as a link if ($video != "") { $link = $video; } $postvars = array('access_token' => $fb_token, 'link' => $link); if ($msg != $video) { $postvars['message'] = $msg; } $url = 'https://graph.facebook.com/' . $target . '/links'; } else { if ($link == "" and $image != "") { // If it is only an image without a page link then post this image as a photo $postvars = array('access_token' => $fb_token, 'url' => $image); if ($msg != $image) { $postvars['message'] = $msg; } $url = 'https://graph.facebook.com/' . $target . '/photos'; //} else if (($link != "") or ($image != "") or ($b['title'] == '') or (strlen($msg) < 500)) { } else { $url = 'https://graph.facebook.com/' . $target . '/feed'; if (!get_pconfig($b['uid'], 'facebook', 'suppress_view_on_friendica') and $b['plink']) { $postvars['actions'] = '{"name": "' . t('View on Friendica') . '", "link": "' . $b['plink'] . '"}'; } } } } /* } else { // if its only a message and a subject and the message is larger than 500 characters then post it as note $postvars = array( 'access_token' => $fb_token, 'message' => bbcode($b['body'], false, false), 'subject' => $b['title'], ); $url = 'https://graph.facebook.com/'.$target.'/notes'; } */ // Post to page? if (!$reply and $target != "me" and $page_access_token) { $postvars['access_token'] = $page_access_token; } logger('fbpost_post_hook: post to ' . $url); logger('fbpost_post_hook: postvars: ' . print_r($postvars, true)); // "test_mode" prevents anything from actually being posted. // Otherwise, let's do it. if (!get_config('facebook', 'test_mode')) { $x = post_url($url, $postvars); logger('fbpost_post_hook: post returns: ' . $x, LOGGER_DEBUG); $retj = json_decode($x); if ($retj->id) { // Only set the extid when it isn't the toplevel post q("UPDATE `item` SET `extid` = '%s' WHERE `id` = %d AND `parent` != %d", dbesc('fb::' . $retj->id), intval($b['id']), intval($b['id'])); } else { // Sometimes posts are accepted from facebook although it telling an error // This leads to endless comment flooding. // If it is a special kind of failure the post was receiced // Although facebook said it wasn't received ... if (!$likes and ($retj->error->type != "OAuthException" or $retj->error->code != 2) and $x != "") { $r = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self`", intval($b['uid'])); if (count($r)) { $a->contact = $r[0]["id"]; } $s = serialize(array('url' => $url, 'item' => $b['id'], 'post' => $postvars)); require_once 'include/queue_fn.php'; add_to_queue($a->contact, NETWORK_FACEBOOK, $s); logger('fbpost_post_hook: Post failed, requeued.', LOGGER_DEBUG); notice(t('Facebook post failed. Queued for retry.') . EOL); } if (isset($retj->error) && $retj->error->type == "OAuthException" && $retj->error->code == 190) { logger('fbpost_post_hook: Facebook session has expired due to changed password.', LOGGER_DEBUG); $last_notification = get_pconfig($b['uid'], 'facebook', 'session_expired_mailsent'); if (!$last_notification || $last_notification < time() - FACEBOOK_SESSION_ERR_NOTIFICATION_INTERVAL) { require_once 'include/enotify.php'; $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($b['uid'])); notification(array('uid' => $b['uid'], 'type' => NOTIFY_SYSTEM, 'system_type' => 'facebook_connection_invalid', 'language' => $r[0]['language'], 'to_name' => $r[0]['username'], 'to_email' => $r[0]['email'], 'source_name' => t('Administrator'), 'source_link' => $a->config["system"]["url"], 'source_photo' => $a->config["system"]["url"] . '/images/person-80.jpg')); set_pconfig($b['uid'], 'facebook', 'session_expired_mailsent', time()); } else { logger('fbpost_post_hook: No notification, as the last one was sent on ' . $last_notification, LOGGER_DEBUG); } } } } } } } }
function diaspora_retraction($importer, $xml, $msg = null) { $guid = notags(diaspora_get_target_guid($xml)); $diaspora_handle = notags(diaspora_get_author($xml)); $type = notags(diaspora_get_type($xml)); $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { return; } if ($type === 'Person' || $type === 'Contact') { contact_remove($importer['channel_id'], $contact['abook_id']); } elseif ($type === 'Post' || $type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc('guid'), intval($importer['channel_id'])); if ($r) { if (link_compare($r[0]['author_xchan'], $contact['xchan_hash']) || link_compare($r[0]['owner_xchan'], $contact['xchan_hash'])) { drop_item($r[0]['id'], false); } // @FIXME - ensure that relay is performed if this was an upstream // Could probably check if we're the owner and it is a like or comment // This may or may not be handled by drop_item } } return 202; // NOTREACHED }
function superblock_item_photo_menu(&$a, &$b) { if (!local_channel()) { return; } $blocked = false; $author = $b['item']['author_xchan']; if (App::$channel['channel_hash'] == $author) { return; } if (is_array(App::$data['superblock'])) { foreach (App::$data['superblock'] as $bloke) { if (link_compare($bloke, $author)) { $blocked = true; break; } } } $b['author_menu'][t('Block Completely')] = 'javascript:superblockBlock(\'' . $author . '\'); return false;'; }
function notifier_run(&$argv, &$argc) { global $a, $db; if (is_null($a)) { $a = new App(); } if (is_null($db)) { @(include ".htconfig.php"); require_once "include/dba.php"; $db = new dba($db_host, $db_user, $db_pass, $db_data); unset($db_host, $db_user, $db_pass, $db_data); } require_once "include/session.php"; require_once "include/datetime.php"; require_once 'include/items.php'; require_once 'include/bbcode.php'; require_once 'include/email.php'; load_config('config'); load_config('system'); load_hooks(); if ($argc < 3) { return; } $a->set_baseurl(get_config('system', 'url')); logger('notifier: invoked: ' . print_r($argv, true), LOGGER_DEBUG); $cmd = $argv[1]; switch ($cmd) { case 'mail': default: $item_id = intval($argv[2]); if (!$item_id) { return; } break; } $expire = false; $mail = false; $fsuggest = false; $relocate = false; $top_level = false; $recipients = array(); $url_recipients = array(); $normal_mode = true; if ($cmd === 'mail') { $normal_mode = false; $mail = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id)); if (!count($message)) { return; } $uid = $message[0]['uid']; $recipients[] = $message[0]['contact-id']; $item = $message[0]; } elseif ($cmd === 'expire') { $normal_mode = false; $expire = true; $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1\n\t\t\tAND `deleted` = 1 AND `changed` > UTC_TIMESTAMP() - INTERVAL 10 MINUTE", intval($item_id)); $uid = $item_id; $item_id = 0; if (!count($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 === 'removeme') { $r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($item_id)); if (!$r) { return; } $user = $r[0]; $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", intval($item_id)); if (!$r) { return; } $self = $r[0]; $r = q("SELECT * FROM `contact` WHERE `self` = 0 AND `uid` = %d", intval($item_id)); if (!$r) { return; } require_once 'include/Contact.php'; foreach ($r as $contact) { terminate_friendship($user, $self, $contact); } return; } elseif ($cmd === 'relocate') { $normal_mode = false; $relocate = true; $uid = $item_id; } else { // find ancestors $r = q("SELECT * FROM `item` WHERE `id` = %d and visible = 1 and moderated = 0 LIMIT 1", intval($item_id)); if (!count($r) || !intval($r[0]['parent'])) { return; } $target_item = $r[0]; $parent_id = intval($r[0]['parent']); $uid = $r[0]['uid']; $updated = $r[0]['edited']; // POSSIBLE CLEANUP --> The following seems superfluous. We've already checked for "if (! intval($r[0]['parent']))" a few lines up if (!$parent_id) { return; } $items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer`\n\t\t\tFROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d and visible = 1 and moderated = 0 ORDER BY `id` ASC", intval($parent_id)); if (!count($items)) { return; } // avoid race condition with deleting entries if ($items[0]['deleted']) { foreach ($items as $item) { $item['deleted'] = 1; } } if (count($items) == 1 && $items[0]['id'] === $target_item['id'] && $items[0]['uri'] === $items[0]['parent-uri']) { logger('notifier: top level post'); $top_level = true; } } $r = q("SELECT `contact`.*, `user`.`pubkey` AS `upubkey`, `user`.`prvkey` AS `uprvkey`,\n\t\t`user`.`timezone`, `user`.`nickname`, `user`.`sprvkey`, `user`.`spubkey`,\n\t\t`user`.`page-flags`, `user`.`prvnets`\n\t\tFROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`\n\t\tWHERE `contact`.`uid` = %d AND `contact`.`self` = 1 LIMIT 1", intval($uid)); if (!count($r)) { return; } $owner = $r[0]; $walltowall = $top_level && $owner['id'] != $items[0]['contact-id'] ? true : false; $hub = get_config('system', 'huburl'); // If this is a public conversation, notify the feed hub $public_message = true; // Do a PuSH $push_notify = false; // fill this in with a single salmon slap if applicable $slap = ''; if (!($mail || $fsuggest || $relocate)) { require_once 'include/group.php'; $parent = $items[0]; $thr_parent = q("SELECT `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d", dbesc($target_item["thr-parent"]), intval($target_item["uid"])); logger('Parent is ' . $parent['network'] . '. Thread parent is ' . $thr_parent[0]['network'], LOGGER_DEBUG); // This is IMPORTANT!!!! // We will only send a "notify owner to relay" or followup message if the referenced post // originated on our system by virtue of having our hostname somewhere // in the URI, AND it was a comment (not top_level) AND the parent originated elsewhere. // if $parent['wall'] == 1 we will already have the parent message in our array // and we will relay the whole lot. // expire sends an entire group of expire messages and cannot be forwarded. // However the conversation owner will be a part of the conversation and will // be notified during this run. // Other DFRN conversation members will be alerted during polled updates. // Diaspora members currently are not notified of expirations, and other networks have // either limited or no ability to process deletions. We should at least fix Diaspora // by stringing togther an array of retractions and sending them onward. $localhost = str_replace('www.', '', $a->get_hostname()); if (strpos($localhost, ':')) { $localhost = substr($localhost, 0, strpos($localhost, ':')); } /** * * Be VERY CAREFUL if you make any changes to the following several lines. Seemingly innocuous changes * have been known to cause runaway conditions which affected several servers, along with * permissions issues. * */ $relay_to_owner = false; if (!$top_level && $parent['wall'] == 0 && !$expire && stristr($target_item['uri'], $localhost)) { $relay_to_owner = true; } if ($cmd === 'uplink' && intval($parent['forum_mode']) == 1 && !$top_level) { $relay_to_owner = true; } // until the 'origin' flag has been in use for several months // we will just use it as a fallback test // later we will be able to use it as the primary test of whether or not to relay. if (!$target_item['origin']) { $relay_to_owner = false; } if ($parent['origin']) { $relay_to_owner = false; } if ($relay_to_owner) { logger('notifier: followup ' . $target_item["guid"], LOGGER_DEBUG); // local followup to remote post $followup = true; $public_message = false; // not public $conversant_str = dbesc($parent['contact-id']); $recipients = array($parent['contact-id']); if (!$target_item['private'] and $target_item['wall'] and strlen($target_item['allow_cid'] . $target_item['allow_gid'] . $target_item['deny_cid'] . $target_item['deny_gid']) == 0) { $push_notify = true; } // We notify Friendica users in the thread when it is an OStatus thread. // Hopefully this transfers the messages to the other Friendica servers. (Untested) if ($thr_parent and $thr_parent[0]['network'] == NETWORK_OSTATUS or $parent['network'] == NETWORK_OSTATUS) { $push_notify = true; if ($parent["network"] == NETWORK_OSTATUS) { $r = q("SELECT `author-link` FROM `item` WHERE `parent` = %d AND `author-link` != '%s'", intval($target_item["parent"]), dbesc($owner['url'])); foreach ($r as $parent_item) { $probed_contact = probe_url($parent_item["author-link"]); if ($probed_contact["notify"] != "" and $probed_contact["network"] == NETWORK_DFRN) { logger('Notify Friendica user ' . $probed_contact["url"] . ': ' . $probed_contact["notify"]); $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; } } } if (count($url_recipients)) { logger("url_recipients " . print_r($url_recipients, true)); } } } else { $followup = false; logger('Distributing directly ' . $target_item["guid"], LOGGER_DEBUG); // don't send deletions onward for other people's stuff if ($target_item['deleted'] && !intval($target_item['wall'])) { logger('notifier: ignoring delete notification for non-wall item'); return; } if (strlen($parent['allow_cid']) || strlen($parent['allow_gid']) || strlen($parent['deny_cid']) || strlen($parent['deny_gid'])) { $public_message = false; // private recipients, not public } $allow_people = expand_acl($parent['allow_cid']); $allow_groups = expand_groups(expand_acl($parent['allow_gid']), true); $deny_people = expand_acl($parent['deny_cid']); $deny_groups = expand_groups(expand_acl($parent['deny_gid'])); // if our parent is a public forum (forum_mode == 1), uplink to the origional author causing // a delivery fork. private groups (forum_mode == 2) do not uplink if (intval($parent['forum_mode']) == 1 && !$top_level && $cmd !== 'uplink') { proc_run('php', 'include/notifier.php', 'uplink', $item_id); } $conversants = array(); foreach ($items as $item) { $recipients[] = $item['contact-id']; $conversants[] = $item['contact-id']; // pull out additional tagged people to notify (if public message) if ($public_message && strlen($item['inform'])) { $people = explode(',', $item['inform']); foreach ($people as $person) { if (substr($person, 0, 4) === 'cid:') { $recipients[] = intval(substr($person, 4)); $conversants[] = intval(substr($person, 4)); } else { $url_recipients[] = substr($person, 4); } } } } if (count($url_recipients)) { logger('notifier: ' . $target_item["guid"] . ' url_recipients ' . print_r($url_recipients, true)); } $conversants = array_unique($conversants); $recipients = array_unique(array_merge($recipients, $allow_people, $allow_groups)); $deny = array_unique(array_merge($deny_people, $deny_groups)); $recipients = array_diff($recipients, $deny); $conversant_str = dbesc(implode(', ', $conversants)); } // If the thread parent is OStatus then do some magic to distribute the messages. // We have not only to look at the parent, since it could be a Friendica thread. if ($thr_parent and $thr_parent[0]['network'] == NETWORK_OSTATUS or $parent['network'] == NETWORK_OSTATUS) { logger('Some parent is OStatus for ' . $target_item["guid"], LOGGER_DEBUG); // Send a salmon notification to every person we mentioned in the post $arr = explode(',', $target_item['tag']); foreach ($arr as $x) { //logger('Checking tag '.$x, LOGGER_DEBUG); $matches = null; if (preg_match('/@\\[url=([^\\]]*)\\]/', $x, $matches)) { $probed_contact = probe_url($matches[1]); if ($probed_contact["notify"] != "") { logger('Notify mentioned user ' . $probed_contact["url"] . ': ' . $probed_contact["notify"]); $url_recipients[$probed_contact["notify"]] = $probed_contact["notify"]; } } } // It only makes sense to distribute answers to OStatus messages to Friendica and OStatus - but not Diaspora $sql_extra = " AND `network` IN ('" . NETWORK_OSTATUS . "', '" . NETWORK_DFRN . "')"; } else { $sql_extra = ""; } $r = q("SELECT * FROM `contact` WHERE `id` IN ({$conversant_str}) AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0" . $sql_extra); if (count($r)) { $contacts = $r; } } $feed_template = get_markup_template('atom_feed.tpl'); $mail_template = get_markup_template('atom_mail.tpl'); $atom = ''; $slaps = array(); $hubxml = feed_hublinks(); $birthday = feed_birthday($owner['uid'], $owner['timezone']); if (strlen($birthday)) { $birthday = '<dfrn:birthday>' . xmlify($birthday) . '</dfrn:birthday>'; } $atom .= replace_macros($feed_template, array('$version' => xmlify(FRIENDICA_VERSION), '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname']), '$feed_title' => xmlify($owner['name']), '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00', ATOM_TIME)), '$hub' => $hubxml, '$salmon' => '', '$name' => xmlify($owner['name']), '$profile_page' => xmlify($owner['url']), '$photo' => xmlify($owner['photo']), '$thumb' => xmlify($owner['thumb']), '$picdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['avatar-date'] . '+00:00', ATOM_TIME)), '$uridate' => xmlify(datetime_convert('UTC', 'UTC', $owner['uri-date'] . '+00:00', ATOM_TIME)), '$namdate' => xmlify(datetime_convert('UTC', 'UTC', $owner['name-date'] . '+00:00', ATOM_TIME)), '$birthday' => $birthday, '$community' => $owner['page-flags'] == PAGE_COMMUNITY ? '<dfrn:community>1</dfrn:community>' : '')); if ($mail) { $public_message = false; // mail is not public $body = fix_private_photos($item['body'], $owner['uid'], null, $message[0]['contact-id']); $atom .= replace_macros($mail_template, array('$name' => xmlify($owner['name']), '$profile_page' => xmlify($owner['url']), '$thumb' => xmlify($owner['thumb']), '$item_id' => xmlify($item['uri']), '$subject' => xmlify($item['title']), '$created' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00', ATOM_TIME)), '$content' => xmlify($body), '$parent_id' => xmlify($item['parent-uri']))); } elseif ($fsuggest) { $public_message = false; // suggestions are not public $sugg_template = get_markup_template('atom_suggest.tpl'); $atom .= replace_macros($sugg_template, array('$name' => xmlify($item['name']), '$url' => xmlify($item['url']), '$photo' => xmlify($item['photo']), '$request' => xmlify($item['request']), '$note' => xmlify($item['note']))); // We don't need this any more q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); } elseif ($relocate) { $public_message = false; // suggestions are not public $sugg_template = get_markup_template('atom_relocate.tpl'); /* get site pubkey. this could be a new installation with no site keys*/ $pubkey = get_config('system', 'site_pubkey'); if (!$pubkey) { $res = new_keypair(1024); set_config('system', 'site_prvkey', $res['prvkey']); set_config('system', 'site_pubkey', $res['pubkey']); } $rp = q("SELECT `resource-id` , `scale`, type FROM `photo` \n\t\t\t\t\t\tWHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid); $photos = array(); $ext = Photo::supportedTypes(); foreach ($rp as $p) { $photos[$p['scale']] = $a->get_baseurl() . '/photo/' . $p['resource-id'] . '-' . $p['scale'] . '.' . $ext[$p['type']]; } unset($rp, $ext); $atom .= replace_macros($sugg_template, array('$name' => xmlify($owner['name']), '$photo' => xmlify($photos[4]), '$thumb' => xmlify($photos[5]), '$micro' => xmlify($photos[6]), '$url' => xmlify($owner['url']), '$request' => xmlify($owner['request']), '$confirm' => xmlify($owner['confirm']), '$notify' => xmlify($owner['notify']), '$poll' => xmlify($owner['poll']), '$sitepubkey' => xmlify(get_config('system', 'site_pubkey')))); $recipients_relocate = q("SELECT * FROM contact WHERE uid = %d AND self = 0 AND network = '%s'", intval($uid), NETWORK_DFRN); unset($photos); } else { $slap = ostatus_salmon($target_item, $owner); //$slap = atom_entry($target_item,'html',null,$owner,false); if ($followup) { foreach ($items as $item) { // there is only one item if (!$item['parent']) { continue; } if ($item['id'] == $item_id) { logger('notifier: followup: item: ' . print_r($item, true), LOGGER_DATA); //$slap = atom_entry($item,'html',null,$owner,false); $atom .= atom_entry($item, 'text', null, $owner, false); } } } else { foreach ($items as $item) { if (!$item['parent']) { continue; } // private emails may be in included in public conversations. Filter them. if ($public_message && $item['private'] == 1) { continue; } $contact = get_item_contact($item, $contacts); if (!$contact) { continue; } if ($normal_mode) { // we only need the current item, but include the parent because without it // older sites without a corresponding dfrn_notify change may do the wrong thing. if ($item_id == $item['id'] || $item['id'] == $item['parent']) { $atom .= atom_entry($item, 'text', null, $owner, true); } } else { $atom .= atom_entry($item, 'text', null, $owner, true); } if ($top_level && $public_message && $item['author-link'] === $item['owner-link'] && !$expire) { $slaps[] = ostatus_salmon($item, $owner); } //$slaps[] = atom_entry($item,'html',null,$owner,true); } } } $atom .= '</feed>' . "\r\n"; logger('notifier: ' . $atom, LOGGER_DATA); logger('notifier: slaps: ' . print_r($slaps, true), LOGGER_DATA); // If this is a public message and pubmail is set on the parent, include all your email contacts $mail_disabled = function_exists('imap_open') && !get_config('system', 'imap_disabled') ? 0 : 1; if (!$mail_disabled) { if (!strlen($target_item['allow_cid']) && !strlen($target_item['allow_gid']) && !strlen($target_item['deny_cid']) && !strlen($target_item['deny_gid']) && intval($target_item['pubmail'])) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `network` = '%s'", intval($uid), dbesc(NETWORK_MAIL)); if (count($r)) { foreach ($r as $rr) { $recipients[] = $rr['id']; } } } } if ($followup) { $recip_str = $parent['contact-id']; } else { $recip_str = implode(', ', $recipients); } if ($relocate) { $r = $recipients_relocate; } else { $r = q("SELECT * FROM `contact` WHERE `id` IN ( %s ) AND `blocked` = 0 AND `pending` = 0 ", dbesc($recip_str)); } require_once 'include/salmon.php'; $interval = get_config('system', 'delivery_interval') === false ? 2 : intval(get_config('system', 'delivery_interval')); // If we are using the worker we don't need a delivery interval if (get_config("system", "worker")) { $interval = false; } // delivery loop if (count($r)) { foreach ($r as $contact) { if (!$mail && !$fsuggest && !$followup && !$relocate && !$contact['self']) { if ($contact['network'] === NETWORK_DIASPORA && $public_message) { continue; } q("insert into deliverq ( `cmd`,`item`,`contact` ) values ('%s', %d, %d )", dbesc($cmd), intval($item_id), intval($contact['id'])); } } // This controls the number of deliveries to execute with each separate delivery process. // By default we'll perform one delivery per process. Assuming a hostile shared hosting // provider, this provides the greatest chance of deliveries if processes start getting // killed. We can also space them out with the delivery_interval to also help avoid them // getting whacked. // If $deliveries_per_process > 1, we will chain this number of multiple deliveries // together into a single process. This will reduce the overall number of processes // spawned for each delivery, but they will run longer. // When using the workerqueue, we don't need this functionality. $deliveries_per_process = intval(get_config('system', 'delivery_batch_count')); if ($deliveries_per_process <= 0 or get_config("system", "worker")) { $deliveries_per_process = 1; } $this_batch = array(); for ($x = 0; $x < count($r); $x++) { $contact = $r[$x]; if ($contact['self']) { continue; } logger("Deliver " . $target_item["guid"] . " to " . $contact['url'], LOGGER_DEBUG); // potentially more than one recipient. Start a new process and space them out a bit. // we will deliver single recipient types of message and email recipients here. if (!$mail && !$fsuggest && !$relocate && !$followup) { $this_batch[] = $contact['id']; if (count($this_batch) == $deliveries_per_process) { proc_run('php', 'include/delivery.php', $cmd, $item_id, $this_batch); $this_batch = array(); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } continue; } // be sure to pick up any stragglers if (count($this_batch)) { proc_run('php', 'include/delivery.php', $cmd, $item_id, $this_batch); } $deliver_status = 0; logger("main delivery by notifier: followup={$followup} mail={$mail} fsuggest={$fsuggest} relocate={$relocate}"); switch ($contact['network']) { case NETWORK_DFRN: // perform local delivery if we are on the same site $basepath = implode('/', array_slice(explode('/', $contact['url']), 0, 3)); if (link_compare($basepath, $a->get_baseurl())) { $nickname = basename($contact['url']); if ($contact['issued-id']) { $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id'])); } else { $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id'])); } $x = q("SELECT\t`contact`.*, `contact`.`uid` AS `importer_uid`,\n\t\t\t\t\t\t\t`contact`.`pubkey` AS `cpubkey`,\n\t\t\t\t\t\t\t`contact`.`prvkey` AS `cprvkey`,\n\t\t\t\t\t\t\t`contact`.`thumb` AS `thumb`,\n\t\t\t\t\t\t\t`contact`.`url` as `url`,\n\t\t\t\t\t\t\t`contact`.`name` as `senderName`,\n\t\t\t\t\t\t\t`user`.*\n\t\t\t\t\t\t\tFROM `contact`\n\t\t\t\t\t\t\tINNER JOIN `user` ON `contact`.`uid` = `user`.`uid`\n\t\t\t\t\t\t\tWHERE `contact`.`blocked` = 0 AND `contact`.`archive` = 0\n\t\t\t\t\t\t\tAND `contact`.`pending` = 0\n\t\t\t\t\t\t\tAND `contact`.`network` = '%s' AND `user`.`nickname` = '%s'\n\t\t\t\t\t\t\t{$sql_extra}\n\t\t\t\t\t\t\tAND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 LIMIT 1", dbesc(NETWORK_DFRN), dbesc($nickname)); if ($x && count($x)) { $write_flag = $x[0]['rel'] && $x[0]['rel'] != CONTACT_IS_SHARING ? true : false; if (($owner['page-flags'] == PAGE_COMMUNITY || $write_flag) && !$x[0]['writable']) { q("update contact set writable = 1 where id = %d", intval($x[0]['id'])); $x[0]['writable'] = 1; } // if contact's ssl policy changed, which we just determined // is on our own server, update our contact links $ssl_policy = get_config('system', 'ssl_policy'); fix_contact_ssl_policy($x[0], $ssl_policy); // If we are setup as a soapbox we aren't accepting top level posts from this person if ($x[0]['page-flags'] == PAGE_SOAPBOX and $top_level) { break; } require_once 'library/simplepie/simplepie.inc'; logger('mod-delivery: local delivery'); local_delivery($x[0], $atom); break; } } logger('notifier: dfrndelivery: ' . $contact['name']); $deliver_status = dfrn_deliver($owner, $contact, $atom); logger('notifier: dfrn_delivery returns ' . $deliver_status); if ($deliver_status == -1) { logger('notifier: delivery failed: queuing message'); // queue message for redelivery add_to_queue($contact['id'], NETWORK_DFRN, $atom); } break; case NETWORK_OSTATUS: // Do not send to ostatus if we are not configured to send to public networks if ($owner['prvnets']) { break; } if (get_config('system', 'ostatus_disabled') || get_config('system', 'dfrn_only')) { break; } if ($followup && $contact['notify']) { logger('slapdelivery followup item ' . $item_id . ' to ' . $contact['name']); $deliver_status = slapper($owner, $contact['notify'], $slap); if ($deliver_status == -1) { // queue message for redelivery add_to_queue($contact['id'], NETWORK_OSTATUS, $slap); } } else { // only send salmon if public - e.g. if it's ok to notify // a public hub, it's ok to send a salmon if (count($slaps) && $public_message && !$expire) { logger('slapdelivery item ' . $item_id . ' to ' . $contact['name']); foreach ($slaps as $slappy) { if ($contact['notify']) { $deliver_status = slapper($owner, $contact['notify'], $slappy); if ($deliver_status == -1) { // queue message for redelivery add_to_queue($contact['id'], NETWORK_OSTATUS, $slappy); } } } } } break; case NETWORK_MAIL: case NETWORK_MAIL2: if (get_config('system', 'dfrn_only')) { break; } // WARNING: does not currently convert to RFC2047 header encodings, etc. $addr = $contact['addr']; if (!strlen($addr)) { break; } if ($cmd === 'wall-new' || $cmd === 'comment-new') { $it = null; if ($cmd === 'wall-new') { $it = $items[0]; } else { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($argv[2]), intval($uid)); if (count($r)) { $it = $r[0]; } } if (!$it) { break; } $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid)); if (!count($local_user)) { break; } $reply_to = ''; $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid)); if ($r1 && $r1[0]['reply_to']) { $reply_to = $r1[0]['reply_to']; } $subject = $it['title'] ? email_header_encode($it['title'], 'UTF-8') : t("(no subject)"); // only expose our real email address to true friends if ($contact['rel'] == CONTACT_IS_FRIEND && !$contact['blocked']) { if ($reply_to) { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . $reply_to . '>' . "\n"; $headers .= 'Sender: ' . $local_user[0]['email'] . "\n"; } else { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . $local_user[0]['email'] . '>' . "\n"; } } else { $headers = 'From: ' . email_header_encode($local_user[0]['username'], 'UTF-8') . ' <' . t('noreply') . '@' . $a->get_hostname() . '>' . "\n"; } //if($reply_to) // $headers .= 'Reply-to: ' . $reply_to . "\n"; $headers .= 'Message-Id: <' . iri2msgid($it['uri']) . '>' . "\n"; if ($it['uri'] !== $it['parent-uri']) { $headers .= "References: <" . iri2msgid($it["parent-uri"]) . ">"; // If Threading is enabled, write down the correct parent if ($it["thr-parent"] != "" and $it["thr-parent"] != $it["parent-uri"]) { $headers .= " <" . iri2msgid($it["thr-parent"]) . ">"; } $headers .= "\n"; if (!$it['title']) { $r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); if (count($r) and $r[0]['title'] != '') { $subject = $r[0]['title']; } else { $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); if (count($r) and $r[0]['title'] != '') { $subject = $r[0]['title']; } } } if (strncasecmp($subject, 'RE:', 3)) { $subject = 'Re: ' . $subject; } } email_send($addr, $subject, $headers, $it); } break; case NETWORK_DIASPORA: if (get_config('system', 'dfrn_only') || !get_config('system', 'diaspora_enabled')) { break; } if ($mail) { diaspora_send_mail($item, $owner, $contact); break; } if (!$normal_mode) { break; } // special handling for followup to public post // all other public posts processed as public batches further below if ($public_message) { if ($followup) { diaspora_send_followup($target_item, $owner, $contact, true); } break; } if (!$contact['pubkey']) { break; } $unsupported_activities = array(ACTIVITY_DISLIKE, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE); //don't transmit activities which are not supported by diaspora foreach ($unsupported_activities as $act) { if (activity_match($target_item['verb'], $act)) { break 2; } } if ($target_item['deleted'] && ($target_item['uri'] === $target_item['parent-uri'] || $followup)) { // send both top-level retractions and relayable retractions for owner to relay diaspora_send_retraction($target_item, $owner, $contact); break; } elseif ($followup) { // send comments and likes to owner to relay diaspora_send_followup($target_item, $owner, $contact); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions // (of comments and likes) to our conversants diaspora_send_relay($target_item, $owner, $contact); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall diaspora_send_status($target_item, $owner, $contact); break; } break; case NETWORK_FEED: case NETWORK_FACEBOOK: if (get_config('system', 'dfrn_only')) { break; } case NETWORK_PUMPIO: if (get_config('system', 'dfrn_only')) { break; } default: break; } } } // send additional slaps to mentioned remote tags (@foo@example.com) //if($slap && count($url_recipients) && ($followup || $top_level) && ($public_message || $push_notify) && (! $expire)) { if ($slap && count($url_recipients) && ($public_message || $push_notify) && !$expire) { if (!get_config('system', 'dfrn_only')) { foreach ($url_recipients as $url) { if ($url) { logger('notifier: urldelivery: ' . $url); $deliver_status = slapper($owner, $url, $slap); // TODO: redeliver/queue these items on failure, though there is no contact record } } } } if ($public_message) { if (!$followup) { $r0 = diaspora_fetch_relay(); } else { $r0 = array(); } $r1 = q("SELECT DISTINCT(`batch`), `id`, `name`,`network` FROM `contact` WHERE `network` = '%s'\n\t\t\tAND `uid` = %d AND `rel` != %d group by `batch` ORDER BY rand() ", dbesc(NETWORK_DIASPORA), intval($owner['uid']), intval(CONTACT_IS_SHARING)); $r2 = q("SELECT `id`, `name`,`network` FROM `contact`\n\t\t\tWHERE `network` in ( '%s', '%s') AND `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0\n\t\t\tAND `rel` != %d order by rand() ", dbesc(NETWORK_DFRN), dbesc(NETWORK_MAIL2), intval($owner['uid']), intval(CONTACT_IS_SHARING)); $r = array_merge($r2, $r1, $r0); if (count($r)) { logger('pubdeliver: ' . print_r($r, true), LOGGER_DEBUG); // throw everything into the queue in case we get killed foreach ($r as $rr) { if (!$mail && !$fsuggest && !$followup) { q("insert into deliverq ( `cmd`,`item`,`contact` ) values ('%s', %d, %d )", dbesc($cmd), intval($item_id), intval($rr['id'])); } } foreach ($r as $rr) { // except for Diaspora batch jobs // Don't deliver to folks who have already been delivered to if ($rr['network'] !== NETWORK_DIASPORA && in_array($rr['id'], $conversants)) { logger('notifier: already delivered id=' . $rr['id']); continue; } if (!$mail && !$fsuggest && !$followup) { logger('notifier: delivery agent: ' . $rr['name'] . ' ' . $rr['id']); proc_run('php', 'include/delivery.php', $cmd, $item_id, $rr['id']); if ($interval) { @time_sleep_until(microtime(true) + (double) $interval); } } } } $push_notify = true; } if ($push_notify and strlen($hub)) { $hubs = explode(',', $hub); if (count($hubs)) { foreach ($hubs as $h) { $h = trim($h); if (!strlen($h)) { continue; } if ($h === '[internal]') { // Set push flag for PuSH subscribers to this topic, // they will be notified in queue.php q("UPDATE `push_subscriber` SET `push` = 1 " . "WHERE `nickname` = '%s'", dbesc($owner['nickname'])); logger('Activating internal PuSH for item ' . $item_id, LOGGER_DEBUG); } else { $params = 'hub.mode=publish&hub.url=' . urlencode($a->get_baseurl() . '/dfrn_poll/' . $owner['nickname']); post_url($h, $params); logger('publish for item ' . $item_id . ' ' . $h . ' ' . $params . ' returned ' . $a->get_curl_code()); } if (count($hubs) > 1) { sleep(7); } // try and avoid multiple hubs responding at precisely the same time } } // Handling the pubsubhubbub requests proc_run('php', 'include/pubsubpublish.php'); } // If the item was deleted, clean up the `sign` table if ($target_item['deleted']) { $r = q("DELETE FROM sign where `retract_iid` = %d", intval($target_item['id'])); } logger('notifier: calling hooks', LOGGER_DEBUG); if ($normal_mode) { call_hooks('notifier_normal', $target_item); } call_hooks('notifier_end', $target_item); return; }
function local_delivery($importer, $data) { $a = get_app(); if ($importer['readonly']) { // We aren't receiving stuff from this person. But we will quietly ignore them // rather than a blatant "go away" message. logger('local_delivery: ignoring'); return 0; //NOTREACHED } // Consume notification feed. This may differ from consuming a public feed in several ways // - might contain email or friend suggestions // - might contain remote followup to our message // - in which case we need to accept it and then notify other conversants // - we may need to send various email notifications $feed = new SimplePie(); $feed->set_raw_data($data); $feed->enable_order_by_date(false); $feed->init(); /* // Currently unsupported - needs a lot of work $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); if(isset($reloc[0]['child'][NAMESPACE_DFRN])) { $base = $reloc[0]['child'][NAMESPACE_DFRN]; $newloc = array(); $newloc['uid'] = $importer['importer_uid']; $newloc['cid'] = $importer['id']; $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); $newloc['site-pubkey'] = notags(unxmlify($base['site-pubkey'][0]['data'])); $newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data'])); // TODO // merge with current record, current contents have priority // update record, set url-updated // update profile photos // schedule a scan? } */ // handle friend suggestion notification $sugg = $feed->get_feed_tags(NAMESPACE_DFRN, 'suggest'); if (isset($sugg[0]['child'][NAMESPACE_DFRN])) { $base = $sugg[0]['child'][NAMESPACE_DFRN]; $fsugg = array(); $fsugg['uid'] = $importer['importer_uid']; $fsugg['cid'] = $importer['id']; $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); // Does our member already have a friend matching this description? $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", dbesc($fsugg['name']), dbesc(normalise_link($fsugg['url'])), intval($fsugg['uid'])); if (count($r)) { return 0; } // Do we already have an fcontact record for this person? $fid = 0; $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; // OK, we do. Do we already have an introduction for this person ? $r = q("select id from intro where uid = %d and fid = %d limit 1", intval($fsugg['uid']), intval($fid)); if (count($r)) { return 0; } } if (!$fid) { $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", dbesc($fsugg['name']), dbesc($fsugg['url']), dbesc($fsugg['photo']), dbesc($fsugg['request'])); } $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; } else { return 0; } $hash = random_string(); $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )\n\t\t\tVALUES( %d, %d, %d, '%s', '%s', '%s', %d )", intval($fsugg['uid']), intval($fid), intval($fsugg['cid']), dbesc($fsugg['body']), dbesc($hash), dbesc(datetime_convert()), intval(0)); notification(array('type' => NOTIFY_SUGGEST, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $fsugg, 'link' => $a->get_baseurl() . '/notifications/intros', 'source_name' => $importer['name'], 'source_link' => $importer['url'], 'source_photo' => $importer['photo'], 'verb' => ACTIVITY_REQ_FRIEND, 'otype' => 'intro')); return 0; } $ismail = false; $rawmail = $feed->get_feed_tags(NAMESPACE_DFRN, 'mail'); if (isset($rawmail[0]['child'][NAMESPACE_DFRN])) { logger('local_delivery: private message received'); $ismail = true; $base = $rawmail[0]['child'][NAMESPACE_DFRN]; $msg = array(); $msg['uid'] = $importer['importer_uid']; $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); $msg['contact-id'] = $importer['id']; $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); $msg['seen'] = 0; $msg['replied'] = 0; $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); $msg['created'] = datetime_convert(notags(unxmlify('UTC', 'UTC', $base['sentdate'][0]['data']))); dbesc_array($msg); $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) . "`) VALUES ('" . implode("', '", array_values($msg)) . "')"); // send notifications. require_once 'include/enotify.php'; $notif_params = array('type' => NOTIFY_MAIL, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $msg, 'source_name' => $msg['from-name'], 'source_link' => $importer['url'], 'source_photo' => $importer['thumb'], 'verb' => ACTIVITY_POST, 'otype' => 'mail'); notification($notif_params); return 0; // NOTREACHED } $community_page = 0; $rawtags = $feed->get_feed_tags(NAMESPACE_DFRN, 'community'); if ($rawtags) { $community_page = intval($rawtags[0]['data']); } if (intval($importer['forum']) != $community_page) { q("update contact set forum = %d where id = %d limit 1", intval($community_page), intval($importer['id'])); $importer['forum'] = (string) $community_page; } logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries)) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $uri = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted) { $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`\n\t\t\t\t\tWHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), intval($importer['importer_uid']), intval($importer['id'])); if (count($r)) { $item = $r[0]; if ($item['deleted']) { continue; } logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); if ($item['verb'] === ACTIVITY_TAG && $item['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($item['object'], false); $xt = parse_xml_string($item['target'], false); if ($xt->type === ACTIVITY_OBJ_NOTE) { $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (count($i)) { // For tags, the owner cannot remove the tag on the author's copy of the post. $owner_remove = $item['contact-id'] == $i[0]['contact-id'] ? true : false; $author_remove = $item['origin'] && $item['self'] ? true : false; $author_copy = $item['origin'] ? true : false; if ($owner_remove && $author_copy) { continue; } if ($author_remove || $owner_remove) { $tags = explode(',', $i[0]['tag']); $newtags = array(); if (count($tags)) { foreach ($tags as $tag) { if (trim($tag) !== trim($xo->body)) { $newtags[] = trim($tag); } } } q("update item set tag = '%s' where id = %d limit 1", dbesc(implode(',', $newtags)), intval($i[0]['id'])); } } } } if ($item['uri'] == $item['parent-uri']) { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s'\n\t\t\t\t\t\t\tWHERE `parent-uri` = '%s' AND `uid` = %d", dbesc($when), dbesc(datetime_convert()), dbesc($item['uri']), intval($importer['importer_uid'])); } else { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' \n\t\t\t\t\t\t\tWHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($when), dbesc(datetime_convert()), dbesc($uri), intval($importer['importer_uid'])); if ($item['last-child']) { // ensure that last-child is set in case the comment that had it just got wiped. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", dbesc(datetime_convert()), dbesc($item['parent-uri']), intval($item['uid'])); // who is the last child now? $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d\n\t\t\t\t\t\t\t\tORDER BY `created` DESC LIMIT 1", dbesc($item['parent-uri']), intval($importer['importer_uid'])); if (count($r)) { q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", intval($r[0]['id'])); } } } } } } } foreach ($feed->get_items() as $item) { $is_reply = false; $item_id = $item->get_id(); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_uri = $rawthread[0]['attribs']['']['ref']; } if ($is_reply) { $community = false; if ($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { $sql_extra = ''; $community = true; logger('local_delivery: possible community reply'); } else { $sql_extra = " and contact.self = 1 and item.wall = 1 "; } // was the top-level post for this reply written by somebody on this site? // Specifically, the recipient? $is_a_remote_comment = false; $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, \n\t\t\t\t`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` \n\t\t\t\tLEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` \n\t\t\t\tWHERE `item`.`uri` = '%s' AND `item`.`parent-uri` = '%s'\n\t\t\t\tAND `item`.`uid` = %d \n\t\t\t\t{$sql_extra}\n\t\t\t\tLIMIT 1", dbesc($parent_uri), dbesc($parent_uri), intval($importer['importer_uid'])); if ($r && count($r)) { $is_a_remote_comment = true; } // Does this have the characteristics of a community or private group comment? // If it's a reply to a wall post on a community/prvgroup page it's a // valid community comment. Also forum_mode makes it valid for sure. // If neither, it's not. if ($is_a_remote_comment && $community) { if (!$r[0]['forum_mode'] && !$r[0]['wall']) { $is_a_remote_comment = false; logger('local_delivery: not a community reply'); } } if ($is_a_remote_comment) { logger('local_delivery: received remote comment'); $is_like = false; // remote reply to our post. Import and then notify everybody else. $datarray = get_atom_elements($feed, $item); $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { $iid = $r[0]['id']; if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { logger('received updated comment', LOGGER_DEBUG); $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc($item_id), intval($importer['importer_uid'])); proc_run('php', "include/notifier.php", "comment-import", $iid); } continue; } // TODO: make this next part work against both delivery threads of a community post // if((! link_compare($datarray['author-link'],$importer['url'])) && (! $community)) { // logger('local_delivery: received relay claiming to be from ' . $importer['url'] . ' however comment author url is ' . $datarray['author-link'] ); // they won't know what to do so don't report an error. Just quietly die. // return 0; // } // our user with $importer['importer_uid'] is the owner $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", intval($importer['importer_uid'])); $datarray['type'] = 'remote-comment'; $datarray['wall'] = 1; $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['owner-name'] = $own[0]['name']; $datarray['owner-link'] = $own[0]['url']; $datarray['owner-avatar'] = $own[0]['thumb']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] === ACTIVITY_LIKE || $datarray['verb'] === ACTIVITY_DISLIKE) { $is_like = true; $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; $datarray['last-child'] = 0; // only one like or dislike per person $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb'])); if ($r && count($r)) { continue; } } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE && $xt->id) { // fetch the parent item $tagp = q("select * from item where uri = '%s' and uid = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (!count($tagp)) { continue; } // extract tag, if not duplicate, and this user allows tags, add to parent item if ($xo->id && $xo->content) { $newtag = '#[url=' . $xo->id . ']' . $xo->content . '[/url]'; if (!stristr($tagp[0]['tag'], $newtag)) { $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", intval($importer['importer_uid'])); if (count($i) && !intval($i[0]['blocktags'])) { q("UPDATE item SET tag = '%s', `edited` = '%s' WHERE id = %d LIMIT 1", dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag), intval($tagp[0]['id']), dbesc(datetime_convert())); } } } } } // if($community) { // $newtag = '@[url=' . $a->get_baseurl() . '/profile/' . $importer['nickname'] . ']' . $importer['username'] . '[/url]'; // if(! stristr($datarray['tag'],$newtag)) { // if(strlen($datarray['tag'])) // $datarray['tag'] .= ','; // $datarray['tag'] .= $newtag; // } // } $posted_id = item_store($datarray); $parent = 0; if ($posted_id) { $r = q("SELECT `parent` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($posted_id), intval($importer['importer_uid'])); if (count($r)) { $parent = $r[0]['parent']; } if (!$is_like) { $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($r[0]['parent'])); $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($posted_id)); } if ($posted_id && $parent) { proc_run('php', "include/notifier.php", "comment-import", "{$posted_id}"); if (!$is_like && !$importer['self']) { require_once 'include/enotify.php'; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, 'source_name' => stripslashes($datarray['author-name']), 'source_link' => $datarray['author-link'], 'source_photo' => link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent)); } } return 0; // NOTREACHED } } else { // regular comment that is part of this total conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc($item_id), intval($importer['importer_uid'])); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", dbesc(datetime_convert()), dbesc($parent_uri), intval($importer['importer_uid'])); $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] == ACTIVITY_LIKE || $datarray['verb'] == ACTIVITY_DISLIKE) { $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; // only one like or dislike per person $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb'])); if ($r && count($r)) { continue; } } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE) { $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (!count($r)) { continue; } // extract tag, if not duplicate, add to parent item if ($xo->content) { if (!stristr($r[0]['tag'], trim($xo->content))) { q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']' . $xo->content . '[/url]'), intval($r[0]['id'])); } } } } $posted_id = item_store($datarray); // find out if our user is involved in this conversation and wants to be notified. if (!x($datarray['type']) || $datarray['type'] != 'activity') { $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", dbesc($parent_uri), intval($importer['importer_uid'])); if (count($myconv)) { $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname']; // first make sure this isn't our own post coming back to us from a wall-to-wall event if (!link_compare($datarray['author-link'], $importer_url)) { foreach ($myconv as $conv) { // now if we find a match, it means we're in this conversation if (!link_compare($conv['author-link'], $importer_url)) { continue; } require_once 'include/enotify.php'; $conv_parent = $conv['parent']; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id, 'source_name' => stripslashes($datarray['author-name']), 'source_link' => $datarray['author-link'], 'source_photo' => link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent)); // only send one notification break; } } } } continue; } } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); if (x($datarray, 'object-type') && $datarray['object-type'] === ACTIVITY_OBJ_EVENT) { $ev = bbtoevent($datarray['body']); if (x($ev, 'desc') && x($ev, 'start')) { $ev['cid'] = $importer['id']; $ev['uid'] = $importer['uid']; $ev['uri'] = $item_id; $ev['edited'] = $datarray['edited']; $ev['private'] = $datarray['private']; $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['uid'])); if (count($r)) { $ev['id'] = $r[0]['id']; } $xyz = event_store($ev); continue; } } $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc($item_id), intval($importer['importer_uid'])); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } // This is my contact on another system, but it's really me. // Turn this into a wall post. if ($importer['remote_self']) { $datarray['wall'] = 1; } $datarray['parent-uri'] = $item_id; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; if (!link_compare($datarray['owner-link'], $contact['url'])) { // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. // the tgroup delivery code called from item_store will correct it if it's a forum, // but we're going to unconditionally correct it here so that the post will always be owned by our contact. logger('local_delivery: Correcting item owner.', LOGGER_DEBUG); $datarray['owner-name'] = $importer['senderName']; $datarray['owner-link'] = $importer['url']; $datarray['owner-avatar'] = $importer['thumb']; } $r = item_store($datarray); continue; } } return 0; // NOTREACHED }
function blockem_init(&$a) { if (!local_user()) { return; } $words = get_pconfig(local_user(), 'blockem', 'words'); if (array_key_exists('block', $_GET) && $_GET['block']) { if (strlen($words)) { $words .= ','; } $words .= trim($_GET['block']); } if (array_key_exists('unblock', $_GET) && $_GET['unblock']) { $arr = explode(',', $words); $newarr = array(); if (count($arr)) { foreach ($arr as $x) { if (!link_compare(trim($x), trim($_GET['unblock']))) { $newarr[] = $x; } } } $words = implode(',', $newarr); } set_pconfig(local_user(), 'blockem', 'words', $words); info(t('blockem settings updated') . EOL); killme(); }
function local_delivery($importer, $data) { $a = get_app(); if ($importer['readonly']) { // We aren't receiving stuff from this person. But we will quietly ignore them // rather than a blatant "go away" message. logger('local_delivery: ignoring'); return 0; //NOTREACHED } // Consume notification feed. This may differ from consuming a public feed in several ways // - might contain email or friend suggestions // - might contain remote followup to our message // - in which case we need to accept it and then notify other conversants // - we may need to send various email notifications $feed = new SimplePie(); $feed->set_raw_data($data); $feed->enable_order_by_date(false); $feed->init(); $reloc = $feed->get_feed_tags(NAMESPACE_DFRN, 'relocate'); if (isset($reloc[0]['child'][NAMESPACE_DFRN])) { $base = $reloc[0]['child'][NAMESPACE_DFRN]; $newloc = array(); $newloc['uid'] = $importer['importer_uid']; $newloc['cid'] = $importer['id']; $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); $newloc['site-pubkey'] = notags(unxmlify($base['site-pubkey'][0]['data'])); $newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data'])); // TODO // merge with current record, current contents have priority // update record, set url-updated // update profile photos // schedule a scan? } // handle friend suggestion notification $sugg = $feed->get_feed_tags(NAMESPACE_DFRN, 'suggest'); if (isset($sugg[0]['child'][NAMESPACE_DFRN])) { $base = $sugg[0]['child'][NAMESPACE_DFRN]; $fsugg = array(); $fsugg['uid'] = $importer['importer_uid']; $fsugg['cid'] = $importer['id']; $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); // Does our member already have a friend matching this description? $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", dbesc($fsugg['name']), dbesc(normalise_link($fsugg['url'])), intval($fsugg['uid'])); if (count($r)) { return 0; } // Do we already have an fcontact record for this person? $fid = 0; $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; } if (!$fid) { $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", dbesc($fsugg['name']), dbesc($fsugg['url']), dbesc($fsugg['photo']), dbesc($fsugg['request'])); } $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; } else { return 0; } $hash = random_string(); $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )\n\t\t\tVALUES( %d, %d, %d, '%s', '%s', '%s', %d )", intval($fsugg['uid']), intval($fid), intval($fsugg['cid']), dbesc($fsugg['body']), dbesc($hash), dbesc(datetime_convert()), intval(0)); // TODO - send email notify (which may require a new notification preference) return 0; } $ismail = false; $rawmail = $feed->get_feed_tags(NAMESPACE_DFRN, 'mail'); if (isset($rawmail[0]['child'][NAMESPACE_DFRN])) { logger('local_delivery: private message received'); $ismail = true; $base = $rawmail[0]['child'][NAMESPACE_DFRN]; $msg = array(); $msg['uid'] = $importer['importer_uid']; $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); $msg['contact-id'] = $importer['id']; $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); $msg['seen'] = 0; $msg['replied'] = 0; $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); $msg['created'] = datetime_convert(notags(unxmlify('UTC', 'UTC', $base['sentdate'][0]['data']))); dbesc_array($msg); $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) . "`) VALUES ('" . implode("', '", array_values($msg)) . "')"); // send email notification if requested. require_once 'bbcode.php'; if ($importer['notify-flags'] & NOTIFY_MAIL) { push_lang($importer['language']); // name of the automated email sender $msg['notificationfromname'] = t('Administrator'); // noreply address to send from $msg['notificationfromemail'] = t('noreply') . '@' . $a->get_hostname(); // text version // process the message body to display properly in text mode // 1) substitute a \n character for the "\" then "n", so it behaves properly (it doesn't come in as a \n character) // 2) remove escape slashes // 3) decode any bbcode from the message editor // 4) decode any encoded html tags // 5) remove html tags $msg['textversion'] = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n"), "\n", $msg['body']))), ENT_QUOTES, 'UTF-8')); // html version // process the message body to display properly in text mode // 1) substitute a <br /> tag for the "\" then "n", so it behaves properly (it doesn't come in as a \n character) // 2) remove escape slashes // 3) decode any bbcode from the message editor // 4) decode any encoded html tags $msg['htmlversion'] = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n\\n", "\\n"), "<br />\n", $msg['body'])))); // load the template for private message notifications $tpl = get_intltext_template('mail_received_html_body_eml.tpl'); $email_html_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$siteName' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $importer['thumb'], '$email' => $importer['email'], '$url' => $importer['url'], '$from' => $msg['from-name'], '$title' => stripslashes($msg['title']), '$htmlversion' => $msg['htmlversion'], '$mimeboundary' => $msg['mimeboundary'], '$hostname' => $a->get_hostname())); // load the template for private message notifications $tpl = get_intltext_template('mail_received_text_body_eml.tpl'); $email_text_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$siteName' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $importer['thumb'], '$email' => $importer['email'], '$url' => $importer['url'], '$from' => $msg['from-name'], '$title' => stripslashes($msg['title']), '$textversion' => $msg['textversion'], '$mimeboundary' => $msg['mimeboundary'], '$hostname' => $a->get_hostname())); // use the EmailNotification library to send the message require_once "include/EmailNotification.php"; EmailNotification::sendTextHtmlEmail($msg['notificationfromname'], $msg['notificationfromemail'], $msg['notificationfromemail'], $importer['email'], t('New mail received at ') . $a->config['sitename'], $email_html_body_tpl, $email_text_body_tpl); pop_lang(); } return 0; // NOTREACHED } logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries)) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $uri = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted) { $r = q("SELECT `item`.*, `contact`.`self` FROM `item` left join contact on `item`.`contact-id` = `contact`.`id`\n\t\t\t\t\tWHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d LIMIT 1", dbesc($uri), intval($importer['importer_uid']), intval($importer['id'])); if (count($r)) { $item = $r[0]; if ($item['deleted']) { continue; } logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); if ($item['verb'] === ACTIVITY_TAG && $item['object-type'] === ACTVITY_OBJ_TAGTERM) { $xo = parse_xml_string($item['object'], false); $xt = parse_xml_string($item['target'], false); if ($xt->type === ACTIVITY_OBJ_NOTE) { $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (count($i)) { // For tags, the owner cannot remove the tag on the author's copy of the post. $owner_remove = $item['contact-id'] == $i[0]['contact-id'] ? true : false; $author_remove = $item['origin'] && $item['self'] ? true : false; $author_copy = $item['origin'] ? true : false; if ($owner_remove && $author_copy) { continue; } if ($author_remove || $owner_remove) { $tags = explode(',', $i[0]['tag']); $newtags = array(); if (count($tags)) { foreach ($tags as $tag) { if (trim($tag) !== trim($xo->body)) { $newtags[] = trim($tag); } } } q("update item set tag = '%s' where id = %d limit 1", dbesc(implode(',', $newtags)), intval($i[0]['id'])); } } } } if ($item['uri'] == $item['parent-uri']) { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s'\n\t\t\t\t\t\t\tWHERE `parent-uri` = '%s' AND `uid` = %d", dbesc($when), dbesc(datetime_convert()), dbesc($item['uri']), intval($importer['importer_uid'])); } else { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s' \n\t\t\t\t\t\t\tWHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($when), dbesc(datetime_convert()), dbesc($uri), intval($importer['importer_uid'])); if ($item['last-child']) { // ensure that last-child is set in case the comment that had it just got wiped. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", dbesc(datetime_convert()), dbesc($item['parent-uri']), intval($item['uid'])); // who is the last child now? $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d\n\t\t\t\t\t\t\t\tORDER BY `created` DESC LIMIT 1", dbesc($item['parent-uri']), intval($importer['importer_uid'])); if (count($r)) { q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d LIMIT 1", intval($r[0]['id'])); } } } } } } } foreach ($feed->get_items() as $item) { $is_reply = false; $item_id = $item->get_id(); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_uri = $rawthread[0]['attribs']['']['ref']; } if ($is_reply) { $community = false; if ($importer['page-flags'] == PAGE_COMMUNITY) { $sql_extra = ''; $community = true; logger('local_delivery: community reply'); } else { $sql_extra = " and contact.self = 1 and item.wall = 1 "; } // was the top-level post for this reply written by somebody on this site? // Specifically, the recipient? $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, \n\t\t\t\t`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` \n\t\t\t\tLEFT JOIN `contact` ON `contact`.`id` = `item`.`contact-id` \n\t\t\t\tWHERE `item`.`uri` = '%s' AND `item`.`parent-uri` = '%s'\n\t\t\t\tAND `item`.`uid` = %d \n\t\t\t\t{$sql_extra}\n\t\t\t\tLIMIT 1", dbesc($parent_uri), dbesc($parent_uri), intval($importer['importer_uid'])); if ($r && count($r)) { logger('local_delivery: received remote comment'); $is_like = false; // remote reply to our post. Import and then notify everybody else. $datarray = get_atom_elements($feed, $item); // TODO: make this next part work against both delivery threads of a community post // if((! link_compare($datarray['author-link'],$importer['url'])) && (! $community)) { // logger('local_delivery: received relay claiming to be from ' . $importer['url'] . ' however comment author url is ' . $datarray['author-link'] ); // they won't know what to do so don't report an error. Just quietly die. // return 0; // } $datarray['type'] = 'remote-comment'; $datarray['wall'] = 1; $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['owner-name'] = $r[0]['name']; $datarray['owner-link'] = $r[0]['url']; $datarray['owner-avatar'] = $r[0]['thumb']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] === ACTIVITY_LIKE || $datarray['verb'] === ACTIVITY_DISLIKE) { $is_like = true; $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; $datarray['last-child'] = 0; } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE && $xt->id == $r[0]['uri']) { // extract tag, if not duplicate, and this user allows tags, add to parent item if ($xo->id && $xo->content) { $newtag = '#[url=' . $xo->id . ']' . $xo->content . '[/url]'; if (!stristr($r[0]['tag'], $newtag)) { $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", intval($importer['importer_uid'])); if (count($i) && !$i[0]['blocktags']) { q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), intval($r[0]['id'])); } } } } } // if($community) { // $newtag = '@[url=' . $a->get_baseurl() . '/profile/' . $importer['nickname'] . ']' . $importer['username'] . '[/url]'; // if(! stristr($datarray['tag'],$newtag)) { // if(strlen($datarray['tag'])) // $datarray['tag'] .= ','; // $datarray['tag'] .= $newtag; // } // } $posted_id = item_store($datarray); $parent = 0; if ($posted_id) { $r = q("SELECT `parent` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($posted_id), intval($importer['importer_uid'])); if (count($r)) { $parent = $r[0]['parent']; } if (!$is_like) { $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($r[0]['parent'])); $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d LIMIT 1", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($posted_id)); } if ($posted_id && $parent) { proc_run('php', "include/notifier.php", "comment-import", "{$posted_id}"); if (!$is_like && $importer['notify-flags'] & NOTIFY_COMMENT && !$importer['self']) { push_lang($importer['language']); require_once 'bbcode.php'; $from = stripslashes($datarray['author-name']); // name of the automated email sender $msg['notificationfromname'] = stripslashes($datarray['author-name']); // noreply address to send from $msg['notificationfromemail'] = t('noreply') . '@' . $a->get_hostname(); // text version // process the message body to display properly in text mode $msg['textversion'] = html_entity_decode(strip_tags(bbcode(stripslashes($datarray['body']))), ENT_QUOTES, 'UTF-8'); // html version // process the message body to display properly in text mode $msg['htmlversion'] = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n\\n", "\\n"), "<br />\n", $datarray['body'])))); $imgtouse = link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar']; // load the template for private message notifications $tpl = get_intltext_template('cmnt_received_html_body_eml.tpl'); $email_html_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$sitename' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $imgtouse, '$email' => $importer['email'], '$url' => $datarray['author-link'], '$from' => $from, '$body' => $msg['htmlversion'], '$display' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id)); // load the template for private message notifications $tpl = get_intltext_template('cmnt_received_text_body_eml.tpl'); $email_text_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$sitename' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $imgtouse, '$email' => $importer['email'], '$url' => $datarray['author-link'], '$from' => $from, '$body' => $msg['textversion'], '$display' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id)); // use the EmailNotification library to send the message require_once "include/EmailNotification.php"; EmailNotification::sendTextHtmlEmail($msg['notificationfromname'], t("Administrator") . '@' . $a->get_hostname(), t("noreply") . '@' . $a->get_hostname(), $importer['email'], sprintf(t('%s commented on an item at %s'), $from, $a->config['sitename']), $email_html_body_tpl, $email_text_body_tpl); pop_lang(); } } return 0; // NOTREACHED } } else { // regular comment that is part of this total conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { $r = q("UPDATE `item` SET `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['body']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc($item_id), intval($importer['importer_uid'])); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", dbesc(datetime_convert()), dbesc($parent_uri), intval($importer['importer_uid'])); $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] == ACTIVITY_LIKE || $datarray['verb'] == ACTIVITY_DISLIKE) { $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE) { $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (!count($r)) { continue; } // extract tag, if not duplicate, add to parent item if ($xo->content) { if (!stristr($r[0]['tag'], trim($xo->content))) { q("UPDATE item SET tag = '%s' WHERE id = %d LIMIT 1", dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']' . $xo->content . '[/url]'), intval($r[0]['id'])); } } } } $posted_id = item_store($datarray); // find out if our user is involved in this conversation and wants to be notified. if ($datarray['type'] != 'activity' && $importer['notify-flags'] & NOTIFY_COMMENT) { $myconv = q("SELECT `author-link`, `author-avatar` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 ", dbesc($parent_uri), intval($importer['importer_uid'])); if (count($myconv)) { $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname']; foreach ($myconv as $conv) { if (!link_compare($conv['author-link'], $importer_url)) { continue; } push_lang($importer['language']); require_once 'bbcode.php'; $from = stripslashes($datarray['author-name']); // name of the automated email sender $msg['notificationfromname'] = stripslashes($datarray['author-name']); // noreply address to send from $msg['notificationfromemail'] = t('noreply') . '@' . $a->get_hostname(); // text version // process the message body to display properly in text mode $msg['textversion'] = html_entity_decode(strip_tags(bbcode(stripslashes($datarray['body']))), ENT_QUOTES, 'UTF-8'); // html version // process the message body to display properly in text mode $msg['htmlversion'] = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r\\n", "\\r", "\\n\\n", "\\n"), "<br />\n", $datarray['body'])))); $imgtouse = link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar']; // load the template for private message notifications $tpl = get_intltext_template('cmnt_received_html_body_eml.tpl'); $email_html_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$sitename' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $imgtouse, '$url' => $datarray['author-link'], '$from' => $from, '$body' => $msg['htmlversion'], '$display' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id)); // load the template for private message notifications $tpl = get_intltext_template('cmnt_received_text_body_eml.tpl'); $email_text_body_tpl = replace_macros($tpl, array('$username' => $importer['username'], '$sitename' => $a->config['sitename'], '$siteurl' => $a->get_baseurl(), '$thumb' => $imgtouse, '$url' => $datarray['author-link'], '$from' => $from, '$body' => $msg['textversion'], '$display' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $posted_id)); // use the EmailNotification library to send the message require_once "include/EmailNotification.php"; EmailNotification::sendTextHtmlEmail($msg['notificationfromname'], t("Administrator@") . $a->get_hostname(), t("noreply") . '@' . $a->get_hostname(), $importer['email'], sprintf(t('%s commented on an item at %s'), $from, $a->config['sitename']), $email_html_body_tpl, $email_text_body_tpl); pop_lang(); break; } } } continue; } } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); if (x($datarray, 'object-type') && $datarray['object-type'] === ACTIVITY_OBJ_EVENT) { $ev = bbtoevent($datarray['body']); if (x($ev, 'desc') && x($ev, 'start')) { $ev['cid'] = $importer['id']; $ev['uid'] = $importer['uid']; $ev['uri'] = $item_id; $ev['edited'] = $datarray['edited']; $ev['private'] = $datarray['private']; $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['uid'])); if (count($r)) { $ev['id'] = $r[0]['id']; } $xyz = event_store($ev); continue; } } $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { $r = q("UPDATE `item` SET `body` = '%s', `edited` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($datarray['body']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc($item_id), intval($importer['importer_uid'])); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } // This is my contact on another system, but it's really me. // Turn this into a wall post. if ($contact['remote_self']) { $datarray['wall'] = 1; } $datarray['parent-uri'] = $item_id; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; $r = item_store($datarray); continue; } } return 0; // NOTREACHED }
/** * @brief This function is called pre-deliver to see if a post matches the criteria to be tag delivered. * * We don't actually do anything except check that it matches the criteria. * This is so that the channel with tag_delivery enabled can receive the post even if they turn off * permissions for the sender to send their stream. tag_deliver() can't be called until the post is actually stored. * By then it would be too late to reject it. */ function tgroup_check($uid, $item) { $mention = false; // check that the message originated elsewhere and is a top-level post // or is a followup and we have already accepted the top level post as an uplink if ($item['mid'] != $item['parent_mid']) { $r = q("select id from item where mid = '%s' and uid = %d and item_uplink = 1 limit 1", dbesc($item['parent_mid']), intval($uid)); if ($r) { return true; } return false; } if (!perm_is_allowed($uid, $item['author_xchan'], 'tag_deliver')) { return false; } $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", intval($uid)); if (!$u) { return false; } $terms = get_terms_oftype($item['term'], TERM_MENTION); if ($terms) { logger('tgroup_check: post mentions: ' . print_r($terms, true), LOGGER_DATA); } $link = normalise_link($u[0]['xchan_url']); if ($terms) { foreach ($terms as $term) { if (link_compare($term['url'], $link)) { $mention = true; break; } } } if ($mention) { logger('tgroup_check: mention found for ' . $u[0]['channel_name']); } else { return false; } // At this point we've determined that the person receiving this post was mentioned in it. // Now let's check if this mention was inside a reshare so we don't spam a forum // note: $term has been set to the matching term $body = $item['body']; if (array_key_exists('item_obscured', $item) && intval($item['item_obscured']) && $body) { $key = get_config('system', 'prvkey'); $body = crypto_unencapsulate(json_decode($body, true), $key); } $body = preg_replace('/\\[share(.*?)\\[\\/share\\]/', '', $body); // $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/'; $pattern = '/@\\!?\\[zrl\\=([^\\]]*?)\\]((?:.(?!\\[zrl\\=))*?)\\+\\[\\/zrl\\]/'; $found = false; $matches = array(); if (preg_match_all($pattern, $body, $matches, PREG_SET_ORDER)) { $max_forums = get_config('system', 'max_tagged_forums'); if (!$max_forums) { $max_forums = 2; } $matched_forums = 0; foreach ($matches as $match) { $matched_forums++; if ($term['url'] === $match[1] && $term['term'] === $match[2]) { if ($matched_forums <= $max_forums) { $found = true; break; } logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } } } if (!$found) { logger('tgroup_check: mention was in a reshare or exceeded max_tagged_forums - ignoring'); return false; } return true; }
function twitter_checknotification($a, $uid, $own_id, $top_item, $postarray) { // this whole function doesn't seem to work. Needs complete check $user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($uid)); if (!count($user)) { return; } // Is it me? if (link_compare($user[0]["url"], $postarray['author-link'])) { return; } $own_user = q("SELECT * FROM `contact` WHERE `uid` = %d AND `alias` = '%s' LIMIT 1", intval($uid), dbesc("twitter::" . $own_id)); if (!count($own_user)) { return; } // Is it me from twitter? if (link_compare($own_user[0]["url"], $postarray['author-link'])) { return; } $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", dbesc($postarray['parent-uri']), intval($uid)); if (count($myconv)) { foreach ($myconv as $conv) { // now if we find a match, it means we're in this conversation if (!link_compare($conv['author-link'], $user[0]["url"]) and !link_compare($conv['author-link'], $own_user[0]["url"])) { continue; } require_once 'include/enotify.php'; $conv_parent = $conv['parent']; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $user[0]['notify-flags'], 'language' => $user[0]['language'], 'to_name' => $user[0]['username'], 'to_email' => $user[0]['email'], 'uid' => $user[0]['uid'], 'item' => $postarray, 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($top_item)), 'source_name' => $postarray['author-name'], 'source_link' => $postarray['author-link'], 'source_photo' => $postarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent)); // only send one notification break; } } }
/** * @brief Process atom feed and update anything/everything we might need to update. * * @param array $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param $contact * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. * @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been * imported prior to its children being seen in the stream unless we are certain * of how the feed is arranged/ordered. * * With $pass = 1, we only pull parent items out of the stream. * * With $pass = 2, we only pull children (comments/likes). * * So running this twice, first with pass 1 and then with pass 2 will do the right * thing regardless of feed ordering. This won't be adequate in a fully-threaded * model where comments can have sub-threads. That would require some massive sorting * to get all the feed items into a mostly linear ordering, and might still require * recursion. */ function consume_feed($xml, $importer, &$contact, $pass = 0) { require_once 'library/simplepie/simplepie.inc'; if (!strlen($xml)) { logger('consume_feed: empty input'); return; } $sys_expire = intval(get_config('system', 'default_expire_days')); $chn_expire = intval($importer['channel_expire_days']); $expire_days = $sys_expire; if ($chn_expire != 0 && $chn_expire < $sys_expire) { $expire_days = $chn_expire; } // logger('expire_days: ' . $expire_days); $feed = new SimplePie(); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('consume_feed: Error parsing XML: ' . $feed->error()); } $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries) && $pass != 2) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $mid = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted && is_array($contact)) { $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1", dbesc(base64url_encode($mid)), dbesc($contact['xchan_hash']), intval($importer['channel_id'])); if ($r) { $item = $r[0]; if (!intval($item['item_deleted'])) { logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . base64url_decode($item['mid']), LOGGER_DEBUG); drop_item($item['id'], false); } } } } } // Now process the feed if ($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG); $items = $feed->get_items(); foreach ($items as $item) { $is_reply = false; $item_id = base64url_encode($item->get_id()); logger('consume_feed: processing ' . $item_id, LOGGER_DEBUG); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_mid = base64url_encode($rawthread[0]['attribs']['']['ref']); } if ($is_reply) { if ($pass == 1) { continue; } // Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $parent_mid; $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if ($contact['xchan_network'] === 'rss') { $datarray['public_policy'] = 'specific'; $datarray['comment_policy'] = 'none'; } if (is_array($contact)) { if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } } if (!x($author, 'author_name') || !x($author, 'author_link')) { logger('consume_feed: no author information! ' . print_r($author, true)); continue; } $datarray['author_xchan'] = ''; if (activity_match($datarray['verb'], ACTIVITY_FOLLOW) && $datarray['obj_type'] === ACTIVITY_OBJ_PERSON) { $cb = array('item' => $datarray, 'channel' => $importer, 'xchan' => null, 'author' => $author, 'caught' => false); call_hooks('follow_from_feed', $cb); if ($cb['caught']) { if ($cb['return_code']) { http_status_exit($cb['return_code']); } continue; } } if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; if (array_key_exists('created', $datarray) && $datarray['created'] != NULL_DATE && $expire_days) { $t1 = $datarray['created']; $t2 = datetime_convert('UTC', 'UTC', 'now - ' . $expire_days . 'days'); if ($t1 < $t2) { logger('feed content older than expiration. Ignoring.', LOGGER_DEBUG, LOG_INFO); continue; } } $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $item_id; $datarray['uid'] = $importer['channel_id']; $datarray['aid'] = $importer['channel_account_id']; if (!link_compare($author['owner_link'], $contact['xchan_url'])) { logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); $author['owner_name'] = $contact['name']; $author['owner_link'] = $contact['url']; $author['owner_avatar'] = $contact['thumb']; } if (!post_is_importable($datarray, $contact)) { continue; } logger('consume_feed: author ' . print_r($author, true), LOGGER_DEBUG); logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } } } }
function diaspora_retraction($importer, $xml) { $guid = notags(unxmlify($xml->guid)); $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); $type = notags(unxmlify($xml->type)); $contact = diaspora_get_contact_by_handle($importer['channel_id'], $diaspora_handle); if (!$contact) { return; } if ($type === 'Person') { require_once 'include/Contact.php'; contact_remove($importer['channel_id'], $contact['abook_id']); } elseif ($type === 'Post') { $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc('guid'), intval($importer['channel_id'])); if (count($r)) { if (link_compare($r[0]['author_xchan'], $contact['xchan_hash'])) { drop_item($r[0]['id'], false); } } } return 202; // NOTREACHED }
function local_delivery($importer, $data) { $a = get_app(); logger(__FUNCTION__, LOGGER_TRACE); if ($importer['readonly']) { // We aren't receiving stuff from this person. But we will quietly ignore them // rather than a blatant "go away" message. logger('local_delivery: ignoring'); return 0; //NOTREACHED } // Consume notification feed. This may differ from consuming a public feed in several ways // - might contain email or friend suggestions // - might contain remote followup to our message // - in which case we need to accept it and then notify other conversants // - we may need to send various email notifications $feed = new SimplePie(); $feed->set_raw_data($data); $feed->enable_order_by_date(false); $feed->init(); if ($feed->error()) { logger('local_delivery: Error parsing XML: ' . $feed->error()); } // Check at the feed level for updated contact name and/or photo $name_updated = ''; $new_name = ''; $photo_timestamp = ''; $photo_url = ''; $contact_updated = ''; $rawtags = $feed->get_feed_tags(NAMESPACE_DFRN, 'owner'); // Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags // if(! $rawtags) // $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); if ($rawtags) { $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; if ($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; $new_name = $elems['name'][0]['data']; // Manually checking for changed contact names if ($new_name != $importer['name'] and $new_name != "" and $name_updated <= $importer['name-date']) { $name_updated = date("c"); $photo_timestamp = date("c"); } } if (x($elems, 'link') && $elems['link'][0]['attribs']['']['rel'] === 'photo' && $elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']) { if ($photo_timestamp == "") { $photo_timestamp = datetime_convert('UTC', 'UTC', $elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); } $photo_url = $elems['link'][0]['attribs']['']['href']; } } if ($photo_timestamp && strlen($photo_url) && $photo_timestamp > $importer['avatar-date']) { $contact_updated = $photo_timestamp; logger('local_delivery: Updating photo for ' . $importer['name']); require_once "include/Photo.php"; $photos = import_profile_photo($photo_url, $importer['importer_uid'], $importer['id']); q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s'\n\t\t\tWHERE `uid` = %d AND `id` = %d AND NOT `self`", dbesc(datetime_convert()), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), intval($importer['importer_uid']), intval($importer['id'])); } if ($name_updated && strlen($new_name) && $name_updated > $importer['name-date']) { if ($name_updated > $contact_updated) { $contact_updated = $name_updated; } $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1", intval($importer['importer_uid']), intval($importer['id'])); $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`", dbesc(notags(trim($new_name))), dbesc(datetime_convert()), intval($importer['importer_uid']), intval($importer['id']), dbesc(notags(trim($new_name)))); // do our best to update the name on content items if (count($r) and notags(trim($new_name)) != $r[0]['name']) { q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'", dbesc(notags(trim($new_name))), dbesc($r[0]['name']), dbesc($r[0]['url']), intval($importer['importer_uid']), dbesc(notags(trim($new_name)))); } } if ($contact_updated and $new_name and $photo_url) { poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']); } // Currently unsupported - needs a lot of work $reloc = $feed->get_feed_tags(NAMESPACE_DFRN, 'relocate'); if (isset($reloc[0]['child'][NAMESPACE_DFRN])) { $base = $reloc[0]['child'][NAMESPACE_DFRN]; $newloc = array(); $newloc['uid'] = $importer['importer_uid']; $newloc['cid'] = $importer['id']; $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data'])); $newloc['micro'] = notags(unxmlify($base['micro'][0]['data'])); $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data'])); /** relocated user must have original key pair */ /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/ logger("items:relocate contact " . print_r($newloc, true) . print_r($importer, true), LOGGER_DEBUG); // update contact $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;", intval($importer['id']), intval($importer['importer_uid'])); if ($r === false) { return 1; } $old = $r[0]; $x = q("UPDATE contact SET\n\t\t\t\t\tname = '%s',\n\t\t\t\t\tphoto = '%s',\n\t\t\t\t\tthumb = '%s',\n\t\t\t\t\tmicro = '%s',\n\t\t\t\t\turl = '%s',\n\t\t\t\t\tnurl = '%s',\n\t\t\t\t\trequest = '%s',\n\t\t\t\t\tconfirm = '%s',\n\t\t\t\t\tnotify = '%s',\n\t\t\t\t\tpoll = '%s',\n\t\t\t\t\t`site-pubkey` = '%s'\n\t\t\tWHERE id=%d AND uid=%d;", dbesc($newloc['name']), dbesc($newloc['photo']), dbesc($newloc['thumb']), dbesc($newloc['micro']), dbesc($newloc['url']), dbesc(normalise_link($newloc['url'])), dbesc($newloc['request']), dbesc($newloc['confirm']), dbesc($newloc['notify']), dbesc($newloc['poll']), dbesc($newloc['sitepubkey']), intval($importer['id']), intval($importer['importer_uid'])); if ($x === false) { return 1; } // update items $fields = array('owner-link' => array($old['url'], $newloc['url']), 'author-link' => array($old['url'], $newloc['url']), 'owner-avatar' => array($old['photo'], $newloc['photo']), 'author-avatar' => array($old['photo'], $newloc['photo'])); foreach ($fields as $n => $f) { $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d", $n, dbesc($f[1]), $n, dbesc($f[0]), intval($importer['importer_uid'])); if ($x === false) { return 1; } } // TODO // merge with current record, current contents have priority // update record, set url-updated // update profile photos // schedule a scan? return 0; } // handle friend suggestion notification $sugg = $feed->get_feed_tags(NAMESPACE_DFRN, 'suggest'); if (isset($sugg[0]['child'][NAMESPACE_DFRN])) { $base = $sugg[0]['child'][NAMESPACE_DFRN]; $fsugg = array(); $fsugg['uid'] = $importer['importer_uid']; $fsugg['cid'] = $importer['id']; $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); // Does our member already have a friend matching this description? $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", dbesc($fsugg['name']), dbesc(normalise_link($fsugg['url'])), intval($fsugg['uid'])); if (count($r)) { return 0; } // Do we already have an fcontact record for this person? $fid = 0; $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; // OK, we do. Do we already have an introduction for this person ? $r = q("select id from intro where uid = %d and fid = %d limit 1", intval($fsugg['uid']), intval($fid)); if (count($r)) { return 0; } } if (!$fid) { $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", dbesc($fsugg['name']), dbesc($fsugg['url']), dbesc($fsugg['photo']), dbesc($fsugg['request'])); } $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", dbesc($fsugg['url']), dbesc($fsugg['name']), dbesc($fsugg['request'])); if (count($r)) { $fid = $r[0]['id']; } else { return 0; } $hash = random_string(); $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` )\n\t\t\tVALUES( %d, %d, %d, '%s', '%s', '%s', %d )", intval($fsugg['uid']), intval($fid), intval($fsugg['cid']), dbesc($fsugg['body']), dbesc($hash), dbesc(datetime_convert()), intval(0)); notification(array('type' => NOTIFY_SUGGEST, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $fsugg, 'link' => $a->get_baseurl() . '/notifications/intros', 'source_name' => $importer['name'], 'source_link' => $importer['url'], 'source_photo' => $importer['photo'], 'verb' => ACTIVITY_REQ_FRIEND, 'otype' => 'intro')); return 0; } $ismail = false; $rawmail = $feed->get_feed_tags(NAMESPACE_DFRN, 'mail'); if (isset($rawmail[0]['child'][NAMESPACE_DFRN])) { logger('local_delivery: private message received'); $ismail = true; $base = $rawmail[0]['child'][NAMESPACE_DFRN]; $msg = array(); $msg['uid'] = $importer['importer_uid']; $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); $msg['contact-id'] = $importer['id']; $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); $msg['seen'] = 0; $msg['replied'] = 0; $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); $msg['created'] = datetime_convert(notags(unxmlify('UTC', 'UTC', $base['sentdate'][0]['data']))); dbesc_array($msg); $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) . "`) VALUES ('" . implode("', '", array_values($msg)) . "')"); // send notifications. require_once 'include/enotify.php'; $notif_params = array('type' => NOTIFY_MAIL, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $msg, 'source_name' => $msg['from-name'], 'source_link' => $importer['url'], 'source_photo' => $importer['thumb'], 'verb' => ACTIVITY_POST, 'otype' => 'mail'); notification($notif_params); return 0; // NOTREACHED } $community_page = 0; $rawtags = $feed->get_feed_tags(NAMESPACE_DFRN, 'community'); if ($rawtags) { $community_page = intval($rawtags[0]['data']); } if (intval($importer['forum']) != $community_page) { q("update contact set forum = %d where id = %d", intval($community_page), intval($importer['id'])); $importer['forum'] = (string) $community_page; } logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries)) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $uri = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted) { // check for relayed deletes to our conversation $is_reply = false; $r = q("select * from item where uri = '%s' and uid = %d limit 1", dbesc($uri), intval($importer['importer_uid'])); if (count($r)) { $parent_uri = $r[0]['parent-uri']; if ($r[0]['id'] != $r[0]['parent']) { $is_reply = true; } } if ($is_reply) { $community = false; if ($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { $sql_extra = ''; $community = true; logger('local_delivery: possible community delete'); } else { $sql_extra = " and contact.self = 1 and item.wall = 1 "; } // was the top-level post for this reply written by somebody on this site? // Specifically, the recipient? $is_a_remote_delete = false; // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,\n\t\t\t\t\t\t`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`\n\t\t\t\t\t\tINNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`\n\t\t\t\t\t\tWHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')\n\t\t\t\t\t\tAND `item`.`uid` = %d\n\t\t\t\t\t\t{$sql_extra}\n\t\t\t\t\t\tLIMIT 1", dbesc($parent_uri), dbesc($parent_uri), dbesc($parent_uri), intval($importer['importer_uid'])); if ($r && count($r)) { $is_a_remote_delete = true; } // Does this have the characteristics of a community or private group comment? // If it's a reply to a wall post on a community/prvgroup page it's a // valid community comment. Also forum_mode makes it valid for sure. // If neither, it's not. if ($is_a_remote_delete && $community) { if (!$r[0]['forum_mode'] && !$r[0]['wall']) { $is_a_remote_delete = false; logger('local_delivery: not a community delete'); } } if ($is_a_remote_delete) { logger('local_delivery: received remote delete'); } } $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id`\n\t\t\t\t\tWHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", dbesc($uri), intval($importer['importer_uid']), intval($importer['id'])); if (count($r)) { $item = $r[0]; if ($item['deleted']) { continue; } logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); if ($item['object-type'] === ACTIVITY_OBJ_EVENT) { logger("Deleting event " . $item['event-id'], LOGGER_DEBUG); event_delete($item['event-id']); } if ($item['verb'] === ACTIVITY_TAG && $item['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($item['object'], false); $xt = parse_xml_string($item['target'], false); if ($xt->type === ACTIVITY_OBJ_NOTE) { $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (count($i)) { // For tags, the owner cannot remove the tag on the author's copy of the post. $owner_remove = $item['contact-id'] == $i[0]['contact-id'] ? true : false; $author_remove = $item['origin'] && $item['self'] ? true : false; $author_copy = $item['origin'] ? true : false; if ($owner_remove && $author_copy) { continue; } if ($author_remove || $owner_remove) { $tags = explode(',', $i[0]['tag']); $newtags = array(); if (count($tags)) { foreach ($tags as $tag) { if (trim($tag) !== trim($xo->body)) { $newtags[] = trim($tag); } } } q("update item set tag = '%s' where id = %d", dbesc(implode(',', $newtags)), intval($i[0]['id'])); create_tags_from_item($i[0]['id']); } } } } if ($item['uri'] == $item['parent-uri']) { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',\n\t\t\t\t\t\t\t`body` = '', `title` = ''\n\t\t\t\t\t\t\tWHERE `parent-uri` = '%s' AND `uid` = %d", dbesc($when), dbesc(datetime_convert()), dbesc($item['uri']), intval($importer['importer_uid'])); create_tags_from_itemuri($item['uri'], $importer['importer_uid']); create_files_from_itemuri($item['uri'], $importer['importer_uid']); update_thread_uri($item['uri'], $importer['importer_uid']); } else { $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s',\n\t\t\t\t\t\t\t`body` = '', `title` = ''\n\t\t\t\t\t\t\tWHERE `uri` = '%s' AND `uid` = %d", dbesc($when), dbesc(datetime_convert()), dbesc($uri), intval($importer['importer_uid'])); create_tags_from_itemuri($uri, $importer['importer_uid']); create_files_from_itemuri($uri, $importer['importer_uid']); update_thread_uri($uri, $importer['importer_uid']); if ($item['last-child']) { // ensure that last-child is set in case the comment that had it just got wiped. q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", dbesc(datetime_convert()), dbesc($item['parent-uri']), intval($item['uid'])); // who is the last child now? $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d\n\t\t\t\t\t\t\t\tORDER BY `created` DESC LIMIT 1", dbesc($item['parent-uri']), intval($importer['importer_uid'])); if (count($r)) { q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", intval($r[0]['id'])); } } // if this is a relayed delete, propagate it to other recipients if ($is_a_remote_delete) { proc_run('php', "include/notifier.php", "drop", $item['id']); } } } } } } foreach ($feed->get_items() as $item) { $is_reply = false; $item_id = $item->get_id(); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_uri = $rawthread[0]['attribs']['']['ref']; } if ($is_reply) { $community = false; if ($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP) { $sql_extra = ''; $community = true; logger('local_delivery: possible community reply'); } else { $sql_extra = " and contact.self = 1 and item.wall = 1 "; } // was the top-level post for this reply written by somebody on this site? // Specifically, the recipient? $is_a_remote_comment = false; $top_uri = $parent_uri; $r = q("select `item`.`parent-uri` from `item`\n\t\t\t\tWHERE `item`.`uri` = '%s'\n\t\t\t\tLIMIT 1", dbesc($parent_uri)); if ($r && count($r)) { $top_uri = $r[0]['parent-uri']; // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`,\n\t\t\t\t\t`contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item`\n\t\t\t\t\tINNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`\n\t\t\t\t\tWHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s')\n\t\t\t\t\tAND `item`.`uid` = %d\n\t\t\t\t\t{$sql_extra}\n\t\t\t\t\tLIMIT 1", dbesc($top_uri), dbesc($top_uri), dbesc($top_uri), intval($importer['importer_uid'])); if ($r && count($r)) { $is_a_remote_comment = true; } } // Does this have the characteristics of a community or private group comment? // If it's a reply to a wall post on a community/prvgroup page it's a // valid community comment. Also forum_mode makes it valid for sure. // If neither, it's not. if ($is_a_remote_comment && $community) { if (!$r[0]['forum_mode'] && !$r[0]['wall']) { $is_a_remote_comment = false; logger('local_delivery: not a community reply'); } } if ($is_a_remote_comment) { logger('local_delivery: received remote comment'); $is_like = false; // remote reply to our post. Import and then notify everybody else. $datarray = get_atom_elements($feed, $item); $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { $iid = $r[0]['id']; if (edited_timestamp_is_newer($r[0], $datarray)) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } logger('received updated comment', LOGGER_DEBUG); $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); create_tags_from_itemuri($item_id, $importer['importer_uid']); proc_run('php', "include/notifier.php", "comment-import", $iid); } continue; } $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", intval($importer['importer_uid'])); $datarray['type'] = 'remote-comment'; $datarray['wall'] = 1; $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['owner-name'] = $own[0]['name']; $datarray['owner-link'] = $own[0]['url']; $datarray['owner-avatar'] = $own[0]['thumb']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] === ACTIVITY_LIKE || $datarray['verb'] === ACTIVITY_DISLIKE || $datarray['verb'] === ACTIVITY_ATTEND || $datarray['verb'] === ACTIVITY_ATTENDNO || $datarray['verb'] === ACTIVITY_ATTENDMAYBE) { $is_like = true; $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; $datarray['last-child'] = 0; // only one like or dislike per person // splitted into two queries for performance issues $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`parent-uri` = '%s') and deleted = 0 limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb']), dbesc($datarray['parent-uri'])); if ($r && count($r)) { continue; } $r = q("select id from item where uid = %d and `contact-id` = %d and verb = '%s' and (`thr-parent` = '%s') and deleted = 0 limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb']), dbesc($datarray['parent-uri'])); if ($r && count($r)) { continue; } } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE && $xt->id) { // fetch the parent item $tagp = q("select * from item where uri = '%s' and uid = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (!count($tagp)) { continue; } // extract tag, if not duplicate, and this user allows tags, add to parent item if ($xo->id && $xo->content) { $newtag = '#[url=' . $xo->id . ']' . $xo->content . '[/url]'; if (!stristr($tagp[0]['tag'], $newtag)) { $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", intval($importer['importer_uid'])); if (count($i) && !intval($i[0]['blocktags'])) { q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d", dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag), intval($tagp[0]['id']), dbesc(datetime_convert()), dbesc(datetime_convert())); create_tags_from_item($tagp[0]['id']); } } } } } $posted_id = item_store($datarray); $parent = 0; if ($posted_id) { $datarray["id"] = $posted_id; $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($posted_id), intval($importer['importer_uid'])); if (count($r)) { $parent = $r[0]['parent']; $parent_uri = $r[0]['parent-uri']; } if (!$is_like) { $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($r[0]['parent'])); $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", dbesc(datetime_convert()), intval($importer['importer_uid']), intval($posted_id)); } if ($posted_id && $parent) { proc_run('php', "include/notifier.php", "comment-import", "{$posted_id}"); if (!$is_like && !$importer['self']) { require_once 'include/enotify.php'; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($posted_id)), 'source_name' => stripslashes($datarray['author-name']), 'source_link' => $datarray['author-link'], 'source_photo' => link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, 'parent_uri' => $parent_uri)); } } return 0; // NOTREACHED } } else { // regular comment that is part of this total conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); if ($importer['rel'] == CONTACT_IS_FOLLOWER) { continue; } $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (edited_timestamp_is_newer($r[0], $datarray)) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); create_tags_from_itemuri($item_id, $importer['importer_uid']); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", dbesc(datetime_convert()), dbesc($parent_uri), intval($importer['importer_uid'])); $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } $datarray['parent-uri'] = $parent_uri; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; if ($datarray['verb'] === ACTIVITY_LIKE || $datarray['verb'] === ACTIVITY_DISLIKE || $datarray['verb'] === ACTIVITY_ATTEND || $datarray['verb'] === ACTIVITY_ATTENDNO || $datarray['verb'] === ACTIVITY_ATTENDMAYBE) { $datarray['type'] = 'activity'; $datarray['gravity'] = GRAVITY_LIKE; // only one like or dislike per person // splitted into two queries for performance issues $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`parent-uri` = '%s') limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb']), dbesc($parent_uri)); if ($r && count($r)) { continue; } $r = q("select id from item where uid = %d and `contact-id` = %d and verb ='%s' and deleted = 0 and (`thr-parent` = '%s') limit 1", intval($datarray['uid']), intval($datarray['contact-id']), dbesc($datarray['verb']), dbesc($parent_uri)); if ($r && count($r)) { continue; } } if ($datarray['verb'] === ACTIVITY_TAG && $datarray['object-type'] === ACTIVITY_OBJ_TAGTERM) { $xo = parse_xml_string($datarray['object'], false); $xt = parse_xml_string($datarray['target'], false); if ($xt->type == ACTIVITY_OBJ_NOTE) { $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", dbesc($xt->id), intval($importer['importer_uid'])); if (!count($r)) { continue; } // extract tag, if not duplicate, add to parent item if ($xo->content) { if (!stristr($r[0]['tag'], trim($xo->content))) { q("UPDATE item SET tag = '%s' WHERE id = %d", dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']' . $xo->content . '[/url]'), intval($r[0]['id'])); create_tags_from_item($r[0]['id']); } } } } $posted_id = item_store($datarray); // find out if our user is involved in this conversation and wants to be notified. if (!x($datarray['type']) || $datarray['type'] != 'activity') { $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", dbesc($top_uri), intval($importer['importer_uid'])); if (count($myconv)) { $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname']; // first make sure this isn't our own post coming back to us from a wall-to-wall event if (!link_compare($datarray['author-link'], $importer_url)) { foreach ($myconv as $conv) { // now if we find a match, it means we're in this conversation if (!link_compare($conv['author-link'], $importer_url)) { continue; } require_once 'include/enotify.php'; $conv_parent = $conv['parent']; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($posted_id)), 'source_name' => stripslashes($datarray['author-name']), 'source_link' => $datarray['author-link'], 'source_photo' => link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent, 'parent_uri' => $parent_uri)); // only send one notification break; } } } } continue; } } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = $item->get_id(); $datarray = get_atom_elements($feed, $item); if (x($datarray, 'object-type') && $datarray['object-type'] === ACTIVITY_OBJ_EVENT) { $ev = bbtoevent($datarray['body']); if ((x($ev, 'desc') || x($ev, 'summary')) && x($ev, 'start')) { $ev['cid'] = $importer['id']; $ev['uid'] = $importer['uid']; $ev['uri'] = $item_id; $ev['edited'] = $datarray['edited']; $ev['private'] = $datarray['private']; $ev['guid'] = $datarray['guid']; $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['uid'])); if (count($r)) { $ev['id'] = $r[0]['id']; } $xyz = event_store($ev); continue; } } $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($item_id), intval($importer['importer_uid'])); // Update content if 'updated' changes if (count($r)) { if (edited_timestamp_is_newer($r[0], $datarray)) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", dbesc($datarray['title']), dbesc($datarray['body']), dbesc($datarray['tag']), dbesc(datetime_convert('UTC', 'UTC', $datarray['edited'])), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); create_tags_from_itemuri($item_id, $importer['importer_uid']); update_thread_uri($item_id, $importer['importer_uid']); } // update last-child if it changes $allow = $item->get_item_tags(NAMESPACE_DFRN, 'comment-allow'); if ($allow && $allow[0]['data'] != $r[0]['last-child']) { $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", intval($allow[0]['data']), dbesc(datetime_convert()), dbesc($item_id), intval($importer['importer_uid'])); } continue; } $datarray['parent-uri'] = $item_id; $datarray['uid'] = $importer['importer_uid']; $datarray['contact-id'] = $importer['id']; if (!link_compare($datarray['owner-link'], $importer['url'])) { // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, // but otherwise there's a possible data mixup on the sender's system. // the tgroup delivery code called from item_store will correct it if it's a forum, // but we're going to unconditionally correct it here so that the post will always be owned by our contact. logger('local_delivery: Correcting item owner.', LOGGER_DEBUG); $datarray['owner-name'] = $importer['senderName']; $datarray['owner-link'] = $importer['url']; $datarray['owner-avatar'] = $importer['thumb']; } if ($importer['rel'] == CONTACT_IS_FOLLOWER && !tgroup_check($importer['importer_uid'], $datarray)) { continue; } // This is my contact on another system, but it's really me. // Turn this into a wall post. $notify = item_is_remote_self($importer, $datarray); $posted_id = item_store($datarray, false, $notify); if (stristr($datarray['verb'], ACTIVITY_POKE)) { $verb = urldecode(substr($datarray['verb'], strpos($datarray['verb'], '#') + 1)); if (!$verb) { continue; } $xo = parse_xml_string($datarray['object'], false); if ($xo->type == ACTIVITY_OBJ_PERSON && $xo->id) { // somebody was poked/prodded. Was it me? $links = parse_xml_string("<links>" . unxmlify($xo->link) . "</links>", false); foreach ($links->link as $l) { $atts = $l->attributes(); switch ($atts['rel']) { case "alternate": $Blink = $atts['href']; break; default: break; } } if ($Blink && link_compare($Blink, $a->get_baseurl() . '/profile/' . $importer['nickname'])) { // send a notification require_once 'include/enotify.php'; notification(array('type' => NOTIFY_POKE, 'notify_flags' => $importer['notify-flags'], 'language' => $importer['language'], 'to_name' => $importer['username'], 'to_email' => $importer['email'], 'uid' => $importer['importer_uid'], 'item' => $datarray, 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($posted_id)), 'source_name' => stripslashes($datarray['author-name']), 'source_link' => $datarray['author-link'], 'source_photo' => link_compare($datarray['author-link'], $importer['url']) ? $importer['thumb'] : $datarray['author-avatar'], 'verb' => $datarray['verb'], 'otype' => 'person', 'activity' => $verb, 'parent' => $datarray['parent'])); } } } continue; } } return 0; // NOTREACHED }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $parent_author_signature = $xml->parent_author_signature ? notags(unxmlify($xml->parent_author_signature)) : ''; $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact'); return; } $signed_data = $guid . ';' . $type; $sig_decode = base64_decode($sig); if (strcasecmp($diaspora_handle, $msg['author']) == 0) { $person = $contact; $key = $msg['key']; } else { $person = find_diaspora_person_by_handle($diaspora_handle); if (is_array($person) && x($person, 'pubkey')) { $key = $person['pubkey']; } else { logger('diaspora_signed_retraction: unable to find author details'); return; } } if (!rsa_verify($signed_data, $sig_decode, $key, 'sha256')) { logger('diaspora_signed_retraction: retraction-owner verification failed.' . print_r($msg, true)); return; } if ($parent_author_signature) { $parent_author_signature = base64_decode($parent_author_signature); $key = $msg['key']; if (!rsa_verify($signed_data, $parent_author_signature, $key, 'sha256')) { logger('diaspora_signed_retraction: failed to verify person relaying the retraction (e.g. owner of a post relaying a retracted comment'); return; } } if ($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d limit 1", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['id'])); // Now check if the retraction needs to be relayed by us // // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. // The only item with `parent` and `id` as the parent id is the parent item. $p = q("select origin from item where parent = %d and id = %d limit 1", $r[0]['parent'], $r[0]['parent']); if (count($p)) { if ($p[0]['origin'] && !$parent_author_signature) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), dbesc($sig), dbesc($diaspora_handle)); // the existence of parent_author_signature would have meant the parent_author or owner // is already relaying. logger('diaspora_signed_retraction: relaying relayable_retraction'); proc_run('php', 'include/notifier.php', 'relayable_retraction', $r[0]['id']); } } } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
/** * @brief Adds a zid parameter to a url. * * @param string $s * The url to accept the zid * @param boolean $address * $address to use instead of session environment * @return string * * @hooks 'zid' * string url - url to accept zid * string zid - urlencoded zid * string result - the return string we calculated, change it if you want to return something else */ function zid($s, $address = '') { if (!strlen($s) || strpos($s, 'zid=')) { return $s; } $has_params = strpos($s, '?') ? true : false; $num_slashes = substr_count($s, '/'); if (!$has_params) { $has_params = strpos($s, '&') ? true : false; } $achar = strpos($s, '?') ? '&' : '?'; $mine = get_my_url(); $myaddr = $address ? $address : get_my_address(); /** @FIXME checking against our own channel url is no longer reliable. We may have a lot * of urls attached to out channel. Should probably match against our site, since we * will not need to remote authenticate on our own site anyway. */ if ($mine && $myaddr && !link_compare($mine, $s)) { $zurl = $s . ($num_slashes >= 3 ? '' : '/') . $achar . 'zid=' . urlencode($myaddr); } else { $zurl = $s; } $arr = array('url' => $s, 'zid' => urlencode($myaddr), 'result' => $zurl); call_hooks('zid', $arr); return $arr['result']; }
function zrl($s, $force = false) { if (!strlen($s)) { return $s; } if (!strpos($s, '/profile/') && !$force) { return $s; } if ($force && substr($s, -1, 1) !== '/') { $s = $s . '/'; } $achar = strpos($s, '?') ? '&' : '?'; $mine = get_my_url(); if ($mine and !link_compare($mine, $s)) { return $s . $achar . 'zrl=' . urlencode($mine); } return $s; }
/** * @brief Process atom feed and update anything/everything we might need to update. * * $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or * might not) try and subscribe to it. * $datedir sorts in reverse order * * @param array $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param $contact * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. * @param int $pass by default ($pass = 0) we cannot guarantee that a parent item has been * imported prior to its children being seen in the stream unless we are certain * of how the feed is arranged/ordered. * * With $pass = 1, we only pull parent items out of the stream. * * With $pass = 2, we only pull children (comments/likes). * * So running this twice, first with pass 1 and then with pass 2 will do the right * thing regardless of feed ordering. This won't be adequate in a fully-threaded * model where comments can have sub-threads. That would require some massive sorting * to get all the feed items into a mostly linear ordering, and might still require * recursion. */ function consume_feed($xml, $importer, &$contact, $pass = 0) { require_once 'library/simplepie/simplepie.inc'; if (!strlen($xml)) { logger('consume_feed: empty input'); return; } $feed = new SimplePie(); $feed->set_raw_data($xml); $feed->init(); if ($feed->error()) { logger('consume_feed: Error parsing XML: ' . $feed->error()); } $permalink = $feed->get_permalink(); // Check at the feed level for updated contact name and/or photo // process any deleted entries $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); if (is_array($del_entries) && count($del_entries) && $pass != 2) { foreach ($del_entries as $dentry) { $deleted = false; if (isset($dentry['attribs']['']['ref'])) { $mid = $dentry['attribs']['']['ref']; $deleted = true; if (isset($dentry['attribs']['']['when'])) { $when = $dentry['attribs']['']['when']; $when = datetime_convert('UTC', 'UTC', $when, 'Y-m-d H:i:s'); } else { $when = datetime_convert('UTC', 'UTC', 'now', 'Y-m-d H:i:s'); } } if ($deleted && is_array($contact)) { $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1", dbesc(base64url_encode($mid)), dbesc($contact['xchan_hash']), intval($importer['channel_id'])); if ($r) { $item = $r[0]; if (!($item['item_restrict'] & ITEM_DELETED)) { logger('consume_feed: deleting item ' . $item['id'] . ' mid=' . base64url_decode($item['mid']), LOGGER_DEBUG); drop_item($item['id'], false); } } } } } // Now process the feed if ($feed->get_item_quantity()) { logger('consume_feed: feed item count = ' . $feed->get_item_quantity(), LOGGER_DEBUG); $items = $feed->get_items(); foreach ($items as $item) { $is_reply = false; $item_id = base64url_encode($item->get_id()); logger('consume_feed: processing ' . $item_id, LOGGER_DEBUG); $rawthread = $item->get_item_tags(NAMESPACE_THREAD, 'in-reply-to'); if (isset($rawthread[0]['attribs']['']['ref'])) { $is_reply = true; $parent_mid = base64url_encode($rawthread[0]['attribs']['']['ref']); } if ($is_reply) { if ($pass == 1) { continue; } // Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $parent_mid; $datarray['uid'] = $importer['channel_id']; logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } else { // Head post of a conversation. Have we seen it? If not, import it. $item_id = base64url_encode($item->get_id()); $author = array(); $datarray = get_atom_elements($feed, $item, $author); if (is_array($contact)) { if (!x($author, 'author_name') || $author['author_is_feed']) { $author['author_name'] = $contact['xchan_name']; } if (!x($author, 'author_link') || $author['author_is_feed']) { $author['author_link'] = $contact['xchan_url']; } if (!x($author, 'author_photo') || $author['author_is_feed']) { $author['author_photo'] = $contact['xchan_photo_m']; } } if (!x($author, 'author_name') || !x($author, 'author_link')) { logger('consume_feed: no author information! ' . print_r($author, true)); continue; } $datarray['author_xchan'] = ''; if ($author['author_link'] != $contact['xchan_url']) { $x = import_author_unknown(array('name' => $author['author_name'], 'url' => $author['author_link'], 'photo' => array('src' => $author['author_photo']))); if ($x) { $datarray['author_xchan'] = $x; } } if (!$datarray['author_xchan']) { $datarray['author_xchan'] = $contact['xchan_hash']; } $datarray['owner_xchan'] = $contact['xchan_hash']; $r = q("SELECT edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($item_id), intval($importer['channel_id'])); // Update content if 'updated' changes if ($r) { if (x($datarray, 'edited') !== false && datetime_convert('UTC', 'UTC', $datarray['edited']) !== $r[0]['edited']) { // do not accept (ignore) an earlier edit than one we currently have. if (datetime_convert('UTC', 'UTC', $datarray['edited']) < $r[0]['edited']) { continue; } update_feed_item($importer['channel_id'], $datarray); } continue; } $datarray['parent_mid'] = $item_id; $datarray['uid'] = $importer['channel_id']; if (!link_compare($author['owner_link'], $contact['xchan_url'])) { logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); $author['owner_name'] = $contact['name']; $author['owner_link'] = $contact['url']; $author['owner_avatar'] = $contact['thumb']; } logger('consume_feed: author ' . print_r($author, true), LOGGER_DEBUG); logger('consume_feed: ' . print_r($datarray, true), LOGGER_DATA); $xx = item_store($datarray); $r = $xx['item_id']; continue; } } } }
function diaspora_signed_retraction($importer, $xml, $msg) { $guid = notags(unxmlify($xml->target_guid)); $diaspora_handle = notags(unxmlify($xml->sender_handle)); $type = notags(unxmlify($xml->target_type)); $sig = notags(unxmlify($xml->target_author_signature)); $contact = diaspora_get_contact_by_handle($importer['uid'], $diaspora_handle); if (!$contact) { logger('diaspora_signed_retraction: no contact'); return; } // this may not yet work for comments. Need to see how the relaying works // and figure out who signs it. $signed_data = $guid . ';' . $type; $sig = base64_decode($sig); $key = $msg['key']; if (!rsa_verify($signed_data, $sig, $key, 'sha256')) { logger('diaspora_signed_retraction: owner verification failed.' . print_r($msg, true)); return; } if ($type === 'StatusMessage') { $r = q("select * from item where guid = '%s' and uid = %d limit 1", dbesc($guid), intval($importer['uid'])); if (count($r)) { if (link_compare($r[0]['author-link'], $contact['url'])) { q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d limit 1", dbesc(datetime_convert()), intval($r[0]['id'])); } } } else { logger('diaspora_signed_retraction: unknown type: ' . $type); } return 202; // NOTREACHED }
function randpost_enotify_store(&$a, &$b) { if (!($b['ntype'] == NOTIFY_COMMENT || $b['ntype'] == NOTIFY_TAGSELF)) { return; } if (!get_pconfig($b['uid'], 'randpost', 'enable')) { return; } $fort_server = get_config('fortunate', 'server'); if (!$fort_server) { return; } $c = q("select * from channel where channel_id = %d limit 1", intval($b['uid'])); if (!$c) { return; } $my_conversation = false; $p = q("select id, item_flags, author_xchan from item where parent_mid = mid and parent_mid = '%s' and uid = %d limit 1", dbesc($b['item']['parent_mid']), intval($b['uid'])); if (!$p) { return; } $p = fetch_post_tags($p, true); if (intval($p[0]['item_obscured'])) { return; } if ($b['ntype'] == NOTIFY_TAGSELF) { $my_conversation = true; } elseif ($p[0]['author_xchan'] === $c[0]['channel_hash']) { $my_conversation = true; } elseif ($p[0]['term']) { $v = get_terms_oftype($p[0]['term'], TERM_MENTION); $link = normalise_link(z_root() . '/channel/' . $c[0]['channel_address']); if ($v) { foreach ($v as $vv) { if (link_compare($vv['url'], $link)) { $my_conversation = true; break; } } } } // don't hijack somebody else's conversation, but respond (once) if invited to. if (!$my_conversation) { return; } // This conversation is boring me. $limit = mt_rand(5, 20); $h = q("select id, body from item where author_xchan = '%s' and parent_mid = '%s' and uid = %d", dbesc($c[0]['channel_hash']), dbesc($b['item']['parent_mid']), intval($b['uid'])); if ($h && count($h) > $limit) { return; } // Be gracious and not obnoxious if thanked $replies = array(t('You\'re welcome.'), t('Ah shucks...'), t('Don\'t mention it.'), t('<blush>'), ':like'); // TODO: if you really want to freak somebody out, add a relevance search function to mod_zotfeed and // use somebody's own words from long ago to craft a reply to them.... require_once 'include/bbcode.php'; require_once 'include/html2plain.php'; if ($b['item'] && $b['item']['body']) { if (stristr($b['item']['body'], 'nocomment')) { return; } $txt = preg_replace('/\\@\\[z(.*?)\\[\\/zrl\\]/', '', $b['item']['body']); $txt = html2plain(bbcode($txt)); $pattern = substr($txt, 0, 255); } if ($b['item']['author_xchan']) { $z = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($b['item']['author_xchan'])); if ($z) { $mention = '@' . '[zrl=' . $z[0]['xchan_url'] . ']' . $z[0]['xchan_name'] . '[/zrl]' . "\n\n"; } } if (stristr($b['item']['body'], $c[0]['channel_name']) && mb_strlen($pattern) < 36 && stristr($pattern, 'thank')) { $reply = $replies[mt_rand(0, count($replies) - 1)]; } $x = array(); if ($reply) { $x['body'] = $mention . $reply; } else { require_once 'include/html2bbcode.php'; $valid = false; do { $url = 'http://' . $fort_server . '/cookie.php?f=&lang=any&off=a&pattern=' . urlencode($pattern); $s = z_fetch_url($url); if ($s['success'] && !$s['body']) { $s = z_fetch_url('http://' . $fort_server . '/cookie.php'); } if (!$s['success'] || !$s['body']) { return; } // if it might be a quote make it a quote if (strpos($s['body'], '--')) { $x['body'] = '[quote]' . html2bbcode($s['body']) . '[/quote]'; } else { $x['body'] = html2bbcode($s['body']); } $found_text = false; if ($h) { foreach ($h as $hh) { if (stripos($hh['body'], $x['body']) !== false) { $pattern = ''; $found_text = true; break; } } } if (!$found_text) { $valid = true; } } while (!$valid); } if ($mention) { $x['body'] = $mention . $x['body']; $x['term'] = array(array('uid' => $c[0]['channel_id'], 'type' => TERM_MENTION, 'otype' => TERM_OBJ_POST, 'term' => $z[0]['xchan_name'], 'url' => $z[0]['xchan_url'])); } $x['uid'] = $c[0]['channel_id']; $x['aid'] = $c[0]['channel_account_id']; $x['mid'] = item_message_id(); $x['parent'] = $p[0]['id']; $x['parent_mid'] = $b['item']['parent_mid']; $x['author_xchan'] = $c[0]['channel_hash']; $x['owner_xchan'] = $b['item']['owner_xchan']; $x['item_origin'] = 1; $x['item_verified'] = 1; // You can't pass a Turing test if you reply in milliseconds. // Also I believe we've got ten minutes fudge before we declare a post as time traveling. // Otherwise we'll just set it to now and it will still go out in milliseconds. // So set the reply to post sometime in the next 15-45 minutes (depends on poller interval) $fudge = mt_rand(15, 30); $x['created'] = $x['edited'] = datetime_convert('UTC', 'UTC', 'now + ' . $fudge . ' minutes'); $x['body'] = trim($x['body']); $x['sig'] = base64url_encode(rsa_sign($x['body'], $c[0]['channel_prvkey'])); $post = item_store($x); $post_id = $post['item_id']; $x['id'] = $post_id; call_hooks('post_local_end', $x); Zotlabs\Daemon\Master::Summon(array('Notifier', 'comment-new', $post_id)); }
function pumpio_dopost(&$a, $client, $uid, $self, $post, $own_id, $threadcompletion = true) { require_once 'include/items.php'; require_once 'include/html2bbcode.php'; if ($post->verb == "like" or $post->verb == "favorite") { return pumpio_dolike($a, $uid, $self, $post, $own_id); } if ($post->verb == "unlike" or $post->verb == "unfavorite") { return pumpio_dounlike($a, $uid, $self, $post, $own_id); } if ($post->verb == "delete") { return pumpio_dodelete($a, $uid, $self, $post, $own_id); } if ($post->verb != "update") { // Two queries for speed issues $r = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($post->object->id), intval($uid)); if (count($r)) { return false; } $r = q("SELECT * FROM `item` WHERE `extid` = '%s' AND `uid` = %d LIMIT 1", dbesc($post->object->id), intval($uid)); if (count($r)) { return false; } } // Only handle these three types if (!strstr("post|share|update", $post->verb)) { return false; } $receiptians = array(); if (@is_array($post->cc)) { $receiptians = array_merge($receiptians, $post->cc); } if (@is_array($post->to)) { $receiptians = array_merge($receiptians, $post->to); } foreach ($receiptians as $receiver) { if (is_string($receiver->objectType)) { if ($receiver->id == "http://activityschema.org/collection/public") { $public = true; } } } $postarray = array(); $postarray['network'] = NETWORK_PUMPIO; $postarray['gravity'] = 0; $postarray['uid'] = $uid; $postarray['wall'] = 0; $postarray['uri'] = $post->object->id; $postarray['object-type'] = NAMESPACE_ACTIVITY_SCHEMA . strtolower($post->object->objectType); if ($post->object->objectType != "comment") { $contact_id = pumpio_get_contact($uid, $post->actor); if (!$contact_id) { $contact_id = $self[0]['id']; } $postarray['parent-uri'] = $post->object->id; if (!$public) { $postarray['private'] = 1; $postarray['allow_cid'] = '<' . $self[0]['id'] . '>'; } } else { $contact_id = 0; if (link_compare($post->actor->url, $own_id)) { $contact_id = $self[0]['id']; $post->actor->displayName = $self[0]['name']; $post->actor->url = $self[0]['url']; $post->actor->image->url = $self[0]['photo']; } else { // Take an existing contact, the contact of the note or - as a fallback - the id of the user $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", dbesc($post->actor->url), intval($uid)); if (count($r)) { $contact_id = $r[0]['id']; } else { $r = q("SELECT * FROM `contact` WHERE `url` = '%s' AND `uid` = %d AND `blocked` = 0 AND `readonly` = 0 LIMIT 1", dbesc($post->actor->url), intval($uid)); if (count($r)) { $contact_id = $r[0]['id']; } else { $contact_id = $self[0]['id']; } } } $reply = new stdClass(); $reply->verb = "note"; $reply->cc = $post->cc; $reply->to = $post->to; $reply->object = new stdClass(); $reply->object->objectType = $post->object->inReplyTo->objectType; $reply->object->content = $post->object->inReplyTo->content; $reply->object->id = $post->object->inReplyTo->id; $reply->actor = $post->object->inReplyTo->author; $reply->url = $post->object->inReplyTo->url; $reply->generator = new stdClass(); $reply->generator->displayName = "pumpio"; $reply->published = $post->object->inReplyTo->published; $reply->received = $post->object->inReplyTo->updated; $reply->url = $post->object->inReplyTo->url; pumpio_dopost($a, $client, $uid, $self, $reply, $own_id, false); $postarray['parent-uri'] = $post->object->inReplyTo->id; } if ($post->object->pump_io->proxyURL) { $postarray['extid'] = $post->object->pump_io->proxyURL; } $postarray['contact-id'] = $contact_id; $postarray['verb'] = ACTIVITY_POST; $postarray['owner-name'] = $post->actor->displayName; $postarray['owner-link'] = $post->actor->url; $postarray['owner-avatar'] = $post->actor->image->url; $postarray['author-name'] = $post->actor->displayName; $postarray['author-link'] = $post->actor->url; $postarray['author-avatar'] = $post->actor->image->url; $postarray['plink'] = $post->object->url; $postarray['app'] = $post->generator->displayName; $postarray['body'] = html2bbcode($post->object->content); if ($post->object->fullImage->url != "") { $postarray["body"] = "[url=" . $post->object->fullImage->url . "][img]" . $post->object->image->url . "[/img][/url]\n" . $postarray["body"]; } if ($post->object->displayName != "") { $postarray['title'] = $post->object->displayName; } $postarray['created'] = datetime_convert('UTC', 'UTC', $post->published); $postarray['edited'] = datetime_convert('UTC', 'UTC', $post->received); if ($post->verb == "share") { if (!intval(get_config('system', 'wall-to-wall_share'))) { $postarray['body'] = "[share author='" . $post->object->author->displayName . "' profile='" . $post->object->author->url . "' avatar='" . $post->object->author->image->url . "' posted='" . datetime_convert('UTC', 'UTC', $post->object->created) . "' link='" . $post->links->self->href . "']" . $postarray['body'] . "[/share]"; } else { // Let shares look like wall-to-wall posts $postarray['author-name'] = $post->object->author->displayName; $postarray['author-link'] = $post->object->author->url; $postarray['author-avatar'] = $post->object->author->image->url; } } if (trim($postarray['body']) == "") { return false; } $top_item = item_store($postarray); $postarray["id"] = $top_item; if ($top_item == 0 and $post->verb == "update") { $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s' , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", dbesc($postarray["title"]), dbesc($postarray["body"]), dbesc($postarray["edited"]), dbesc($postarray["uri"]), intval($uid)); } if ($post->object->objectType == "comment") { if ($threadcompletion) { pumpio_fetchallcomments($a, $uid, $postarray['parent-uri']); } $user = q("SELECT * FROM `user` WHERE `uid` = %d AND `account_expired` = 0 LIMIT 1", intval($uid)); if (!count($user)) { return $top_item; } $importer_url = $a->get_baseurl() . '/profile/' . $user[0]['nickname']; if (link_compare($own_id, $postarray['author-link'])) { return $top_item; } $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", dbesc($postarray['parent-uri']), intval($uid)); if (count($myconv)) { foreach ($myconv as $conv) { // now if we find a match, it means we're in this conversation if (!link_compare($conv['author-link'], $importer_url) and !link_compare($conv['author-link'], $own_id)) { continue; } require_once 'include/enotify.php'; $conv_parent = $conv['parent']; notification(array('type' => NOTIFY_COMMENT, 'notify_flags' => $user[0]['notify-flags'], 'language' => $user[0]['language'], 'to_name' => $user[0]['username'], 'to_email' => $user[0]['email'], 'uid' => $user[0]['uid'], 'item' => $postarray, 'link' => $a->get_baseurl() . '/display/' . urlencode(get_item_guid($top_item)), 'source_name' => $postarray['author-name'], 'source_link' => $postarray['author-link'], 'source_photo' => $postarray['author-avatar'], 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $conv_parent)); // only send one notification break; } } } return $top_item; }