http://wiki.geeklog.net/api.php?action=feedcontributions&user=Sbarakat&feedformat=atomGeeklogWiki - User contributions [en]2024-03-28T22:09:38ZUser contributionsMediaWiki 1.27.5http://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5603Using Geeklog's Improved Search Engine2009-10-25T23:40:36Z<p>Sbarakat: /* Including External Results */ Documented callback function</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== Introduction ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search engine, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Over the next few sections we will go over these functions in details.<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
=== Returned Values ===<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
===== URL =====<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Search by Title =====<br />
<br />
The buildSearchSQL() function has another trick up its sleeve that allows the user to perform a search only on the title of an item. For it to work you must identify, by an associative key, the title column. The function will then take care of the rest.<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title' => 'title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
</pre><br />
<br />
==== Enabling URL Rewrite ====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== Identifying A Plugin's Comments ====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
==== Including External Results ====<br />
<br />
Got some other way of searching your plugin that does not fit into the current search API? Well there is now a way to include those results as well. By using the setResults() function of the SearchCriteria() class you can provide several items that will be shown along side the other plugins on the search results page. The setResults() function takes an array parameter that contains multiple associative arrays each defining a single result. <br />
<br />
The setResults() function is only available as of Geeklog 1.6.1. To lower the plugin's requirements to 1.6.0 it would be best to check the function exists before executing it.<br />
<br />
Below is an example of the function's use<br />
<pre><br />
$search = new SearchCriteria('myplugin', 'My Plugin');<br />
...snip...<br />
$search->setResults(<br />
array(<br />
array(<br />
LF_SOURCE_NAME => 'myplugin',<br />
LF_SOURCE_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 1',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => 'http://www.example.com/custom1',<br />
'hits' => 20000,<br />
'uid' => 2<br />
),<br />
array(<br />
LF_SOURCE_NAME => 'myplugin',<br />
LF_SOURCE_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 2',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => '/internal/page/custom2',<br />
'hits' => 20000,<br />
'uid' => 2<br />
)<br />
)<br />
);<br />
<br />
return $search;<br />
</pre><br />
<br />
An alternative is to use a callback function. By using a callback function any plugin can add results to the search results page using any method they like. Just be sure to use the same column names.<br />
<pre><br />
function plugin_dopluginsearch_myplugin( ... )<br />
{<br />
$search_cus = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_cus->setCallback('myplugin_SearchCallback');<br />
<br />
// If the callback function is in a class use this instead<br />
//$search_cus->setCallback(array($this, 'myplugin_SearchCallback'));<br />
<br />
$search_cus->setTotal(100);<br />
$search_cus->setRank(5);<br />
}<br />
<br />
function myplugin_SearchCallback( $offset, $limit )<br />
{<br />
$myrows = array();<br />
for ($i = $offset; $i < $offset + $limit; $i++)<br />
{<br />
$row['title'] = "This is custom result " . ($i + 1);<br />
$row['description'] = "The passed parameters are \$offset=$offset, \$limit=$limit... that is all";<br />
$row['uid'] = 2;<br />
$row['date'] = '1256058000'; // Unix timestamp<br />
$row['hits'] = 5000 * $limit;<br />
$row['url'] = "/internal/page/custom" . ($i + 1);<br />
<br />
$myrows[] = $row;<br />
}<br />
return $myrows;<br />
}<br />
</pre><br />
<br />
== A Working Example ==<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching, note we have marked the title column<br />
$columns = array('title' => 'title','introtext','bodytext');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Search_Engine&diff=5597Using Geeklog's Search Engine2009-10-20T21:40:20Z<p>Sbarakat: Placed new API at the top</p>
<hr />
<div>== New Search API (GL v1.6.x or greater) ==<br />
As of Geeklog v1.6 the core search functionality has been changed which has required updated API methods. The new API makes use of the new and improved '''SearchCriteria()''' class. See [[Using Geeklog's Improved Search Engine]].<br />
<br />
== Old Search API (GL v1.5.x or below) ==<br />
The methods documented on this page are meant for Geeklog v1.5 and below. They make use of the Plugin() class which will eventually be phased out.<br />
<br />
===The functions===<br />
Tying your plugin into Geeklogs search API is rather easy. Easy, that is, if you know how search your plugins data already. The Geeklog search API provides a way for you to return your search results back to Geeklog to be included in its search results page.<br />
<br />
For the data in your plugin to be searched by Geeklogs search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the actual results to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
==== plugin_searchtypes_{plugin_name}() ====<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this<br />
function will make it all clear. Normally the function looks like this:<br />
<br />
<pre><br />
function plugin_searchtypes_{plugin_name}() {<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}<br />
</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your plugin and the LANGUAGE varible $LANG_PL00 is just an example. Your Plugin needs to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
==== plugin_dopluginsearch_{plugin_name}() ====<br />
<br />
This function is where the search is actually done. It is passed a number of parameters that can be used in your search. They are:<br />
<br />
* $query -- the actual items being searched for.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- type of item (see searchtypes above).<br />
* $author -- author of item.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. Here is a brief overview of what your function should do.<br />
<br />
# Initialize plugin object<br />
# Check to see if type is appropriate for this plugin -- if not, bail out.<br />
# Get a list of all your items to search.<br />
# Create Search object and Build search results header.<br />
# Search each of your items in turn.<br />
# If a match then add item to the results.<br />
# When done set number of results and return search object<br />
<br />
Looking at the example code should make all of this clear. The search results are returned in a object of type Plugin. You initialize the object by setting the Label and then setting the names of the columns you want returned. This is done in typical object fashion with this construct: $plugin->addSearch Heading. <br />
<br />
The headings will vary according to what your plugin does, but should include a link to the item found. After searching each of your items in turn; you add your search results to an array having the same number of items as the labels you set earlier. <br />
<br />
Note: your search should be case insensitive to be compatible with Geeklogs search. This array will correspond to the column headings entered above. In the example below, the array consists of the Title of the page, the url to the page (a link) and the number of hits. This array is then added to the Plugin object as a row. <br />
<br />
The following construct is used: $plugin->addSearchResult. <br />
When you are done searching you set the number of rows found and the number searched to the plugin object and return it. The example below is from the External Pages Plugin.<br />
<br />
<pre><br />
function plugin_dopluginsearch_external($query,$datestart,$dateend,$topic,$type,$author)<br />
{<br />
global $_TABLES, $_CONF, $LANG_EX00;<br />
<br />
if (empty($type)) {<br />
$type = 'all';<br />
}<br />
<br />
// Bail if we aren't supppose to do our search<br />
if ($type &lt;&gt; 'all' AND $type &lt;&gt; 'external') {<br />
$plugin_results = new Plugin();<br />
$plugin_results-&gt;plugin_name = 'external';<br />
$plugin_results-&gt;searchlabel = $LANG_EX00['externpages'] . $LANG_EX00['results'];<br />
return $plugin_results;<br />
}<br />
<br />
// Build search SQL - Modified to exclude static PHP pages from search.<br />
$sql = &quot;SELECT * from &quot; . $_TABLES['external'];<br />
$result = DB_query($sql);<br />
<br />
// OK, now create new plugin object and insert table header labels<br />
require_once($_CONF['path_system'] . 'classes/plugin.class.php');<br />
$plugin_results = new Plugin();<br />
$plugin_results-&gt;plugin_name = 'external';<br />
$plugin_results-&gt;searchlabel = $LANG_EX00['externpages'] . $LANG_EX00['results'];<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['titlemsg']);<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['urlmsg']);<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['hitsmsg']);<br />
$mycount = DB_numRows($result);<br />
// NOTE if any of your data items need to be links then add them here! <br />
// make sure data elements are in an array and in the same order as your<br />
// headings above!<br />
for ($i = 1; $i <= $mycount; $i++) {<br />
$A = DB_fetchArray($result);<br />
<br />
if(SEC_hasAccess($A[owner_id],$A[group_id],$A[perm_owner],$A[perm_group],$A[perm_members],$A[perm_anon])){<br />
if (preg_match("/^(http:\/\/)/i",$A['url']) == 1) {<br />
$pth = $A['url'];<br />
$url = $A['url'];<br />
} else {<br />
$pth = $_CONF['path_html'] . $A['url'];<br />
$url = $_CONF['site_url'] . '/' . $A['url'];<br />
}<br />
$cnts = implode('',file($pth));<br />
if (stristr($cnts,$query) != '') {<br />
$rcnt++;<br />
$A['title'] = stripslashes($A['title']);<br />
$row = array($A['title'],<br />
'<a href="' . $url . '">' . $A['url'] . "</a>",<br />
$A['hits']);<br />
$plugin_results->addSearchResult($row);<br />
}<br />
}<br />
<br />
}<br />
$plugin_results->num_searchresults = $rcnt;<br />
$plugin_results->num_itemssearched = DB_count($_TABLES['external']);<br />
<br />
return $plugin_results;<br />
}<br />
</pre><br />
<br />
[[Category:Plugin Developers Handbook]] [[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5596Using Geeklog's Improved Search Engine2009-10-20T21:21:29Z<p>Sbarakat: /* Search by Title */</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== Introduction ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search engine, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Over the next few sections we will go over these functions in details.<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
=== Returned Values ===<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
===== URL =====<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Search by Title =====<br />
<br />
The buildSearchSQL() function has another trick up its sleeve that allows the user to perform a search only on the title of an item. For it to work you must identify, by an associative key, the title column. The function will then take care of the rest.<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title' => 'title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
</pre><br />
<br />
==== Enabling URL Rewrite ====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== Identifying A Plugin's Comments ====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
==== Including External Results ====<br />
<br />
Got some other way of searching your plugin that does not fit into the current search API? Well there is now a way to include those results as well. By using the setResults() function of the SearchCriteria() class you can provide several items that will be shown along side the other plugins on the search results page. The setResults() function takes an array parameter that contains multiple associative arrays each defining a single result. <br />
<br />
The setResults() function is only available as of Geeklog 1.6.1. To lower the plugin's requirements to 1.6.0 it would be best to check the function exists before executing it.<br />
<br />
Below is an example of the function's use<br />
<pre><br />
$search = new SearchCriteria('myplugin', 'My Plugin');<br />
...snip...<br />
$search->setResults(<br />
array(<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 1',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => 'http://www.example.com/custom1',<br />
'hits' => 20000,<br />
'uid' => 2<br />
),<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 2',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => '/internal/page/custom2',<br />
'hits' => 20000,<br />
'uid' => 2<br />
)<br />
)<br />
);<br />
<br />
return $search;<br />
</pre><br />
<br />
== A Working Example ==<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching, note we have marked the title column<br />
$columns = array('title' => 'title','introtext','bodytext');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5595Using Geeklog's Improved Search Engine2009-10-20T21:20:47Z<p>Sbarakat: Documented search by title</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== Introduction ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search engine, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Over the next few sections we will go over these functions in details.<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
=== Returned Values ===<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
===== URL =====<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Search by Title =====<br />
<br />
The buildSearchSQL() function has another trick up its sleeve that allows the user to perform a search only on the title of an item. For it to work you must identify, by an associative key, the title column. The function will then take care of the rest.<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title' => 'title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
</pre><br />
<br />
==== Enabling URL Rewrite ====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== Identifying A Plugin's Comments ====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
==== Including External Results ====<br />
<br />
Got some other way of searching your plugin that does not fit into the current search API? Well there is now a way to include those results as well. By using the setResults() function of the SearchCriteria() class you can provide several items that will be shown along side the other plugins on the search results page. The setResults() function takes an array parameter that contains multiple associative arrays each defining a single result. <br />
<br />
The setResults() function is only available as of Geeklog 1.6.1. To lower the plugin's requirements to 1.6.0 it would be best to check the function exists before executing it.<br />
<br />
Below is an example of the function's use<br />
<pre><br />
$search = new SearchCriteria('myplugin', 'My Plugin');<br />
...snip...<br />
$search->setResults(<br />
array(<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 1',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => 'http://www.example.com/custom1',<br />
'hits' => 20000,<br />
'uid' => 2<br />
),<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 2',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => '/internal/page/custom2',<br />
'hits' => 20000,<br />
'uid' => 2<br />
)<br />
)<br />
);<br />
<br />
return $search;<br />
</pre><br />
<br />
== A Working Example ==<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching, note we have marked the title column<br />
$columns = array('title' => 'title','introtext','bodytext');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5594Using Geeklog's Improved Search Engine2009-10-20T21:02:56Z<p>Sbarakat: Added Including External Results</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== Introduction ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search engine, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Over the next few sections we will go over these functions in details.<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
=== Returned Values ===<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
===== URL =====<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
==== Enabling URL Rewrite ====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== Identifying A Plugin's Comments ====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
==== Including External Results ====<br />
<br />
Got some other way of searching your plugin that does not fit into the current search API? Well there is now a way to include those results as well. By using the setResults() function of the SearchCriteria() class you can provide several items that will be shown along side the other plugins on the search results page. The setResults() function takes an array parameter that contains multiple associative arrays each defining a single result. <br />
<br />
The setResults() function is only available as of Geeklog 1.6.1. To lower the plugin's requirements to 1.6.0 it would be best to check the function exists before executing it.<br />
<br />
Below is an example of the function's use<br />
<pre><br />
$search = new SearchCriteria('myplugin', 'My Plugin');<br />
...snip...<br />
$search->setResults(<br />
array(<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 1',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => 'http://www.example.com/custom1',<br />
'hits' => 20000,<br />
'uid' => 2<br />
),<br />
array(<br />
SQL_NAME => 'myplugin',<br />
SQL_TITLE => 'My Plugin',<br />
'title' => 'Custom Result 2',<br />
'description' => 'This is a custom result',<br />
'date' => '1256058000',<br />
'url' => '/internal/page/custom2',<br />
'hits' => 20000,<br />
'uid' => 2<br />
)<br />
)<br />
);<br />
<br />
return $search;<br />
</pre><br />
<br />
== A Working Example ==<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5593Using Geeklog's Improved Search Engine2009-10-20T20:14:56Z<p>Sbarakat: Little shuffling of the sections</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== Introduction ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search engine, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Over the next few sections we will go over these functions in details.<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
=== Returned Values ===<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
===== URL =====<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
==== Enabling URL Rewrite ====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== Identifying A Plugin's Comments ====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
== A Working Example ==<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5592Using Geeklog's Improved Search Engine2009-10-20T20:06:16Z<p>Sbarakat: /* Identifying A Plugin's Comments */</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or Static Pages. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the plugin name to 'comments'. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. This method of searching for comments was introduced in ''Geeklog 1.6.1''.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('comments', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5591Using Geeklog's Improved Search Engine2009-09-30T23:26:54Z<p>Sbarakat: /* Identifying A Plugin's Comments */ Clarified supported GL version</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or the Polls Plugin. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the setComment() function to true. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Using separate objects is the prefered method of separating a plugins comments from the main results. The setComment() function however is only available as of ''Geeklog 1.6.1''. To lower the plugin's requirements to 1.6.0 it would be best to check the function exists before executing it.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('myplugin', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setComment(true);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5590Using Geeklog's Improved Search Engine2009-09-30T23:18:11Z<p>Sbarakat: /* Identifying A Plugin's Comments */ Added supported GL version</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
''This feature is available as of Geeklog 1.6.1''<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or the Polls Plugin. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the setComment() function to true. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('myplugin', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setComment(true);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5589Using Geeklog's Improved Search Engine2009-09-30T21:14:45Z<p>Sbarakat: /* Standard SQL Query */</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or the Polls Plugin. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the setComment() function to true. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('myplugin', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setComment(true);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5588Using Geeklog's Improved Search Engine2009-09-30T21:09:41Z<p>Sbarakat: /* Returning Multiple Objects */ code typo</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...{space}</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$search_main->setSQL($sql_main);<br />
$search_main->setFTSQL($ftsql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or the Polls Plugin. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the setComment() function to true. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('myplugin', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setComment(true);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5587Using Geeklog's Improved Search Engine2009-09-30T21:08:41Z<p>Sbarakat: Added comments section</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...{space}</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$plugin_main->setSQL($sql_main);<br />
$plugin_main->setFTSQL($ftsql_main);<br />
$plugin_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
===== Identifying A Plugin's Comments =====<br />
<br />
Some plugins may have comments associated with them such as the [[File Management Plugin|FileMgmt]] or the Polls Plugin. To allow these comments to be searched the core needs to know where to find them. The process is similar to [[#Returning Multiple Objects|returning multiple SearchCriteria() objects]] with the exception of identifying the object that contains the comments. This is done by setting the setComment() function to true. As comments will be included in general searches it is best practice to set a lower rank for the comments object to ensure the search results page isn't spammed with comment results.<br />
<br />
Below is a short example.<br />
<br />
<pre><br />
// Search the main table<br />
$sql_main = "SELECT ... FROM gl_myplugin WHERE ...";<br />
$search_main = new SearchCriteria('myplugin', 'My Plugin');<br />
$search_main->setSQL($sql_main);<br />
$search_main->setRank(4);<br />
<br />
// Search the comments tables<br />
$sql_comments = "SELECT ... FROM gl_comments" WHERE ...";<br />
$search_comments = new SearchCriteria('myplugin', array('My Plugin', 'Comments'));<br />
$search_comments->setSQL($sql_comments);<br />
$search_comments->setComment(true);<br />
$search_comments->setRank(1);<br />
<br />
// Return both objects<br />
return array($search_main, $sql_comments);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5586Using Geeklog's Improved Search Engine2009-09-30T20:08:53Z<p>Sbarakat: /* Full-Text SQL Query (optional) */</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...{space}</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which are optimised to reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$plugin_main->setSQL($sql_main);<br />
$plugin_main->setFTSQL($ftsql_main);<br />
$plugin_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=5585Using Geeklog's Improved Search Engine2009-09-30T20:05:04Z<p>Sbarakat: /* URL */</p>
<hr />
<div>== Plugins' compatibility with old Geeklog versions ==<br />
<br />
If you want to make your plugin compatible with Geeklog v1.5 and below, you must also implement [[Using Geeklog's Search Engine]].<br />
<br />
== The functions ==<br />
<br />
Tying your plugin into Geeklog's search API is rather easy. Easy, that is, if you know how to search your plugin's data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
* plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
* plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
=== plugin_searchtypes_{plugin_name}() ===<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['{plugin_name}']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<sup>(don't forget to replace {plugin_name} with the actual name).</sup><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE variable $LANG_PL00 is just an example. Your plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype is returned to your plugin search routine and Description is displayed in the Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
=== plugin_dopluginsearch_{plugin_name}() ===<br />
<br />
This section will outline the method used to search a plugin's data.<br />
<br />
==== Input Parameters ====<br />
<br />
The parameters that are passed to this function are as follows:<br />
* $query -- a string containing the search term.<br />
* $datestart -- starting date to begin search.<br />
* $dateend -- ending date to end search.<br />
* $topic -- topic item is assigned to.<br />
* $type -- no longer used, deprecated.<br />
* $author -- the user id of the author.<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $page -- no longer used, deprecated.<br />
* $perpage -- no longer used, deprecated.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
# Build search SQL string using the input parameters.<br />
# Return the query along with some plugin information.<br />
<br />
==== Returned Values ====<br />
<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
===== Plugin Name =====<br />
<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
===== A Search Rank =====<br />
<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritize important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if it's not provided it will default to 3.<br />
<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
===== Standard SQL Query =====<br />
<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, uid, hits, url FROM ... WHERE ...{space}</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
====== URL ======<br />
<br />
The URL is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result will be printed, as is, in the href attribute of the link. For example this URL belongs to the Geeklog site:<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However some plugins may need to direct users to an external site, ie the Links plugin. In this instance providing a full URL will suffice.<br />
<br />
===== Full-Text SQL Query (optional) =====<br />
<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
<br />
This search method will take advantage of Full-Text indexes which will reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
==== The SearchCriteria() Class ====<br />
<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
===== buildSearchSQL() Function =====<br />
<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
* $keyType -- search key type: 'all', 'phrase', 'any'.<br />
* $query -- the query string.<br />
* $columns -- the column names to search.<br />
* $sql -- the sql query to append to. (optional)<br />
<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
===== Enabling URL Rewrite =====<br />
<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
===== Returning Multiple Objects =====<br />
<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$plugin_main->setSQL($sql_main);<br />
$plugin_main->setFTSQL($ftsql_main);<br />
$plugin_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
==== A Working Example ====<br />
<br />
This example searches the Stories. Although it's not a plugin, it puts to use everything discussed here so it's a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
For more examples, there's a [http://www.geeklog.net/forum/viewtopic.php?showtopic=87624 forum post] with updated search functions for the [[FAQ Manager Plugin|FAQ Manager]], [[Forum Plugin|Forum]], and [[File Management Plugin|File Management]] plugins.<br />
<br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=4716Using Geeklog's Improved Search Engine2008-08-17T17:18:14Z<p>Sbarakat: minor corrections</p>
<hr />
<div>Tying your plugin into Geeklogs search API is rather easy. Easy, that is, if you know how search <br />
your plugins data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
*plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
*plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this<br />
function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['searchtype']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE varible $LANG_PL00 is just an example. Your Plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype <br />
is returned to your plugin search routine and Description is displayed in the <br />
Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
The parameters that are passed to this function are as follows:<br />
*$query -- a string containing the search term.<br />
*$datestart -- starting date to begin search.<br />
*$dateend -- ending date to end search.<br />
*$topic -- topic item is assigned to.<br />
*$type -- no longer used, deprecated.<br />
*$author -- the user id of the author.<br />
*$keyType -- search key type: 'all', 'phrase', 'any'.<br />
*$page -- no longer used, deprecated.<br />
*$perpage -- no longer used, deprecated.<br />
<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
#Build search SQL string using the input parameters.<br />
#Return the query along with some plugin information.<br />
<br />
<br />
=== Returned Values ===<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritise important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if its not provided it will default to 3.<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
All SQL queries returned should be without the LIMIT and ORDER BY clauses. The SQL query must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, user, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
The url is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result with be printed, as is, in the href attribute of the link. For example this url belongs to the Geeklog site<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However the Links plugin needs to direct users to external sites when clicked, so providing the url from the database will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
This search method will take advantage of Full-Text indexes which will reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
<br />
=== The SearchCriteria() Class ===<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
As a lot of the plugins will be processing the same or similar SQL queries the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
*$keyType -- search key type: 'all', 'phrase', 'any'.<br />
*$query -- the query string.<br />
*$columns -- the column names to search.<br />
*$sql -- the sql query to append to. (optional)<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
==== Enabling URL Rewrite ====<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL query. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$plugin_main->setSQL($sql_main);<br />
$plugin_main->setFTSQL($ftsql_main);<br />
$plugin_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
<br />
=== A Working Example ===<br />
This example searches the Stories, although its not a plugin it puts to use everything discussed here so its a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Improved_Search_Engine&diff=4715Using Geeklog's Improved Search Engine2008-08-17T16:54:01Z<p>Sbarakat: </p>
<hr />
<div>Tying your plugin into Geeklogs search API is rather easy. Easy, that is, if you know how search <br />
your plugins data already. The Geeklog search API provides a way for you to return the SQL query back to Geeklog which will be executed in the core and the results included in its search page.<br />
<br />
For the data in your plugin to be searched by Geeklog's search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
*plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
*plugin_dopluginsearch_{plugin_name}() returns the search SQL query back to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this<br />
function will make it all clear. Normally the function looks like this:<br />
<br />
<pre>function plugin_searchtypes_{plugin_name}()<br />
{<br />
global $LANG_PL00;<br />
$tmp['searchtype']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}</pre><br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE varible $LANG_PL00 is just an example. Your Plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype <br />
is returned to your plugin search routine and Description is displayed in the <br />
Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
This section will outline the method used to search a plugin's data.<br />
<br />
=== Input Parameters ===<br />
The parameters that are passed to this function are as follows:<br />
*$query -- a string containing the search term.<br />
*$datestart -- starting date to begin search.<br />
*$dateend -- ending date to end search.<br />
*$topic -- topic item is assigned to.<br />
*$type -- no longer used, deprecated.<br />
*$author -- the user id of the author.<br />
*$keyType -- search key type: 'all', 'phrase', 'any'.<br />
*$page -- no longer used, deprecated.<br />
*$perpage -- no longer used, deprecated.<br />
<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. The function should then perform two basic operations:<br />
#Build search SQL string using the input parameters.<br />
#Return the query along with some plugin information.<br />
<br />
<br />
=== Returned Values ===<br />
The search API requires the following information to be returned by the plugin_dopluginsearch_{plugin_name}() function:<br />
<br />
==== Plugin Name ====<br />
* A name to display to the user, i.e. 'My Plugin' (singular preferred)<br />
* A name used locally to identify the plugin, i.e. 'myplugin'<br />
<br />
==== A Search Rank ====<br />
* An integer between 1 and 5 based on how many result to extract from the query.<br />
As all the results have been combined into a single list, the core needs to prioritise important plugins to non important ones. The higher the ranking the more results will be displayed to the user. A rank of 1 will show fewer results, where as a rank of 5 shows the most amount of results from the plugin. The rank is optional, if its not provided it will default to 3.<br />
Here is an example of Geeklog setup with three plugins, the different rank values will allow varying results from each plugin. The results page is set to display a total of 50 results.<br />
plugin rank results<br />
---------------------------<br />
stories 5 23<br />
forums 4 18<br />
comments 2 9<br />
<br />
==== Standard SQL Query ====<br />
* A string containing a single query.<br />
* OR An array of two queries for both MSSQL and MySQL DBMS.<br />
All SQL statements returned should be without the LIMIT and ORDER BY clauses. The SQL statement must have the following column names and look like:<br />
<pre>SELECT id, title, description, date, user, hits, url FROM ... WHERE ...</pre><br />
If a column does not exist in the table then another value should be substituted, for example: '0' AS hits, ...<br />
<br />
The url is where to take the user when they have clicked a result. It should start with a single slash if the target is within the current domain. Otherwise the result with be printed, as is, in the href attribute of the link. For example this url belongs to the Geeklog site<br />
<pre>CONCAT('/staticpages/index.php?page=', sp.sp_id) AS url</pre><br />
However the Links plugin needs to direct users to external sites when clicked, so providing the url from the database will suffice.<br />
<br />
==== Full-Text SQL Query (optional) ====<br />
* An array of two queries for both MSSQL and MySQL DBMS using the Full-Text search method.<br />
This search method will take advantage of Full-Text indexes which will reduce search times. This should only be returned if the columns being searched have been properly indexed. The Full-Text SQL Query will only be executed if Full-Text searches have been enabled by the admin during the installation or upgrading process, otherwise the core will always fall back to the Standard SQL Query.<br />
<br />
=== The SearchCriteria() Class ===<br />
The process of returning the values is done by initializing a SearchCriteria() object, setting the parameters for the search then returning the object. Here is an example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setFTSQL($ftsql);<br />
$search->setRank(4);</pre><br />
This indicates that the 'myplugin' plugin supports Full-Text searches and will have a ranking of 4.<br />
<br />
==== buildSearchSQL() Function ====<br />
As a lot of the plugins will be processing the same or similar SQL statements the buildSearchSQL() function has been provided to simplify things. This function will take four parameters then build the searching part of the SQL query. It will also return the Full-Text query strings should they be required. The parameters are:<br />
*$keyType -- search key type: 'all', 'phrase', 'any'.<br />
*$query -- the query string.<br />
*$columns -- the column names to search.<br />
*$sql -- the sql query to append to. (optional)<br />
And an example of how it can be put to use:<br />
<pre><br />
$sql = 'SELECT ... FROM ... WHERE ... ';<br />
$columns = array('title','bodytext');<br />
list($sql,$ftsql) = buildSearchSQL('any', 'my geeklog', $columns, $sql);<br />
<br />
// $sql => SELECT ... FROM ... WHERE ... AND ((title LIKE '%my%' OR bodytext LIKE '%my%') OR (title LIKE '%geeklog%' OR bodytext LIKE '%geeklog%'))<br />
// $ftsql[mysql] => SELECT ... FROM ... WHERE ... AND MATCH(title,bodytext) AGAINST ('my geeklog' IN BOOLEAN MODE)<br />
// $ftsql[mssql] => SELECT ... FROM ... WHERE ... AND CONTAINS((title,bodytext), '"my" OR "geeklog"')<br />
</pre><br />
A proper example of its use is at the bottom of the page.<br />
<br />
==== Enabling URL Rewrite ====<br />
If the plugin requires the URL to be passed through the COM_buildUrl() function then it should set the setURLRewrite() function to true. For example:<br />
<pre>$search = new SearchCriteria('myplugin', 'My Plugin');<br />
$search->setSQL($sql);<br />
$search->setURLRewrite(true);</pre><br />
<br />
==== Returning Multiple Objects ====<br />
In some cases a plugin may be required to search across two or more tables that have no relation with one another. To accommodate this they may return an array of SearchCriteria() objects, each with a different SQL statement. To allow the user to differentiate between the results an array of names can be passed, each name will be a separate sub group and will be appended to one another using the separator from the configuration.<br />
The following example shows how this works:<br />
<pre><br />
// Search the main table<br />
// These results will be labelled 'My Plugin > Main'<br />
$search_main = new SearchCriteria('myplugin', array('My Plugin', 'Main'));<br />
$plugin_main->setSQL($sql_main);<br />
$plugin_main->setFTSQL($ftsql_main);<br />
$plugin_main->setRank(4);<br />
<br />
// Search a sub tables<br />
// These results will be labelled 'My Plugin > Sub'<br />
$search_sub = new SearchCriteria('myplugin', array('My Plugin', 'Sub'));<br />
$search_sub->setSQL($sql_sub);<br />
$search_sub->setFTSQL($ftsql_sub);<br />
$search_sub->setRank(4);<br />
<br />
// Return both objects<br />
return array($search_main, $search_sub);<br />
</pre><br />
Note: The plugin identifier needs to stay the same across all objects, in this case 'myplugin'.<br />
<br />
=== A Working Example ===<br />
This example searches the Stories, although its not a plugin it puts to use everything discussed here so its a good example of how to implement the API in your plugin.<br />
<pre>function plugin_dopluginsearch_searchStories($query, $datestart, $dateend, $topic, $type, $author, $keyType, $page, $perpage)<br />
{<br />
global $_TABLES, $_DB_dbms, $LANG09;<br />
<br />
// Make sure the query is SQL safe<br />
$query = trim(addslashes($query));<br />
<br />
// Build the first part of the query<br />
$sql = "SELECT<br />
s.sid AS id,<br />
s.title AS title,<br />
s.introtext AS description,<br />
UNIX_TIMESTAMP(s.date) AS date,<br />
s.uid AS uid,<br />
s.hits AS hits,<br />
CONCAT('/article.php?story=',s.sid) AS url ";<br />
$sql .= "FROM {$_TABLES['stories']} AS s, {$_TABLES['users']} AS u ";<br />
$sql .= "WHERE (draft_flag = 0) AND (date <= NOW()) AND (u.uid = s.uid) ";<br />
$sql .= COM_getPermSQL('AND') . COM_getTopicSQL('AND') . COM_getLangSQL('sid', 'AND') . ' ';<br />
<br />
// If we are searching by date add that too<br />
if (!empty($datestart) && !empty($dateend)) {<br />
$delim = substr($datestart, 4, 1);<br />
if (!empty($delim)) {<br />
$DS = explode($delim, $datestart);<br />
$DE = explode($delim, $dateend);<br />
$startdate = mktime(0,0,0,$DS[1],$DS[2],$DS[0]);<br />
$enddate = mktime(23,59,59,$DE[1],$DE[2],$DE[0]);<br />
$sql .= "AND (UNIX_TIMESTAMP(date) BETWEEN '$startdate' AND '$enddate') ";<br />
}<br />
}<br />
if (!empty($topic)) {<br />
$sql .= "AND (s.tid = '$topic') ";<br />
}<br />
if (!empty($author)) {<br />
$sql .= "AND (s.uid = '$author') ";<br />
}<br />
<br />
// Create a SearchCriteria instance with the name of the plugin<br />
$search = new SearchCriteria('stories', $LANG09[65]);<br />
<br />
// These are the columns in the table that need searching<br />
$columns = array('introtext','bodytext','title');<br />
<br />
// Get back the completed SQL query<br />
list($sql,$ftsql) = $search->buildSearchSQL($keyType, $query, $columns, $sql);<br />
<br />
// Set the Std. SQL Query<br />
$search->setSQL($sql);<br />
<br />
// Set the Full-Text Query, remember the columns _MUST_ be indexed first. If they are not then don't set this.<br />
$search->setFTSQL($ftsql);<br />
<br />
// Finally set a high ranking, enable URLRewrite and return the object.<br />
$search->setRank(5);<br />
$search->setURLRewrite(true);<br />
return $search;<br />
}</pre><br />
<br />
[[Category:Plugin Development]]</div>Sbarakathttp://wiki.geeklog.net/index.php?title=Using_Geeklog%27s_Search_Engine&diff=4714Using Geeklog's Search Engine2008-08-17T16:53:53Z<p>Sbarakat: </p>
<hr />
<div>The information on this page refers to the old search API which makes use of the Plugin() class. There has since been improvements made to the search pages that requires an updated API which makes use of the new and improved [[Using Geeklog's Improved Search Engine|SearchCriteria()]] class.<br />
<br />
<br />
<br />
<br />
Tying your plugin into Geeklogs search API is rather easy. Easy, that is, if you know how search <br />
your plugins data already. The Geeklog search API provides a way for you to return your search<br />
results back to Geeklog to be included in its search results page.<br />
<br />
For the data in your plugin to be searched by Geeklogs search functions, there are two functions that you need to implement in your plugins function.inc:<br />
<br />
*plugin_searchtypes_{plugin_name}() returns to Geeklog the type(s) of search.<br />
*plugin_dopluginsearch_{plugin_name}() returns the actual results to Geeklog.<br />
<br />
Let's look at each of them in turn:<br />
<br />
<br />
== plugin_searchtypes_{plugin_name}() ==<br />
<br />
<br />
This function takes no parameters and returns an associative array of the search type. The normal code for this<br />
function will make it all clear. Normally the function looks like this:<br />
<br />
function plugin_searchtypes_{plugin_name}() {<br />
global $LANG_PL00;<br />
$tmp['searchtype']= $LANG_PL00['searchtype'];<br />
return $tmp;<br />
}<br />
<br />
Naturally all occurrences of plugin would be replaced with the name of your <br />
plugin and the LANGUAGE varible $LANG_PL00 is just an example. Your Plugin needs <br />
to use a unique variable name so replace PL00.<br />
<br />
The return array is of the format array['searchtype'] = 'Search Description', where searchtype <br />
is returned to your plugin search routine and Description is displayed in the <br />
Search Page Drop Down box.<br />
<br />
You can have multiple search types and descriptions.<br />
<br />
<br />
== plugin_dopluginsearch_{plugin_name}() ==<br />
<br />
<br />
This function is where the search is actually done. It is passed a number of parameters that can be used <br />
in your search. They are:<br />
<br />
*$query -- the actual items being searched for.<br />
*$datestart -- starting date to begin search.<br />
*$dateend -- ending date to end search.<br />
*$topic -- topic item is assigned to.<br />
*$type -- type of item (see searchtypes above).<br />
*$author -- author of item.<br />
<br />
Depending on your plugin, some of these criteria may be meaningless and thus ignored. Here is a brief overview of what your function should do.<br />
<br />
#Initialize plugin object<br />
#Check to see if type is appropriate for this plugin -- if not, bail out.<br />
#Get a list of all your items to search.<br />
#Create Search object and Build search results header.<br />
#Search each of your items in turn.<br />
#If a match then add item to the results.<br />
#When done set number of results and return search object<br />
<br />
Looking at the example code should make all of this clear. The search results are returned in a object of type Plugin. You initialize <br />
the object by setting the Label and then setting the names of the columns you want returned. This is done in<br />
typical object fashion with this construct: $plugin->addSearch Heading. <br />
<br />
The headings will vary according to <br />
what your plugin does, but should include a link to the item found. After searching each of your items in turn; you add your search results <br />
to an array having the same number of items as the labels you set earlier. <br />
<br />
Note: your search should be case insensitive to be compatible with Geeklogs search. This array will correspond to the column headings entered above. In the example below, the array consists of<br />
the Title of the page, the url to the page (a link) and the number of hits. This array is then added to the Plugin object as a row. <br />
<br />
The following construct is used: $plugin->addSearchResult. <br />
When you are done searching you set the number of rows found and the number <br />
searched to the plugin object and return it.<br />
The example below is the External Pages Plugin.<br />
<br />
function plugin_dopluginsearch_external($query,$datestart,$dateend,$topic,$type,$author){<br />
global $_TABLES, $_CONF,$LANG_EX00;<br />
if (empty($type)) {<br />
$type = 'all';<br />
}<br />
<br />
// Bail if we aren't supppose to do our search<br />
if ($type &lt;&gt; 'all' AND $type &lt;&gt; 'external') {<br />
$plugin_results = new Plugin();<br />
$plugin_results-&gt;plugin_name = 'external';<br />
$plugin_results-&gt;searchlabel = $LANG_EX00['externpages'] . $LANG_EX00['results'];<br />
return $plugin_results;<br />
}<br />
<br />
// Build search SQL - Modified to exclude static PHP pages from search.<br />
$sql = &quot;SELECT * from &quot; . $_TABLES['external'];<br />
$result = DB_query($sql);<br />
<br />
// OK, now create new plugin object and insert table header labels<br />
require_once($_CONF['path_system'] . 'classes/plugin.class.php');<br />
$plugin_results = new Plugin();<br />
$plugin_results-&gt;plugin_name = 'external';<br />
$plugin_results-&gt;searchlabel = $LANG_EX00['externpages'] . $LANG_EX00['results'];<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['titlemsg']);<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['urlmsg']);<br />
$plugin_results-&gt;addSearchHeading($LANG_EX00['hitsmsg']);<br />
$mycount = DB_numRows($result);<br />
// NOTE if any of your data items need to be links then add them here! <br />
// make sure data elements are in an array and in the same order as your<br />
// headings above!<br />
for ($i = 1; $i <= $mycount; $i++) {<br />
$A = DB_fetchArray($result);<br />
<br />
if(SEC_hasAccess($A[owner_id],$A[group_id],$A[perm_owner],$A[perm_group],$A[perm_members],$A[perm_anon])){<br />
if (preg_match("/^(http:\/\/)/i",$A['url']) == 1) {<br />
$pth = $A['url'];<br />
$url = $A['url'];<br />
} else {<br />
$pth = $_CONF['path_html'] . $A['url'];<br />
$url = $_CONF['site_url'] . '/' . $A['url'];<br />
}<br />
$cnts = implode('',file($pth));<br />
if (stristr($cnts,$query) != '') {<br />
$rcnt++;<br />
$A['title'] = stripslashes($A['title']);<br />
$row = array($A['title'],<br />
'<a href="' . $url . '">' . $A['url'] . "</a>",<br />
$A['hits']);<br />
$plugin_results->addSearchResult($row);<br />
}<br />
}<br />
<br />
}<br />
$plugin_results->num_searchresults = $rcnt;<br />
$plugin_results->num_itemssearched = DB_count($_TABLES['external']);<br />
<br />
return $plugin_results;<br />
}<br />
<br />
[[Category:Plugin Development]]</div>Sbarakat