/** * @brief "Render" a conversation or list of items for HTML display. * * There are two major forms of display: * - Sequential or unthreaded ("New Item View" or search results) * - conversation view * * The $mode parameter decides between the various renderings and also * figures out how to determine page owner and other contextual items * that are based on unique features of the calling module. * * @param App &$a * @param array $items * @param string $mode * @param boolean $update * @param string $page_mode default traditional * @param string $prepared_item * @return string */ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $prepared_item = '') { $content_html = ''; $o = ''; require_once 'bbcode.php'; $ssl_state = local_channel() ? true : false; if (local_channel()) { load_pconfig(local_channel(), ''); } $arr_blocked = null; if (local_channel()) { $str_blocked = get_pconfig(local_channel(), 'system', 'blocked'); } if (!local_channel() && $mode == 'network') { $sys = get_sys_channel(); $id = $sys['channel_id']; $str_blocked = get_pconfig($id, 'system', 'blocked'); } if ($str_blocked) { $arr_blocked = explode(',', $str_blocked); for ($x = 0; $x < count($arr_blocked); $x++) { $arr_blocked[$x] = trim($arr_blocked[$x]); } } $profile_owner = 0; $page_writeable = false; $live_update_div = ''; $preview = $page_mode === 'preview' ? true : false; $previewing = $preview ? ' preview ' : ''; if ($mode === 'network') { $profile_owner = local_channel(); $page_writeable = true; if (!$update) { // The special div is needed for liveUpdate to kick in for this page. // We only launch liveUpdate if you aren't filtering in some incompatible // way and also you aren't writing a comment (discovered in javascript). $live_update_div = '<div id="live-network"></div>' . "\r\n" . "<script> var profile_uid = " . $_SESSION['uid'] . "; var netargs = '" . substr($a->cmd, 8) . '?f=' . (x($_GET, 'cid') ? '&cid=' . $_GET['cid'] : '') . (x($_GET, 'search') ? '&search=' . $_GET['search'] : '') . (x($_GET, 'star') ? '&star=' . $_GET['star'] : '') . (x($_GET, 'order') ? '&order=' . $_GET['order'] : '') . (x($_GET, 'bmark') ? '&bmark=' . $_GET['bmark'] : '') . (x($_GET, 'liked') ? '&liked=' . $_GET['liked'] : '') . (x($_GET, 'conv') ? '&conv=' . $_GET['conv'] : '') . (x($_GET, 'spam') ? '&spam=' . $_GET['spam'] : '') . (x($_GET, 'nets') ? '&nets=' . $_GET['nets'] : '') . (x($_GET, 'cmin') ? '&cmin=' . $_GET['cmin'] : '') . (x($_GET, 'cmax') ? '&cmax=' . $_GET['cmax'] : '') . (x($_GET, 'file') ? '&file=' . $_GET['file'] : '') . (x($_GET, 'uri') ? '&uri=' . $_GET['uri'] : '') . "'; var profile_page = " . $a->pager['page'] . "; </script>\r\n"; } } elseif ($mode === 'channel') { $profile_owner = $a->profile['profile_uid']; $page_writeable = $profile_owner == local_channel(); if (!$update) { $tab = notags(trim($_GET['tab'])); if ($tab === 'posts') { // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, // because browser prefetching might change it on us. We have to deliver it with the page. $live_update_div = '<div id="live-channel"></div>' . "\r\n" . "<script> var profile_uid = " . $a->profile['profile_uid'] . "; var netargs = '?f='; var profile_page = " . $a->pager['page'] . "; </script>\r\n"; } } } elseif ($mode === 'display') { $profile_owner = local_channel(); $page_writeable = false; $live_update_div = '<div id="live-display"></div>' . "\r\n"; } elseif ($mode === 'page') { $profile_owner = $a->profile['uid']; $page_writeable = $profile_owner == local_channel(); $live_update_div = '<div id="live-page"></div>' . "\r\n"; } elseif ($mode === 'search') { $live_update_div = '<div id="live-search"></div>' . "\r\n"; } elseif ($mode === 'photos') { $profile_onwer = $a->profile['profile_uid']; $page_writeable = $profile_owner == local_channel(); $live_update_div = '<div id="live-photos"></div>' . "\r\n"; // for photos we've already formatted the top-level item (the photo) $content_html = $a->data['photo_html']; } $page_dropping = local_channel() && local_channel() == $profile_owner ? true : false; if (!feature_enabled($profile_owner, 'multi_delete')) { $page_dropping = false; } $channel = $a->get_channel(); $observer = $a->get_observer(); if ($update) { $return_url = $_SESSION['return_url']; } else { $return_url = $_SESSION['return_url'] = $a->query_string; } load_contact_links(local_channel()); $cb = array('items' => $items, 'mode' => $mode, 'update' => $update, 'preview' => $preview); call_hooks('conversation_start', $cb); $items = $cb['items']; $conv_responses = array('like' => array('title' => t('Likes', 'title')), 'dislike' => array('title' => t('Dislikes', 'title')), 'agree' => array('title' => t('Agree', 'title')), 'disagree' => array('title' => t('Disagree', 'title')), 'abstain' => array('title' => t('Abstain', 'title')), 'attendyes' => array('title' => t('Attending', 'title')), 'attendno' => array('title' => t('Not attending', 'title')), 'attendmaybe' => array('title' => t('Might attend', 'title'))); // array with html for each thread (parent+comments) $threads = array(); $threadsid = -1; $page_template = get_markup_template("conversation.tpl"); if ($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) { if ($arr_blocked) { $blocked = false; foreach ($arr_blocked as $b) { if ($b && $item['author_xchan'] == $b) { $blocked = true; break; } } if ($blocked) { continue; } } $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']; } $profile_name = strlen($item['author-name']) ? $item['author-name'] : $item['name']; if ($item['author-link'] && !$item['author-name']) { $profile_name = $item['author-link']; } $tags = array(); $hashtags = array(); $mentions = array(); $sp = false; $profile_link = best_link_url($item, $sp); if ($sp) { $sparkle = ' sparkle'; } else { $profile_link = zid($profile_link); } $normalised = normalise_link(strlen($item['author-link']) ? $item['author-link'] : $item['url']); $profile_name = $item['author']['xchan_name']; $profile_link = $item['author']['xchan_url']; $profile_avatar = $item['author']['xchan_photo_m']; $location = format_location($item); localize_item($item); if ($mode === 'network-new') { $dropping = true; } else { $dropping = false; } $drop = array('pagedropping' => $page_dropping, 'dropping' => $dropping, 'select' => t('Select'), 'delete' => t('Delete')); $star = false; $isstarred = "unstarred icon-star-empty"; $lock = $item['item_private'] || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid']) ? t('Private Message') : false; $likebuttons = false; $shareable = false; $verified = $item['item_flags'] & ITEM_VERIFIED ? t('Message signature validated') : ''; $forged = $item['sig'] && !($item['item_flags'] & ITEM_VERIFIED) ? t('Message signature incorrect') : ''; $unverified = ''; $tags = array(); $terms = get_terms_oftype($item['term'], array(TERM_HASHTAG, TERM_MENTION, TERM_UNKNOWN)); if (count($terms)) { foreach ($terms as $tag) { $tags[] = format_term_for_display($tag); } } $body = prepare_body($item, true); $tmp_item = array('template' => $tpl, 'toplevel' => 'toplevel_item', 'mode' => $mode, 'id' => $preview ? 'P0' : $item['item_id'], 'linktitle' => sprintf(t('View %s\'s profile @ %s'), $profile_name, $profile_url), 'profile_url' => $profile_link, 'item_photo_menu' => item_photo_menu($item), 'name' => $profile_name, 'sparkle' => $sparkle, 'lock' => $lock, 'thumb' => $profile_avatar, 'title' => $item['title'], 'body' => $body, 'tags' => $tags, 'hashtags' => $hashtags, 'mentions' => $mentions, 'verified' => $verified, 'unverified' => $unverified, 'forged' => $forged, 'txt_cats' => t('Categories:'), 'txt_folders' => t('Filed under:'), 'has_cats' => count($categories) ? 'true' : '', 'has_folders' => count($folders) ? 'true' : '', 'categories' => $categories, 'folders' => $folders, 'text' => strip_tags($body), 'ago' => relative_date($item['created']), 'app' => $item['app'], 'str_app' => sprintf(t(' from %s'), $item['app']), 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), 'editedtime' => $item['edited'] != $item['created'] ? sprintf(t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : '', 'expiretime' => $item['expires'] !== NULL_DATE ? sprintf(t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')) : '', 'location' => $location, 'indent' => '', 'owner_name' => $owner_name, 'owner_url' => $owner_url, 'owner_photo' => $owner_photo, 'plink' => get_plink($item, false), 'edpost' => false, 'isstarred' => $isstarred, 'star' => $star, 'drop' => $drop, 'vote' => $likebuttons, 'like' => '', 'dislike' => '', 'comment' => '', 'conv' => $preview ? '' : array('href' => z_root() . '/display/' . $item['mid'], 'title' => t('View in context')), 'previewing' => $previewing, 'wait' => t('Please wait'), 'thread_level' => 1); $arr = array('item' => $item, 'output' => $tmp_item); call_hooks('display_item', $arr); // $threads[$threadsid]['id'] = $item['item_id']; $threads[] = $arr['output']; } } else { // Normal View // logger('conv: items: ' . print_r($items,true)); require_once 'include/ConversationObject.php'; require_once 'include/ItemObject.php'; $conv = new Conversation($mode, $preview, $prepared_item); // In the display mode we don't have a profile owner. if ($mode === 'display' && $items) { $conv->set_profile_owner($items[0]['uid']); } // get all the topmost parents // this shouldn't be needed, as we should have only them in our array // But for now, this array respects the old style, just in case $threads = array(); foreach ($items as $item) { // Check for any blocked authors if ($arr_blocked) { $blocked = false; foreach ($arr_blocked as $b) { if ($b && $item['author_xchan'] == $b) { $blocked = true; break; } } if ($blocked) { continue; } } // Check all the kids too if ($arr_blocked && $item['children']) { for ($d = 0; $d < count($item['children']); $d++) { foreach ($arr_blocked as $b) { if ($b && $item['children'][$d]['author_xchan'] == $b) { $item['children'][$d]['author_blocked'] = true; } } } } builtin_activity_puller($item, $conv_responses); if (!visible_activity($item)) { continue; } $item['pagedrop'] = $page_dropping; if ($item['id'] == $item['parent']) { $item_object = new Item($item); $conv->add_thread($item_object); if ($page_mode === 'list') { $item_object->set_template('conv_list.tpl'); $item_object->set_display_mode('list'); } } } $threads = $conv->get_template_data($conv_responses); if (!$threads) { logger('[ERROR] conversation : Failed to get template data.', LOGGER_DEBUG); $threads = array(); } } } if ($page_mode === 'traditional' || $page_mode === 'preview') { $page_template = get_markup_template("threaded_conversation.tpl"); } elseif ($update) { $page_template = get_markup_template("convobj.tpl"); } else { $page_template = get_markup_template("conv_frame.tpl"); $threads = null; } // if($page_mode === 'preview') // logger('preview: ' . print_r($threads,true)); // Do not un-comment if smarty3 is in use // logger('page_template: ' . $page_template); // logger('nouveau: ' . print_r($threads,true)); $o .= replace_macros($page_template, array('$baseurl' => $a->get_baseurl($ssl_state), '$photo_item' => $content_html, '$live_update' => $live_update_div, '$remove' => t('remove'), '$mode' => $mode, '$user' => $a->user, '$threads' => $threads, '$wait' => t('Loading...'), '$dropping' => $page_dropping ? t('Delete Selected Items') : False)); return $o; }