Exemple #1
0
    echo "<a href='" . URL("conversation/markAsRead/" . $conversation["conversationId"] . "?token=" . ET::$session->token . "&return=" . urlencode(ET::$controller->selfURL)) . "' class='unreadIndicator' title='" . T("Mark as read") . "'>" . $conversation["unread"] . "</a> ";
}
// Output controls which apply to this conversation.
echo "<span class='controls'>";
// A Jump to last/unread link, depending on the user and the unread state.
if (ET::$session->user and $conversation["unread"]) {
    echo "<a href='" . URL($conversationURL . "/unread") . "' class='jumpToUnread'>" . T("Jump to unread") . "</a>";
} else {
    echo "<a href='" . URL($conversationURL . "/last") . "' class='jumpToLast'>" . T("Jump to last") . "</a>";
}
// If we're highlighting search terms (i.e. if we did a fulltext search), then output a "show matching posts" link.
if (ET::$session->get("highlight")) {
    echo " <a href='" . URL($conversationURL . "/?search=" . urlencode($data["fulltextString"])) . "' class='showMatchingPosts'>" . T("Show matching posts") . "</a>";
}
echo "</span>";
?>
</div>
<div class='col-channel'><?php 
$channel = $data["channelInfo"][$conversation["channelId"]];
echo "<a href='" . URL(searchURL("", $channel["slug"])) . "' class='channel channel-{$conversation["channelId"]}' data-channel='{$channel["slug"]}'>{$channel["title"]}</a>";
?>
</div>
<div class='col-lastPost'><?php 
echo "<span class='action'>" . avatar($conversation["lastPostMemberId"], $conversation["lastPostMemberAvatarFormat"], "thumb"), " ", sprintf(T("%s posted %s"), "<span class='lastPostMember name'>" . memberLink($conversation["lastPostMemberId"], $conversation["lastPostMember"]) . "</span>", "<span class='lastPostTime'>" . relativeTime($conversation["lastPostTime"], true) . "</span>"), "</span>";
?>
</div>
<div class='col-replies'><?php 
echo "<span>" . Ts("%s reply", "%s replies", $conversation["replies"]) . "</span>";
?>
</div>
</li>
Exemple #2
0
if (empty($member["preferences"]["hideOnline"])) {
    $lastAction = ET::memberModel()->getLastActionInfo($member["lastActionTime"], $member["lastActionDetail"]);
    if ($lastAction) {
        echo "<" . (!empty($lastAction[1]) ? "a href='{$lastAction[1]}'" : "span") . " class='online' title='" . T("Online") . ($lastAction[0] ? " (" . sanitizeHTML($lastAction[0]) . ")" : "") . "'><i class='icon-circle'></i></" . (!empty($lastAction[1]) ? "a" : "span") . ">";
    }
}
?>

<span class='group subText'><?php 
echo memberGroup($member["account"], $member["groups"]);
?>
</span>

</div>

<div class='col-lastActive'>
<span class='subText'><?php 
printf(T("Last active %s"), empty($member["preferences"]["hideOnline"]) ? "<span title='" . date(T("date.full"), $member["lastActionTime"]) . "'>" . relativeTime($member["lastActionTime"], true) . "</span>" : "[" . T("hidden") . "]");
?>
</span>
</div>

<div class='col-replies'>
<span class='subText'><?php 
echo Ts("%s post", "%s posts", $member["countPosts"]);
?>
</span>
</div>

</li>
 /**
  * Common initialization for all controllers, called on every page load. This will add basic user links to
  * the "user" menu, and add core JS files and language definitions.
  *
  * If this is overridden, parent::init() should be called to maintain consistency between controllers.
  *
  * @return void
  */
 public function init()
 {
     // Check for updates to the esoTalk software, but only if we're the root admin and we haven't checked in
     // a while.
     if (ET::$session->userId == C("esoTalk.rootAdmin") and C("esoTalk.admin.lastUpdateCheckTime") + C("esoTalk.updateCheckInterval") < time()) {
         ET::upgradeModel()->checkForUpdates();
     }
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // If the user IS NOT logged in, add the 'login' and 'sign up' links to the bar.
         if (!ET::$session->user) {
             $this->addToMenu("user", "join", "<a href='" . URL("user/join?return=" . urlencode($this->selfURL)) . "' class='link-join'>" . T("Sign Up") . "</a>");
             $this->addToMenu("user", "login", "<a href='" . URL("user/login?return=" . urlencode($this->selfURL)) . "' class='link-login'>" . T("Log In") . "</a>");
         } else {
             $this->addToMenu("user", "user", "<a href='" . URL("member/me") . "'>" . avatar(ET::$session->user, "thumb") . name(ET::$session->user["username"]) . "</a>");
             $this->addToMenu("user", "settings", "<a href='" . URL("settings") . "' class='link-settings'>" . T("Settings") . "</a>");
             if (ET::$session->isAdmin()) {
                 $this->addToMenu("user", "administration", "<a href='" . URL("admin") . "' class='link-administration'>" . T("Administration") . "</a>");
             }
             $this->addToMenu("user", "logout", "<a href='" . URL("user/logout?token=" . ET::$session->token) . "' class='link-logout'>" . T("Log Out") . "</a>");
         }
         // Get the number of members currently online and add it as a statistic.
         if (C("esoTalk.members.visibleToGuests") or ET::$session->user) {
             $online = ET::SQL()->select("COUNT(*)")->from("member")->where("UNIX_TIMESTAMP()-:seconds<lastActionTime")->bind(":seconds", C("esoTalk.userOnlineExpire"))->exec()->result();
             $stat = Ts("statistic.online", "statistic.online.plural", number_format($online));
             $stat = "<a href='" . URL("members/online") . "' class='link-membersOnline'>{$stat}</a>";
             $this->addToMenu("statistics", "statistic-online", $stat);
         }
         $this->addToMenu("meta", "copyright", "<a href='http://esotalk.org/' target='_blank'>" . T("Powered by") . " esoTalk</a>");
         // Set up some default JavaScript files and language definitions.
         $this->addJSFile("core/js/lib/jquery.js", true);
         $this->addJSFile("core/js/lib/jquery.migrate.js", true);
         $this->addJSFile("core/js/lib/jquery.misc.js", true);
         $this->addJSFile("core/js/lib/jquery.history.js", true);
         $this->addJSFile("core/js/lib/jquery.scrollTo.js", true);
         $this->addJSFile("core/js/global.js", true);
         $this->addJSLanguage("message.ajaxRequestPending", "message.ajaxDisconnected", "Loading...", "Notifications");
         $this->addJSVar("notificationCheckInterval", C("esoTalk.notificationCheckInterval"));
     }
     $this->trigger("init");
 }
/**
 * Get a human-friendly string (eg. 1 hour ago) for how much time has passed since a given time.
 *
 * @param int $then UNIX timestamp of the time to work out how much time has passed since.
 * @param bool $precise Whether or not to return "x minutes/seconds", or just "a few minutes".
 * @return string A human-friendly time string.
 *
 * @package esoTalk
 */
function relativeTime($then, $precise = false)
{
    // If there is no $then, we can only assume that whatever it is never happened...
    if (!$then) {
        return T("never");
    }
    // Work out how many seconds it has been since $then.
    $ago = time() - $then;
    // If $then happened less than 1 second ago, say "Just now".
    if ($ago < 1) {
        return T("just now");
    }
    // If this happened over a year ago, return "x years ago".
    if ($ago >= ($period = 60 * 60 * 24 * 365.25)) {
        $years = round($ago / $period);
        return Ts("%d year ago", "%d years ago", $years);
    } elseif ($ago >= ($period = 60 * 60 * 24 * (365.25 / 12)) * 2) {
        $months = round($ago / $period);
        return Ts("%d month ago", "%d months ago", $months);
    } elseif ($ago >= ($period = 60 * 60 * 24 * 7)) {
        $weeks = round($ago / $period);
        return Ts("%d week ago", "%d weeks ago", $weeks);
    } elseif ($ago >= ($period = 60 * 60 * 24)) {
        $days = round($ago / $period);
        return Ts("%d day ago", "%d days ago", $days);
    } elseif ($ago >= ($period = 60 * 60)) {
        $hours = round($ago / $period);
        return Ts("%d hour ago", "%d hours ago", $hours);
    }
    // If we're going for a precise value, go on to test at the minute/second level.
    if ($precise) {
        // If this happened over a minute ago, return "x minutes ago".
        if ($ago >= ($period = 60)) {
            $minutes = round($ago / $period);
            return Ts("%d minute ago", "%d minutes ago", $minutes);
        } elseif ($ago >= 1) {
            return Ts("%d second ago", "%d seconds ago", $ago);
        }
    }
    // Otherwise, just return "Just now".
    return T("just now");
}
Exemple #5
0
 public function handler_conversationController_renderScrubberBefore($sender, $data)
 {
     echo "<div class='conversationViews'><i class='icon-eye-open'></i> " . Ts("%s view", "%s views", $data["conversation"]["views"]) . "</div>";
 }
 /**
  * Display a list of conversations, optionally filtered by channel(s) and a search string.
  *
  * @return void
  */
 public function action_index($channelSlug = false)
 {
     if (!$this->allowed()) {
         return;
     }
     list($channelInfo, $currentChannels, $channelIds, $includeDescendants) = $this->getSelectedChannels($channelSlug);
     // Now we need to construct some arrays to determine which channel "tabs" to show in the view.
     // $channels is a list of channels with the same parent as the current selected channel(s).
     // $path is a breadcrumb trail to the depth of the currently selected channel(s).
     $channels = array();
     $path = array();
     // Work out what channel we will use as the "parent" channel. This will be the last item in $path,
     // and its children will be in $channels.
     $curChannel = false;
     // If channels have been selected, use the first of them.
     if (count($currentChannels)) {
         $curChannel = $channelInfo[$currentChannels[0]];
     }
     // If the currently selected channel has no children, or if we're not including descendants, use
     // its parent as the parent channel.
     if ($curChannel and $curChannel["lft"] >= $curChannel["rgt"] - 1 or !$includeDescendants) {
         $curChannel = @$channelInfo[$curChannel["parentId"]];
     }
     // If no channel is selected, make a faux parent channel.
     if (!$curChannel) {
         $curChannel = array("lft" => 0, "rgt" => PHP_INT_MAX, "depth" => -1);
     }
     // Now, finally, go through all the channels and add ancestors of the "parent" channel to the $path,
     // and direct children to the list of $channels. Make sure we don't include any channels which
     // the user has unsubscribed to.
     foreach ($channelInfo as $channel) {
         if ($channel["lft"] > $curChannel["lft"] and $channel["rgt"] < $curChannel["rgt"] and $channel["depth"] == $curChannel["depth"] + 1 and empty($channel["unsubscribed"])) {
             $channels[] = $channel;
         } elseif ($channel["lft"] <= $curChannel["lft"] and $channel["rgt"] >= $curChannel["rgt"]) {
             $path[] = $channel;
         }
     }
     // Store the currently selected channel in the session, so that it can be automatically selected
     // if "New conversation" is clicked.
     if (!empty($currentChannels)) {
         ET::$session->store("searchChannelId", $currentChannels[0]);
     }
     // Get the search string request value.
     $searchString = R("search");
     // Last, but definitely not least... perform the search!
     $search = ET::searchModel();
     $conversationIDs = $search->getConversationIDs($channelIds, $searchString, count($currentChannels) or !ET::$session->userId);
     // If this page was originally accessed at conversations/markAsRead/all?search=whatever (the
     // markAsRead method simply calls the index method), then mark the results as read.
     if ($this->controllerMethod == "markasread" and ET::$session->userId) {
         ET::conversationModel()->markAsRead($conversationIDs, ET::$session->userId);
     }
     $results = $search->getResults($conversationIDs);
     // Were there any errors? Show them as messages.
     if ($search->errorCount()) {
         $this->messages($search->errors(), "warning dismissable");
     } else {
         $this->highlight($search->fulltext);
     }
     // Pass on a bunch of data to the view.
     $this->data("results", $results);
     $this->data("limit", $search->limit);
     $this->data("showViewMoreLink", $search->areMoreResults());
     $this->data("channelPath", $path);
     $this->data("channelTabs", $channels);
     $this->data("currentChannels", $currentChannels);
     $this->data("channelInfo", $channelInfo);
     $this->data("channelSlug", $channelSlug = $channelSlug ? $channelSlug : "all");
     $this->data("searchString", $searchString);
     $this->data("fulltextString", implode(" ", $search->fulltext));
     // Construct a canonical URL and add to the breadcrumb stack.
     $slugs = array();
     foreach ($currentChannels as $channel) {
         $slugs[] = $channelInfo[$channel]["slug"];
     }
     $url = "conversations/" . urlencode(($k = implode(" ", $slugs)) ? $k : "all") . ($searchString ? "?search=" . urlencode($searchString) : "");
     $this->pushNavigation("conversations", "search", URL($url));
     $this->canonicalURL = URL($url, true);
     // If we're loading the page in full...
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // Update the user's last action.
         ET::memberModel()->updateLastAction("search");
         // Add a link to the RSS feed in the bar.
         // $this->addToMenu("meta", "feed", "<a href='".URL(str_replace("conversations/", "conversations/index.atom/", $url))."' id='feed'>".T("Feed")."</a>");
         $controls = ETFactory::make("menu");
         // Mark as read controls
         if (ET::$session->user) {
             $controls->add("markAllAsRead", "<a href='" . URL("conversations/markAllAsRead/?token=" . ET::$session->token . "' id='control-markAllAsRead'><i class='icon-check'></i> " . T("Mark all as read") . "</a>"));
             $controls->add("markListedAsRead", "<a href='" . URL("conversations/{$channelSlug}/?search=" . urlencode($searchString) . "&markAsRead=1&token=" . ET::$session->token . "' id='control-markListedAsRead'><i class='icon-list'></i> " . T("Mark listed as read") . "</a>"));
         }
         // Add the default gambits to the gambit cloud: gambit text => css class to apply.
         $gambits = array("main" => array(T("gambit.sticky") => array("gambit-sticky", "icon-pushpin")), "time" => array(T("gambit.order by newest") => array("gambit-orderByNewest", "icon-list-ol"), T("gambit.active last ? hours") => array("gambit-activeLastHours", "icon-time"), T("gambit.active last ? days") => array("gambit-activeLastDays", "icon-calendar"), T("gambit.active today") => array("gambit-activeToday", "icon-asterisk"), T("gambit.dead") => array("gambit-dead", "icon-remove"), T("gambit.locked") => array("gambit-locked", "icon-lock")), "member" => array(T("gambit.author:") . T("gambit.member") => array("gambit-author", "icon-user"), T("gambit.contributor:") . T("gambit.member") => array("gambit-contributor", "icon-user")), "replies" => array(T("gambit.has replies") => array("gambit-hasReplies", "icon-comment"), T("gambit.has >10 replies") => array("gambit-replies", "icon-comments"), T("gambit.order by replies") => array("gambit-orderByReplies", "icon-list-ol")), "text" => array(T("gambit.title:") . " ?" => array("gambit-title", "icon-font")), "misc" => array(T("gambit.random") => array("gambit-random", "icon-random"), T("gambit.reverse") => array("gambit-reverse", "icon-exchange")));
         // Add some more personal gambits if there is a user logged in.
         if (ET::$session->user) {
             addToArrayString($gambits["main"], T("gambit.private"), array("gambit-private", "icon-envelope-alt"), 1);
             addToArrayString($gambits["main"], T("gambit.starred"), array("gambit-starred", "icon-star"), 2);
             addToArrayString($gambits["main"], T("gambit.draft"), array("gambit-draft", "icon-pencil"), 3);
             addToArrayString($gambits["main"], T("gambit.ignored"), array("gambit-ignored", "icon-eye-close"), 4);
             addToArrayString($gambits["time"], T("gambit.unread"), array("gambit-unread", "icon-inbox"), 0);
             addToArrayString($gambits["member"], T("gambit.author:") . T("gambit.myself"), array("gambit-authorMyself", "icon-smile"), 0);
             addToArrayString($gambits["member"], T("gambit.contributor:") . T("gambit.myself"), array("gambit-contributorMyself", "icon-smile"), 2);
         }
         $this->trigger("constructGambitsMenu", array(&$gambits));
         // Construct the gambits menu based on the above arrays.
         $gambitsMenu = ETFactory::make("menu");
         $linkPrefix = "conversations/" . $channelSlug . "/?search=" . urlencode(!empty($searchString) ? $searchString . " + " : "");
         foreach ($gambits as $section => $items) {
             foreach ($items as $gambit => $classes) {
                 $gambitsMenu->add($classes[0], "<a href='" . URL($linkPrefix . urlencode("#" . $gambit)) . "' class='{$classes[0]}' data-gambit='{$gambit}'>" . (!empty($classes[1]) ? "<i class='{$classes[1]}'></i> " : "") . "{$gambit}</a>");
             }
             end($gambits);
             if ($section !== key($gambits)) {
                 $gambitsMenu->separator();
             }
         }
         $this->data("controlsMenu", $controls);
         $this->data("gambitsMenu", $gambitsMenu);
         // Construct a list of keywords to use in the meta tags.
         $keywords = array();
         foreach ($channelInfo as $c) {
             if ($c["depth"] == 0) {
                 $keywords[] = strtolower($c["title"]);
             }
         }
         // Add meta tags to the header.
         $this->addToHead("<meta name='keywords' content='" . sanitizeHTML(($k = C("esoTalk.meta.keywords")) ? $k : implode(",", $keywords)) . "'>");
         $lastKeyword = reset(array_splice($keywords, count($keywords) - 1, 1));
         $this->addToHead("<meta name='description' content='" . sanitizeHTML(($d = C("esoTalk.meta.description")) ? $d : sprintf(T("forumDescription"), C("esoTalk.forumTitle"), implode(", ", $keywords), $lastKeyword)) . "'>");
         // If this is not technically the homepage (if it's a search page) the we don't want it to be indexed.
         if ($searchString) {
             $this->addToHead("<meta name='robots' content='noindex, noarchive'>");
         }
         // Add JavaScript language definitions and variables.
         $this->addJSLanguage("Starred", "Unstarred", "gambit.member", "gambit.more results", "Filter conversations", "Jump to last");
         $this->addJSVar("searchUpdateInterval", C("esoTalk.search.updateInterval"));
         $this->addJSVar("currentSearch", $searchString);
         $this->addJSVar("currentChannels", $currentChannels);
         $this->addJSFile("core/js/lib/jquery.cookie.js");
         $this->addJSFile("core/js/autocomplete.js");
         $this->addJSFile("core/js/search.js");
         // Add an array of channels in the form slug => id for the JavaScript to use.
         $channels = array();
         foreach ($channelInfo as $id => $c) {
             $channels[$id] = $c["slug"];
         }
         $this->addJSVar("channels", $channels);
         // Get a bunch of statistics...
         $queries = array("post" => ET::SQL()->select("COUNT(*)")->from("post")->get(), "conversation" => ET::SQL()->select("COUNT(*)")->from("conversation")->get(), "member" => ET::SQL()->select("COUNT(*)")->from("member")->get());
         $sql = ET::SQL();
         foreach ($queries as $k => $query) {
             $sql->select("({$query}) AS {$k}");
         }
         $stats = $sql->exec()->firstRow();
         // ...and show them in the footer.
         foreach ($stats as $k => $v) {
             $stat = Ts("statistic.{$k}", "statistic.{$k}.plural", number_format($v));
             if ($k == "member" and (C("esoTalk.members.visibleToGuests") or ET::$session->user)) {
                 $stat = "<a href='" . URL("members") . "'>{$stat}</a>";
             }
             $this->addToMenu("statistics", "statistic-{$k}", $stat, array("before" => "statistic-online"));
         }
         $this->render("conversations/index");
     } elseif ($this->responseType === RESPONSE_TYPE_VIEW) {
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_AJAX) {
         $this->json("channels", $this->getViewContents("channels/tabs", $this->data));
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_JSON) {
         $this->json("results", $results);
         $this->render();
     }
 }
Exemple #7
0
    }
    ?>

<div class='info'>
<a href='<?php 
    echo URL("conversations/" . $channel["slug"]);
    ?>
' class='channel channel-<?php 
    echo $channel["channelId"];
    ?>
'><?php 
    echo $channel["title"];
    ?>
</a>
<span class='stats'><?php 
    echo Ts("%s conversation", "%s conversations", $channel["countConversations"]);
    ?>
</span>
<?php 
    if (!empty($channel["description"])) {
        ?>
<p class='description'><?php 
        echo $channel["description"];
        ?>
</p><?php 
    }
    ?>
</div>
</li>

<?php 
 /**
  * Common initialization for all controllers, called on every page load. This will add basic user links to
  * the "user" menu, and add core JS files and language definitions.
  *
  * If this is overridden, parent::init() should be called to maintain consistency between controllers.
  *
  * @return void
  */
 public function init()
 {
     // Check for updates to the esoTalk software, but only if we're the root admin and we haven't checked in
     // a while.
     if (ET::$session->userId == C("esoTalk.rootAdmin") and C("esoTalk.admin.lastUpdateCheckTime") + C("esoTalk.updateCheckInterval") < time()) {
         ET::upgradeModel()->checkForUpdates();
     }
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // If the user IS NOT logged in, add the 'login' and 'sign up' links to the bar.
         if (!ET::$session->user) {
             $this->addToMenu("user", "login", "<a href='" . URL("user/login?return=" . urlencode($this->selfURL)) . "' class='link-login'>" . T("Log In") . "</a>");
             $this->addToMenu("user", "join", "<a href='" . URL("user/join?return=" . urlencode($this->selfURL)) . "' class='link-join'>" . T("Sign Up") . "</a>");
         } else {
             $this->addToMenu("user", "user", "<a href='" . URL("member/me") . "'>" . avatar(ET::$session->userId, ET::$session->user["avatarFormat"], "thumb") . name(ET::$session->user["username"]) . "</a>");
             // Fetch all unread notifications so we have a count for the notifications button.
             $notifications = ET::activityModel()->getNotifications(-1);
             $count = count($notifications);
             $this->addToMenu("user", "notifications", "<a href='" . URL("settings/notifications") . "' id='notifications' class='popupButton " . ($count ? "new" : "") . "'><span>{$count}</span></a>");
             // Show messages with these notifications.
             $this->notificationMessages($notifications);
             $this->addToMenu("user", "settings", "<a href='" . URL("settings") . "' class='link-settings'>" . T("Settings") . "</a>");
             if (ET::$session->isAdmin()) {
                 $this->addToMenu("user", "administration", "<a href='" . URL("admin") . "' class='link-administration'>" . T("Administration") . "</a>");
             }
             $this->addToMenu("user", "logout", "<a href='" . URL("user/logout") . "' class='link-logout'>" . T("Log Out") . "</a>");
         }
         // Get the number of members currently online and add it as a statistic.
         $online = ET::SQL()->select("COUNT(*)")->from("member")->where("UNIX_TIMESTAMP()-:seconds<lastActionTime")->bind(":seconds", C("esoTalk.userOnlineExpire"))->exec()->result();
         $stat = Ts("statistic.online", "statistic.online.plural", number_format($online));
         $stat = "<a href='" . URL("members/online") . "' class='link-membersOnline'>{$stat}</a>";
         $this->addToMenu("statistics", "statistic-online", $stat);
         $this->addToMenu("meta", "copyright", "<a href='http://esotalk.com/'>Powered by esoTalk" . (ET::$session->isAdmin() ? " " . ESOTALK_VERSION : "") . "</a>");
         // Set up some default JavaScript files and language definitions.
         $this->addJSFile("js/lib/jquery.js", true);
         $this->addJSFile("js/lib/jquery.misc.js", true);
         $this->addJSFile("js/lib/jquery.history.js", true);
         $this->addJSFile("js/lib/jquery.scrollTo.js", true);
         $this->addJSFile("js/global.js", true);
         $this->addJSLanguage("message.ajaxRequestPending", "message.ajaxDisconnected", "Loading...", "Notifications");
         $this->addJSVar("notificationCheckInterval", C("esoTalk.notificationCheckInterval"));
         // If config/custom.css contains something, add it to be included in the page.
         if (file_exists($file = PATH_CONFIG . "/custom.css") and filesize($file) > 0) {
             $this->addCSSFile("config/custom.css", true);
         }
     }
     $this->trigger("init");
 }
 /**
  * Display a list of conversations, optionally filtered by channel(s) and a search string.
  *
  * @return void
  */
 function index($channelSlug = false)
 {
     // Add the default gambits to the gambit cloud: gambit text => css class to apply.
     $gambits = array(T("gambit.active last ? hours") => "gambit-activeLastHours", T("gambit.active last ? days") => "gambit-activeLastDays", T("gambit.active today") => "gambit-activeToday", T("gambit.author:") . T("gambit.member") => "gambit-author", T("gambit.contributor:") . T("gambit.member") => "gambit-contributor", T("gambit.dead") => "gambit-dead", T("gambit.has replies") => "gambit-hasReplies", T("gambit.has >10 replies") => "gambit-replies", T("gambit.locked") => "gambit-locked", T("gambit.more results") => "gambit-more", T("gambit.order by newest") => "gambit-orderByNewest", T("gambit.order by replies") => "gambit-orderByReplies", T("gambit.random") => "gambit-random", T("gambit.reverse") => "gambit-reverse", T("gambit.sticky") => "gambit-sticky");
     // Add some more personal gambits if there is a user logged in.
     if (ET::$session->user) {
         $gambits += array(T("gambit.contributor:") . T("gambit.myself") => "gambit-contributorMyself", T("gambit.author:") . T("gambit.myself") => "gambit-authorMyself", T("gambit.draft") => "gambit-draft", T("gambit.muted") => "gambit-muted", T("gambit.private") => "gambit-private", T("gambit.starred") => "gambit-starred", T("gambit.unread") => "gambit-unread");
     }
     list($channelInfo, $currentChannels, $channelIds, $includeDescendants) = $this->getSelectedChannels($channelSlug);
     // Now we need to construct some arrays to determine which channel "tabs" to show in the view.
     // $channels is a list of channels with the same parent as the current selected channel(s).
     // $path is a breadcrumb trail to the depth of the currently selected channel(s).
     $channels = array();
     $path = array();
     // Work out what channel we will use as the "parent" channel. This will be the last item in $path,
     // and its children will be in $channels.
     $curChannel = false;
     // If channels have been selected, use the first of them.
     if (count($currentChannels)) {
         $curChannel = $channelInfo[$currentChannels[0]];
     }
     // If the currently selected channel has no children, or if we're not including descendants, use
     // its parent as the parent channel.
     if ($curChannel and $curChannel["lft"] >= $curChannel["rgt"] - 1 or !$includeDescendants) {
         $curChannel = @$channelInfo[$curChannel["parentId"]];
     }
     // If no channel is selected, make a faux parent channel.
     if (!$curChannel) {
         $curChannel = array("lft" => 0, "rgt" => PHP_INT_MAX, "depth" => -1);
     }
     // Now, finally, go through all the channels and add ancestors of the "parent" channel to the $path,
     // and direct children to the list of $channels. Make sure we don't include any channels which
     // the user has unsubscribed to.
     foreach ($channelInfo as $channel) {
         if ($channel["lft"] > $curChannel["lft"] and $channel["rgt"] < $curChannel["rgt"] and $channel["depth"] == $curChannel["depth"] + 1 and empty($channel["unsubscribed"])) {
             $channels[] = $channel;
         } elseif ($channel["lft"] <= $curChannel["lft"] and $channel["rgt"] >= $curChannel["rgt"]) {
             $path[] = $channel;
         }
     }
     // Store the currently selected channel in the session, so that it can be automatically selected
     // if "New conversation" is clicked.
     if (!empty($currentChannels)) {
         ET::$session->store("channelId", $currentChannels[0]);
     }
     // Get the search string request value.
     $searchString = R("search");
     // Last, but definitely not least... perform the search!
     $search = ET::searchModel();
     $conversationIDs = $search->getConversationIDs($channelIds, $searchString, count($currentChannels));
     $results = $search->getResults($conversationIDs);
     // Were there any errors? Show them as messages.
     if ($search->errorCount()) {
         $this->messages($search->errors(), "warning dismissable");
     } else {
         $words = array();
         foreach ($search->fulltext as $term) {
             if (preg_match_all('/"(.+?)"/', $term, $matches)) {
                 $words[] = $matches[1];
                 $term = preg_replace('/".+?"/', '', $term);
             }
             $words = array_unique(array_merge($words, explode(" ", $term)));
         }
         ET::$session->store("highlight", $words);
     }
     // Pass on a bunch of data to the view.
     $this->data("results", $results);
     $this->data("showViewMoreLink", $search->areMoreResults());
     $this->data("channelPath", $path);
     $this->data("channelTabs", $channels);
     $this->data("currentChannels", $currentChannels);
     $this->data("channelInfo", $channelInfo);
     $this->data("channelSlug", $channelSlug ? $channelSlug : "all");
     $this->data("searchString", $searchString);
     $this->data("fulltextString", implode(" ", $search->fulltext));
     $this->data("gambits", $gambits);
     // If we're loading the page in full...
     if ($this->responseType === RESPONSE_TYPE_DEFAULT) {
         // Update the user's last action.
         ET::memberModel()->updateLastAction("search");
         // Construct a canonical URL and add to the breadcrumb stack.
         $slugs = array();
         foreach ($currentChannels as $channel) {
             $slugs[] = $channelInfo[$channel]["slug"];
         }
         $url = "conversations/" . urlencode(($k = implode(" ", $slugs)) ? $k : "all") . ($searchString ? "?search=" . urlencode($searchString) : "");
         $this->pushNavigation("conversations", "search", URL($url));
         $this->canonicalURL = URL($url, true);
         // Add a link to the RSS feed in the bar.
         $this->addToMenu("meta", "feed", "<a href='" . URL(str_replace("conversations/", "conversations/index.atom/", $url)) . "' id='feed'>" . T("Feed") . "</a>");
         // Construct a list of keywords to use in the meta tags.
         $keywords = array();
         foreach ($channelInfo as $c) {
             if ($c["depth"] == 0) {
                 $keywords[] = strtolower($c["title"]);
             }
         }
         // Add meta tags to the header.
         $this->addToHead("<meta name='keywords' content='" . sanitizeHTML(($k = C("esoTalk.meta.keywords")) ? $k : implode(",", $keywords)) . "'>");
         list($lastKeyword) = array_splice($keywords, count($keywords) - 1, 1);
         $this->addToHead("<meta name='description' content='" . sanitizeHTML(($d = C("esoTalk.meta.description")) ? $d : sprintf(T("forumDescription"), C("esoTalk.forumTitle"), implode(", ", $keywords), $lastKeyword)) . "'>");
         // If this is not technically the homepage (if it's a search page) the we don't want it to be indexed.
         if ($searchString) {
             $this->addToHead("<meta name='robots' content='noindex, noarchive'>");
         }
         // Add JavaScript language definitions and variables.
         $this->addJSLanguage("Starred", "Unstarred", "gambit.member", "gambit.more results", "Filter conversations", "Jump to last");
         $this->addJSVar("searchUpdateInterval", C("esoTalk.search.updateInterval"));
         $this->addJSVar("currentSearch", $searchString);
         $this->addJSVar("currentChannels", $currentChannels);
         $this->addJSFile("js/lib/jquery.cookie.js");
         $this->addJSFile("js/autocomplete.js");
         $this->addJSFile("js/search.js");
         // Add an array of channels in the form slug => id for the JavaScript to use.
         $channels = array();
         foreach ($channelInfo as $id => $c) {
             $channels[$id] = $c["slug"];
         }
         $this->addJSVar("channels", $channels);
         // Get a bunch of statistics...
         $queries = array("post" => ET::SQL()->select("COUNT(*)")->from("post")->get(), "conversation" => ET::SQL()->select("COUNT(*)")->from("conversation")->get(), "member" => ET::SQL()->select("COUNT(*)")->from("member")->get());
         $sql = ET::SQL();
         foreach ($queries as $k => $query) {
             $sql->select("({$query}) AS {$k}");
         }
         $stats = $sql->exec()->firstRow();
         // ...and show them in the footer.
         foreach ($stats as $k => $v) {
             $stat = Ts("statistic.{$k}", "statistic.{$k}.plural", number_format($v));
             if ($k == "member") {
                 $stat = "<a href='" . URL("members") . "'>{$stat}</a>";
             }
             $this->addToMenu("statistics", "statistic-{$k}", $stat, array("before" => "statistic-online"));
         }
         $this->render("conversations/index");
     } elseif ($this->responseType === RESPONSE_TYPE_VIEW) {
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_AJAX) {
         $this->json("channels", $this->getViewContents("channels/tabs", $this->data));
         $this->render("conversations/results");
     } elseif ($this->responseType === RESPONSE_TYPE_JSON) {
         $this->json("results", $results);
         $this->render();
     }
 }
// If this conversation is stickied, output an excerpt from its first post.
if ($conversation["sticky"]) {
    echo "<div class='excerpt'>" . ET::formatter()->init($conversation["firstPost"])->inline(true)->firstLine()->clip(200)->format()->get() . "</div>";
}
?>
</div>
<div class='col-channel'><?php 
$channel = $data["channelInfo"][$conversation["channelId"]];
echo "<a href='" . URL(searchURL("", $channel["slug"])) . "' class='channel channel-{$conversation["channelId"]}' data-channel='{$channel["slug"]}'>{$channel["title"]}</a>";
?>
</div>
<div class='col-replies'>
<i class='icon-comment<?php 
if (!$conversation["replies"]) {
    echo "-alt";
}
?>
'></i>
<?php 
echo "<span>" . Ts("%s reply", "%s replies", $conversation["replies"]) . "</span>";
// Output an "unread indicator", showing the number of unread posts.
if (ET::$session->user and $conversation["unread"]) {
    echo " <a href='" . URL("conversation/markAsRead/" . $conversation["conversationId"] . "?token=" . ET::$session->token . "&return=" . urlencode(ET::$controller->selfURL)) . "' class='unreadIndicator' title='" . T("Mark as read") . "'>" . Ts("%s new", "%s new", $conversation["unread"]) . "</a> ";
}
?>
</div>
<div class='col-lastPost'><?php 
echo "<span class='action'>" . avatar(array("memberId" => $conversation["lastPostMemberId"], "avatarFormat" => $conversation["lastPostMemberAvatarFormat"], "email" => $conversation["lastPostMemberEmail"]), "thumb"), " ", sprintf(T("%s posted %s"), "<span class='lastPostMember name'>" . memberLink($conversation["lastPostMemberId"], $conversation["lastPostMember"]) . "</span>", "<a href='" . URL($conversationURL . "/unread") . "' class='lastPostTime'>" . relativeTime($conversation["lastPostTime"], true) . "</a>"), "</span>";
?>
</div>
</li>