Search
Introduction
One of esoTalk's most powerful features is its search system. The search system makes use of a concept called "gambits". A gambit starts with a hash (#
) and represents an independent piece of search criteria — for example, the #sticky
gambit means "only match conversations that are sticky." The search string can contain multiple gambits, separated by plus characters (+
).
The ETSearchModel
class is responsible for deconstructing a search string into individual gambits, and then performing a search for conversations based on those gambits' criteria. Searches are performed by the following steps:
- The
getConversationIDs
method is called, passing a list of channel IDs to show results from and a search string. - The search string is parsed and split into terms. When a term is matched to a gambit, the gambit's callback function is called.
- Gambit callback functions add "conversation ID filters" to narrow the range of conversations being searched, or may alter other parts of the search query.
- Using the applied ID filters, a final list of conversation IDs is retrieved and returned.
- The
getResults
method can then be called with this list, and full details are retireved for each of the conversations.
Confusing? Let's break it down using an example search string that means "find sticky conversations that I've contributed to":
#sticky + #contributor:Toby
The search model's getConversationIDs
method will be called by the conversations controller. The search string will be parsed and split into two gambits: sticky
and contributor:Toby
. (The preceeding hash is discarded.) It will match these to two default gambits that are registered with the search model which have the callback functions gambitSticky
and gambitContributor
.
In gambitSticky
, we simply add a WHERE condition to the search model's SQL query object.
public static function gambitSticky(&$search, $term, $negate)
{
$search->sql->where("sticky=".($negate ? "0" : "1"));
}
gambitContributor
is a bit more complex. First, we parse the contributor:Toby
term and find the ID of the member with the name "Toby". Then, we need to add an "ID filter". An ID filter is an independent SQL query which SELECTs a list of conversation IDs that match a certain criteria. In this case, we want our ID filter query to select the conversation IDs of all posts by this member.
public static function gambitContributor(&$search, $term, $negate)
{
// Get the name of the member.
$term = trim(substr($term, strlen(T("gambit.contributor:"))));
// If the user is referring to themselves, then we already have their member ID.
if ($term == T("gambit.myself")) $q = (int)ET::$session->userId;
// Otherwise, make a query to find the member ID of the specified member name.
else $q = ET::SQL()->select("memberId")->from("member")->where("username=:username")->bind(":username", $term)->get();
// Apply the ID filter.
$sql = ET::SQL()
->select("DISTINCT conversationId")
->from("post")
->where("memberId IN ($q)");
$search->addIDFilter($sql, $negate);
}
Once all of the search terms have been parsed and their gambit callback functions have been executed, the search model will progressively run all of the ID filter queries to narrow down a list of matching conversation IDs. Finally, the results will be retrieved from search model's SQL query object (which is where the sticky
gambit was applied.)
Gambits
Gambits can be registered using the search model's static addGambit
function. This function accepts two arguments:
- A condition to run through
eval()
to determine a match.$term
represents the search term, in lowercase, in theeval()
context. The condition should return a boolean value:true
means a match,false
means no match. Example:return $term == "sticky";
- The function to call if the gambit is matched. The function will be called with the parameters
callback($search, $term, $negate)
.
Within the callback, the SQL query object on the search model ($search->sql
) may be manipulated, or ID filters may be added using the $search->addIDFilter
method. For more information, see the API Reference.
Registering A Gambit
public function init()
{
ET::define("gambit.bookmarked", "bookmarked");
}
// Register the "bookmarked" gambit with the search model.
public function handler_conversationsController_init($sender)
{
ET::searchModel(); // Load the search model so we can use the static class
ETSearchModel::addGambit('return $term == strtolower(T("gambit.bookmarked"));', array($this, "gambitBookmarked"));
}
public static function gambitBookmarked(&$search, $term, $negate)
{
if (!ET::$session->user or $negate) return;
$sql = ET::SQL()
->select("DISTINCT conversationId")
->from("member_conversation")
->where("type='member'")
->where("id=:memberId")
->where("bookmarked=1")
->bind(":memberId", ET::$session->userId);
$search->addIDFilter($sql);
}
Aliases
Aliases can be created to simplify the use of complex gambits. For example, the dead
gambit is actually just an alias for active >30 day
.
Registering An Alias
public function handler_conversationsController_init($sender)
{
ET::searchModel(); // Load the search model so we can use the static class
ETSearchModel::addAlias("popular", "has >50 replies");
}
Gambits Menu
To make gambits usable, they should be added to the gambits menu that appears when the search field gains focus. The gambit menu is constructed by the conversations controller, not the search model, as it is part of the user interface. It can be altered by handling the constructGambitsMenu
event.
The gambits menu is structured as a multi-dimensional array rather than a traditional menu object; in this way, gambits can be grouped into categories according to the type of criteria they represent. There are a few gambit categories by default: main
, time
, member
, replies
, and misc
. You can add gambits to any one of these categories, or create your own.
Each key-value pair within a category represents a gambit. The key is the gambit; the value is an array containing a CSS class and an icon to display the gambit with.
Adding A Gambit To The Menu
public function handler_conversationsController_constructGambitsMenu($controller, &$gambits)
{
addToArrayString($gambits["main"], T("gambit.bookmarked"), array("gambit-bookmarked", "icon-bookmark"));
}