Fork me on GitHub

Watch

GitHub Core

Show your support for Zikula! Sign up at Github account and watch the Core project!




GitHub Modules

Forum Activity

Forum feed

» Visit forum | » View latest posts

Last edit 2008-08-21 20:40:16 by billthecat [ ]

Additions
Now, when the user submits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" function, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - corresponding to (for instance) "FAQ_searchapi_search".

Deletions
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" function, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - corresponding to (for instance) "FAQ_searchapi_search".


_EDITED 2008-06-19 07:50:58 by Operator [ Minor edits (quick review to catch and update PN references of all sorts. ]

Additions
To be able to find your items via the normal search function provided by Zikula, you need to implement Zikula's Search API interface - that is, you must create an API file named "pnsearchapi.php" in your module's root directory and implement a couple of specific functions in it.
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items based on the search query, and 3) insert information about the found items in the Search module's result table. This is done for the following reasons:


Deletions
To be able to find your items via the normal search function provided by Zikula 0.8, you need to implement Zikula's Search API interface - that is, you must create an API file named "pnsearchapi.php" in your module's root directory and implement a couple of specific functions in it.
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastic change from the Zikula .76 API, where the plugins had to create the output HTML by themselves, and it is done for the following reasons:



_EDITED 2008-04-23 21:43:41 by dits [ modvar based parameter passing ]

Additions

Passing of module specific variables

It is possible to pass variables specific for your module by adding inputs to your form like
<input name="modvar[yourmodulename][yourparametername]" value="yourparametervalue" type="hidden">
You will receive the variable in the args array passed to your yourmodule_pnsearchapi_searchfunction. You will thus be able to use the parameters passed to modify your search implementation.



_EDITED 2008-02-08 22:25:16 by Paustian [ ]

Additions
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastic change from the Zikula .76 API, where the plugins had to create the output HTML by themselves, and it is done for the following reasons:
2. Paging, browsing, and sorting can all be done through SQL. This performs a lot better than doing so in PHP (for "large" result sets - wherever that threshold is). .

Deletions
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastical change from the Zikula .76 API, where the plugins had to create the output HTML by themselves, and it is done for the following reasons:
2. Paging, browsing, and sorting can all be done through SQL. This performs a lot better than doing so in PHP (for "large" result sets - where ever that treshold is). .


_EDITED 2008-02-08 22:22:49 by Paustian [ ]

Additions
To be able to find your items via the normal search function provided by Zikula 0.8, you need to implement Zikula's Search API interface - that is, you must create an API file named "pnsearchapi.php" in your module's root directory and implement a couple of specific functions in it.
3. All the search HTML is concatenated and presented to the user as the complete search form. .

Deletions
To be able to find your items via the normal search function provided by Zikula .8, you need to implement Zikula's Search API interface - that is, you must create an API file named "pnsearchapi.php" in your module's root directory and implement a couple of specific functions in it.
3. All the search HTML is concatenated and presented to the user a the complete search form. .


_EDITED 2007-12-19 10:57:48 by espaan [ forgot next step text ]

Additions
Next step: Module Programming Part 5

Deletions
Module Programming Part 5


_EDITED 2007-12-19 10:56:23 by espaan [ Added next step programming part 5 ]

Additions
Module Programming Part 5

Deletions



_EDITED 2007-05-25 21:27:58 by [ ]

Additions

3. The search plugins are only activated on the first page. Subsequent browsing through the results is done much more efficiently by browsing through the result table.
Instead of going through PHP we just do a "INSERT INTO result-table SELECT ..." where the select statement is more or less the same as the normal search statement.

Implementation details

One problem is that the search module won't be informed when a session is deleted. This means the result table will grow and grow, keeping up with an ever increasing amount of unused results stored for unused sessions. To avoid this the system adds a timestamp to each found item. Items older than some hours are automatically deleted.


Deletions
Instead of going through PHP we just do a "INSERT INTO result-table SELECT ..." where the select statement is more or less the same as the normal search statement.


_EDITED 2007-05-24 22:12:50 by [ ]

Additions

Adding URLs and last minute access checking

You may wonder why there was no "link-to-item" URL in the above code examples. This is because URLs must be generated via the pnModUrl() function - and we only want to call that function for the items actually displayed to the end user. So the item URL is added in a post processing step for each item displayed to the user.
This post processing is done with the "xxx_yyy_check" API function (where xxx=module name and yyy=search function name). This function is called by the search API and passed the database data for the found item:
The ampersands are *very* important here! We need to modify the $args variable
function faq_searchapi_search_check(&$args)
Fetch database row and the "extra" value in this row
$datarow = &$args['datarow'];
$faqId = $datarow['extra'];

Write URL into the data
$datarow['url'] = pnModUrl('FAQ', 'user', 'display', array('faqid' => $faqId));
User has access to this item - so return true
It is also possible to do late access checking at this time - but it should be avoided since the displayed count of found items will be too big if you filter out some items. If you need to use late access checking then return true/false signalling access allowed/denied.



_EDITED 2007-05-24 21:58:56 by [ ]

Additions
1. title - the name of the module (must match the name used in the search options template exactly).
The example here is from the FAQ module:
function faq_searchapi_info()
return array('title' => 'FAQ',
'functions' => array('FAQ' => 'search'));
You need to implement the "options" function that returns the input form's HTML code. Here's an example from the FAQ module:
function faq_searchapi_options($args)
if (SecurityUtil::checkPermission( 'FAQ::', '::', ACCESS_READ)) {
$pnRender = new pnRender('FAQ');
return $pnRender->fetch('faq_search_options.htm');
return ;
<input type="checkbox" id="active_faqs" name="active[FAQ]" value="1" checked="checked" />
<label for="active_faqs"><!--[pnml name="_FAQ_CATEGORIES]--></label>
</div>
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" function, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - corresponding to (for instance) "FAQ_searchapi_search".


Deletions
1. title - the name of the module (must match the module directoy name exactly since it is used to locate the module later on).
The example here is from the Admin_Messages module:
function Admin_Messages_searchapi_info()
return array('title' =>'Admin_Messages',
'functions' => array('Admin_Messages' => 'search'));
You need to implement the "options" function that returns the input form's HTML code. Here's an example from the Admin_Messages module:
function Admin_Messages_searchapi_options($args)
if (SecurityUtil::checkPermission('Admin_Messages:search:', '::', ACCESS_READ))
{
Make sure we have language defines available in the templates
pnModLangLoad('Admin_Messages', 'admin');
Create output render object
$pnRender = new pnRender('Admin_Messages');

Assign various module variables for the template
$pnRender->assign(pnModGetVar('Admin_Messages'));
$pnRender->assign('active', (isset($args['active']) && isset($args['active']['Admin_Messages']))
(!isset($args['active'])));
$pnRender->assign('activeonly', (isset($args['modvar']) && isset($args['modvar']['Admin_Messages']) && isset($args['modvar']['Admin_Messages']['activeonly']))
!isset($args['modvar']));

return $pnRender->fetch('admin_messages_search_options.htm');
else return ;
<input type="checkbox" id="active_admin_messages" name="active[Admin_Messages]" value="1" <!--[if $active]-->checked="checked"<!--[/if]--> />
<label for="active_admin_messages"><!--[pnml name="_ADMINMESSAGES_SEARCH]--></label>
<!--[if $allowsearchinactive]-->
<input type="checkbox" id="admin_messages_activeonly" name="modvar[Admin_Messages][activeonly]" value="1" <!--[if $activeonly]-->checked="checked"<!--[/if]--> />
<label for="admin_messages_activeonly"><!--[pnml name="_ADMINMESSAGES_ACTIVEONLY]--></label>
<!--[/if]-->
</div>%%
Try also to take a look at the template from the User module which is a bit simpler.
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" function, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - corresponding to (for instance) "Admin_Messages_searchapi_search".



_EDITED 2007-05-24 21:55:24 by [ ]

Additions
1. title - the name of the module (must match the module directoy name exactly since it is used to locate the module later on).
'functions' => array('Admin_Messages' => 'search'));
You need to implement the "options" function that returns the input form's HTML code. Here's an example from the Admin_Messages module:
Make sure we have language defines available in the templates
Try also to take a look at the template from the User module which is a bit simpler.
A very important step here is to "decorate" all data in the result table with the current user's session ID. This is done so multiple users can do searching simultaneously without seeing each others results.
3. extra - any data needed to create a link to the item (typically the item ID).
5. module - exact name of the module that owns the item.
6. session - the session ID of the current user.
Here is how it is done in the FAQ API:
function faq_searchapi_search($args)
$faqtable = $pntable['faqanswer'];
$faqcolumn = $pntable['faqanswer_column'];
$searchTable = $pntable['search_result'];
$searchColumn = $pntable['search_result_column'];
$where = search_construct_where($args,
array($faqcolumn['question'],
$faqcolumn['answer']),
  1. ;
$sessionId = session_id();
$faqcolumn[question] as title,
$faqcolumn[answer] as text,
$faqcolumn[faqid] as id,
$faqcolumn[cr_date] as date
FROM $faqtable
$searchColumn[extra],
$searchColumn[session])
$item = $result->GetRowAssoc?(2);
if (SecurityUtil::checkPermission('FAQ::', "$item[id]::", ACCESS_READ)) {
. '\ . DataUtil::formatForStore($item['title']) . '\', '
. '\
. DataUtil::formatForStore($item['text']) . '\', '
. '\ . DataUtil::formatForStore($item['id']) . '\', '
. '\
. DataUtil::formatForStore($item['date']) . '\', '
. '\ . 'FAQ' . '\', '
. '\
. DataUtil::formatForStore($sessionId) . '\')'; ...........

Deletions

1. title - the name of the module (must match exactly since it is used to locate the module later on).
Make sure translation of "_ADMINMESSAGES" is available
pnModLangLoad('Admin_Messages', 'admin');
'functions' => array(_ADMINMESSAGES => 'search'));
You need to implement the following function:
A very important step here is to "decorate" all data in the result table with the current user's user-ID. This is done so multiple users can do searching simultaneously without seeing each others results.
3. module - exact name of the module that owns the item.
5. updated - date of last update made to the item (often just the created date).
6. uid - the user-ID of the current user.
Here is how it is done in the Admin_Messages API:
function Admin_Messages_searchapi_search($args)
Security check
if (SecurityUtil::checkPermission('Admin_Messages::', '::', ACCESS_READ)) {
return true;
get the db and table info
$messagestable = $pntable['message'];
$messagescolumn = $pntable['message_column'];
$searchTable = &$pntable['search_result'];
$searchColumn = &$pntable['search_result_column'];
form the where clause
$where = ;
if (!pnModGetVar('Admin_Messages', 'allowsearchinactive')
(isset($args['activeonly']) && (bool)$args['activeonly'])){
$where .= " $messagescolumn[active] = 1 AND ";
$where .= " ($messagescolumn[date]+$messagescolumn[expire] > '".time()."' OR $messagescolumn[expire] = 0) AND";
$where .= search_construct_where($args, array($messagescolumn['title'], $messagescolumn['content']), $messagescolumn['language']);
$userId = (int)pnUserGetVar('uid');
pn_title as title,
pn_content as text,
pn_date as date,
pn_date as date
FROM $messagestable
$searchColumn[updated],
$searchColumn[uid])
$message = $result->GetRowAssoc?(2);
if (SecurityUtil::checkPermission('Admin_Messages::', "$message[title]::$message[mid]", ACCESS_READ)) {
. '\ . DataUtil::formatForStore($message['title']) . '\', '
. '\ . DataUtil::formatForStore($message['text']) . '\', '
. '\
. 'Admin_Messages' . '\', '
. '\ . DataUtil::formatForStore($message['date']) . '\', '
. '\
. DataUtil::formatForStore($message['date']) . '\', '
. $userId . ')'; ...........



_EDITED 2007-05-16 23:20:51 by [ ]

Additions
Please refer to the code in your working copy of Zikula - the above code might easily be deprecated when you read this.


_EDITED 2007-05-16 23:16:08 by [ ]

Additions
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" function, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - corresponding to (for instance) "Admin_Messages_searchapi_search".
1. Access check - does the current user have access to searching through this kind of documents?
5. Check each found item for item based access. If the user has access to it then it is inserted in the Search module's result table.
A very important step here is to "decorate" all data in the result table with the current user's user-ID. This is done so multiple users can do searching simultaneously without seeing each others results.
1. title - the title of the found item.
2. text - any relevant text representing the item.
3. module - exact name of the module that owns the item.
4. created - date of creation of the item.
5. updated - date of last update made to the item (often just the created date).
6. uid - the user-ID of the current user.
Process the result set and insert into search result table
If it is possible to do searching without access control, or with an access control that can be expressed in SQL, then we can short-circuit the "select-from-database-into-PHP-and-insert-into-database-again" step.
Instead of going through PHP we just do a "INSERT INTO result-table SELECT ..." where the select statement is more or less the same as the normal search statement.


Deletions
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" functions, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - for instance "Admin_Messages_searchapi_search".
1. An access check - does the current user have access to searching through this kind of documents?
5. Each found item is checked for item based access. If the user has access to it then it is inserted in the Search module's result table.
A very important step here is to "decorate" all data in the result table with the current user's user-ID. This is done so multiple users can do searching simultaneously without seeing each others results.
1. title - the title of the found item.
2. text - any relevant text representing the found item.
3. module - exact name of the module that owns the found item.
4. created - date of creation of the item.
5. updated - date of last update made to the item (often just the created date).
6. uid - the user-ID of the current user.
Process the result set into an array of records
If it is possible to do searching without access control, or with an access control that can be expressed in SQL, then we can short-circuit the "select-from-database-into-PHP-and-insert-into-database-again" step. Instead of going through PHP we just do a "INSERT INTO result-table SELECT ..." where the select statement is more or less the same as the normal search statement.



_EDITED 2007-05-16 23:11:25 by [ ]

Additions
Now, when the user subsmits the search query, the Search module starts scanning for search plugins. For each found plugin it locates the "info" functions, fetches the "functions" list, and iterates through each document type's search function. Typically there will only be one type with a search function named "search" - for instance "Admin_Messages_searchapi_search".
A typical search function performs these steps:
1. An access check - does the current user have access to searching through this kind of documents?
2. Loading database information.
3. Create an SQL "select" statement based on the search query.
4. Execute the "select" statement and iterate through the found items.
5. Each found item is checked for item based access. If the user has access to it then it is inserted in the Search module's result table.
A very important step here is to "decorate" all data in the result table with the current user's user-ID. This is done so multiple users can do searching simultaneously without seeing each others results.
We need to insert the following values in the result table:
1. title - the title of the found item.
2. text - any relevant text representing the found item.
3. module - exact name of the module that owns the found item.
4. created - date of creation of the item.
5. updated - date of last update made to the item (often just the created date).
6. uid - the user-ID of the current user.
Here is how it is done in the Admin_Messages API:
function Admin_Messages_searchapi_search($args)
Security check
if (SecurityUtil::checkPermission('Admin_Messages::', '::', ACCESS_READ)) {
return true;
get the db and table info
pnModDBInfoLoad('Search');
$pntable = pnDBGetTables();
$messagestable = $pntable['message'];
$messagescolumn = $pntable['message_column'];
$searchTable = &$pntable['search_result'];
$searchColumn = &$pntable['search_result_column'];
form the where clause
$where = ;
if (!pnModGetVar('Admin_Messages', 'allowsearchinactive')
(isset($args['activeonly']) && (bool)$args['activeonly'])){
$where .= " $messagescolumn[active] = 1 AND ";
$where .= " ($messagescolumn[date]+$messagescolumn[expire] > '".time()."' OR $messagescolumn[expire] = 0) AND";
$where .= search_construct_where($args, array($messagescolumn['title'], $messagescolumn['content']), $messagescolumn['language']);
$userId = (int)pnUserGetVar('uid');
$sql = "
SELECT
pn_title as title,
pn_content as text,
pn_date as date,
pn_date as date
FROM $messagestable
WHERE $where";
$result = DBUtil::executeSQL($sql);
if (!$result) {
return LogUtil::registerError (_GETFAILED);
$insertSql =
"INSERT INTO $searchTable
($searchColumn[title],
$searchColumn[text],
$searchColumn[module],
$searchColumn[created],
$searchColumn[updated],
$searchColumn[uid])
VALUES ";
Process the result set into an array of records
for (; !$result->EOF; $result->MoveNext?()) {
$message = $result->GetRowAssoc?(2);
if (SecurityUtil::checkPermission('Admin_Messages::', "$message[title]::$message[mid]", ACCESS_READ)) {
$sql = $insertSql . '('
. '\ . DataUtil::formatForStore($message['title']) . '\', '
. '\ . DataUtil::formatForStore($message['text']) . '\', '
. '\
. 'Admin_Messages' . '\', '
. '\ . DataUtil::formatForStore($message['date']) . '\', '
. '\
. DataUtil::formatForStore($message['date']) . '\', '
. $userId . ')';
$insertResult = DBUtil::executeSQL($sql);
if (!$insertResult) {
return LogUtil::registerError (_GETFAILED);
}
}
return true;

Speeding up searching

If it is possible to do searching without access control, or with an access control that can be expressed in SQL, then we can short-circuit the "select-from-database-into-PHP-and-insert-into-database-again" step. Instead of going through PHP we just do a "INSERT INTO result-table SELECT ..." where the select statement is more or less the same as the normal search statement..


Deletions
... more to come ...


_EDITED 2007-05-16 22:57:09 by [ ]

Additions
Your pnsearchapi.php file must include a "info" function that passes various required information about the module back to the Search module.
1. title - the name of the module (must match exactly since it is used to locate the module later on).
2. functions - an array of document types and their search function. The Search module will iterate over each document type in the array and call the corresponding search functions when doing the search.
Create output render object
Assign various module variables for the template
The corresponding pnRender template is:


Deletions
Your pnsearchapi.php file must include a "info" function that passed various required information about the module back to the Search module.
1. title - the name of the module (must match exactly it is used to locate the module later on).
2. functions - an array of document types and their search function. The Search module will iterate over each element in the array and call the corresponding search functions when doing the search.
Create and setup output render object
The searchform has changed from .7x to .8.
Now we have all variables of one module in a html form array named like the module:
This Example is taken from the admin messages module. It's important that the text in brackets (here: Admin_Messages) is exactly the module's name (case sensitive) and that it's equal to the title field from above.



_EDITED 2007-05-16 22:53:06 by [ cleaning up ]

Additions
1. The user enters a search query - some keywords like "Zikula administration" - and fills out other search elements.
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastical change from the Zikula .76 API, where the plugins had to create the output HTML by themselves, and it is done for the following reasons:

Interacting with the search module



Deletions
1. The user enters a search query - some keywords like "Zikula administration" - fills out other search elements.
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items, based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastical change from the Zikula .76 API, where the plugins had to create the output HTML by them self, and it is done for the following reasons:

Interacting with the search module




_EDITED 2007-05-16 22:49:27 by [ ]

Additions

Module Programming Part 4 - Searching

But let's first take a look at how searching actually work. Here is what happens when a search form is created:
1. The Search module scans all modules for an implementation of the Search interface.
2. Each found module's search "plugin" is then asked to generate the HTML needed for any special search settings (like selecting a subset of the available topics or restricting search to a certain forum).
3. All the search HTML is concatenated and presented to the user a the complete search form.
Now the actual searching starts:
1. The user enters a search query - some keywords like "Zikula administration" - fills out other search elements.
3. Each search plugin is passed the search query and other data from the search form and is asked to make a search of it in all the module's items.
So the most important thing for your search plugin to do is 1) To create a search form, 2) to find items, based on the search query, and 3) insert information about the found items in the Search module's result table. This last step is a rather drastical change from the Zikula .76 API, where the plugins had to create the output HTML by them self, and it is done for the following reasons: