Hi Matt,
a while ago, I created a pretty simple search.
1. Extend the SearchForm (see EOSSearchForm.php)
2. Implement the interface in your DataObject (see
SearchableDataObject.php)
3. Put this line in your _config.php: EOSSearchForm::
$DataObjectsToSearch = array('SiteTree', 'ArticleDataObject');
4. Create your SearchForm in the Page-Controller
5. Modify your Page_results.ss to your needs. E.g. create a different
list entry (check for the ClassName) for each DataObject you want to
use. So you can have somthing like "Goto Page", "Goto Article", etc
You can do anything you want with this code as long as you keep the
author and the link in the phpdoc-comment ;)
Cheers,
Malte
------
EOSSearchForm.php
<?php
/**
* @fixme Description
* @fixme Description of method params!
*
* @package eos_basics
* @subpackage forms
* @author Malte Jansen, EOS Uptrade GmbH, Germany <development@eos-
uptrade.de>
* @link
http://www.eos-uptrade.de/silverstripe/
*/
class EOSSearchForm extends SearchForm {
protected $classesToSearchCache = NULL;
public static $DataObjectsToSearch = array();
/**
* The core search engine, used by this class and its subclasses to
do fun stuff.
* Searches both SiteTree and File.
*
* @param string $keywords Keywords as a string.
*/
public function searchEngine($keywords, $pageLength = null, $sortBy =
"Relevance DESC", $extraFilter = "", $booleanSearch = false,
$alternativeFileFilter = "", $invertedMatch = false) {
if (!$pageLength) $pageLength = $this->pageLength;
$fileFilter = '';
$keywords = trim($keywords);
if (empty($keywords)) {
return new DataObjectSet();
}
$keywords = addslashes($keywords);
$extraFilters = $this->getExtraFilters();
if ($booleanSearch) $boolean = "IN BOOLEAN MODE";
if ($extraFilter) {
$extraFilters['SiteTree'] = " AND $extraFilter";
if ($alternativeFileFilter)
$extraFilters['File'] = " AND $alternativeFileFilter";
else
$extraFilters['File'] = $extraFilters['SiteTree'];
}
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
$limit = $start . ", " . (int) $pageLength;
$notMatch = $invertedMatch ? "NOT " : "";
if($keywords) {
$match = $this->getMatch($keywords, $boolean);
// We make the relevance search by converting a boolean mode
// search into a normal one
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
$relevance = $this->getRelevance($relevanceKeywords);
} else {
$match = array();
$relevance = array();
foreach ($this->getClassesToSearch() AS $ClassNameToSearch) {
$relevance[$ClassNameToSearch] = 1;
$match[$ClassNameToSearch] = "1 = 1";
}
}
// Generate initial queries and base table names
foreach($this->getClassesToSearch() as $class => $value) {
$queries[$class] = singleton($class)->extendedSQL($notMatch .
$match[$class] . $extraFilters[$class],"");
$baseClasses[$class] = reset($queries[$class]->from);
}
// Make column selection lists
$select = $this->getSelect($baseClasses, $relevance);
// Process queries
foreach($this->getClassesToSearch() as $class => $value) {
// There's no need to do all that joining
$queries[$class]->from = array(str_replace('`','',
$baseClasses[$class]) => $baseClasses[$class]);
$queries[$class]->select = $select[$class];
$queries[$class]->orderby = null;
// echo $class."<br />".$queries[$class]."<br /><br />";
}
// Combine queries
$querySQLs = array();
$totalCount = 0;
foreach($queries as $query) {
$querySQLs[] = $query->sql();
$totalCount += $query->unlimitedRowCount();
}
$fullQuery = implode(" UNION ", $querySQLs) .
" ORDER BY $sortBy LIMIT $limit";
// Get records
$records = DB::query($fullQuery);
$objects = array();
foreach($records as $record) {
$object = DataObject::get_by_id($record['ClassName'],
$record['ID']);
if (is_a($object, 'ContentDataObject')) {
$object->setExternalLinkData(NULL, TRUE);
}
$objects[] = $object;
}
if(count($objects) > 0) {
$doSet = new DataObjectSet($objects);
$doSet->setPageLimits($start, $pageLength, $totalCount);
return $doSet;
}
return new DataObjectSet();
}
/**
*
*
* @param object $relevanceKeywords
* @return array
*/
protected function getRelevance($relevanceKeywords) {
$relevance = $this->getClassesToSearch();
foreach ($relevance AS $className => $value) {
switch ($className) {
case 'SiteTree':
$relevance[$className] = "MATCH (Title) AGAINST
('$relevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle,
MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords')";
break;
case 'File':
$relevance[$className] = "MATCH (Filename, Title, Content)
AGAINST ('$relevanceKeywords')";
break;
default:
$dataObject = singleton($className);
$relevance[$className] = $dataObject-
>getSearchableRelevance($relevanceKeywords);
break;
}
}
return $relevance;
}
/**
*
*
* @param string $keywords
* @param string $boolean
* @return array(ClassName => string)
*/
protected function getMatch($keywords, $boolean) {
$match = $this->getClassesToSearch();
foreach ($match AS $className => $value) {
switch ($className) {
case 'SiteTree':
$match[$className] = "MATCH (Title, MenuTitle, Content,
MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords'
$boolean)";
break;
case 'File':
$match[$className] = "MATCH (Filename, Title, Content) AGAINST
('$keywords' $boolean) AND ClassName = 'File'";
break;
default:
$dataObject = DataObject::get_one($className);
if ($dataObject) {
$match[$className] = $dataObject->getSearchableMatch($keywords,
$boolean);
}
unset($dataObject);
break;
}
}
return $match;
}
/**
* Returns all additional filters
*
* @return array(ClassName => string)
*/
protected function getExtraFilters() {
$extraFilters = $this->getClassesToSearch();
foreach ($extraFilters AS $className => $value) {
switch ($className) {
case 'SiteTree':
$extraFilters[$className].= " AND showInSearch != 0";
break;
default:
$dataObject = DataObject::get_one($className);
if ($dataObject) {
$dataObjectFilters = $dataObject->getSearchableExtraFilters();
if (!is_array($dataObjectFilters)) $dataObjectFilters = (array)
$dataObjectFilters;
if (count($dataObjectFilters) > 0) {
$extraFilters[$className].= " AND (" . implode(") AND (",
$dataObjectFilters).") " ;
}
}
break;
}
}
unset($dataObject);
return $extraFilters;
}
/**
* Returns the fields for the query
*
* @param array $baseClasses
* @param array $relevance
* @return array(array of Fields)
*/
protected function getSelect($baseClasses, $relevance) {
$select = $this->getClassesToSearch();
foreach ($select AS $className => $value) {
$select[$className] =
array("ClassName","Title","{$baseClasses[$className]}.ID","{$relevance[$className]}
AS Relevance");
}
return $select;
}
/**
* Returns and calculates the property classesToSearchCache.
*
* @return array(ClassName => NULL)
*/
protected function getClassesToSearch() {
if ($this->classesToSearchCache !== NULL) return $this-
>classesToSearchCache;
$this->classesToSearchCache = Object::get_static(__CLASS__,
'DataObjectsToSearch');
$this->classesToSearchCache = array_flip($this-
>classesToSearchCache);
foreach ($this->classesToSearchCache AS $class => $value) {
$this->classesToSearchCache[$class] = NULL;
}
return $this->classesToSearchCache;
}
}
?>
---
SearchableDataObject.php
<?php
/**
* All searchable DataObjects must contain this interface to be
loaded.
*
* @package eos_basics
* @subpackage Search
* @author Malte Jansen, EOS Uptrade GmbH, Germany <development@eos-
uptrade.de>
* @link
http://www.eos-uptrade.de/silverstripe/
*/
interface SearchableDataObject {
/**
* Returns a SQL-String for the relevance calculation
* e.g. "MATCH (Title) AGAINST ('$relevanceKeywords') + MATCH (Title,
MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST
('$relevanceKeywords')"
*
* @param string $relevanceKeywords
* @return string
*/
public function getSearchableRelevance($relevanceKeywords);
/**
* Returns a SQL-String for the match calculation
* e.g. "MATCH (Title, MenuTitle, Content, MetaTitle,
MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)"
*
* @param string $relevanceKeywords
* @return string
*/
public function getSearchableMatch($keywords, $boolean);
/**
* Returns an Filter-Array (AND)
*
*
* @return array(string)
*/
public function getSearchableExtraFilters();
}
?>
---
For your Page-Controller:
/**
* Site search form
*/
public function SearchForm() {
global $enableSearch;
if ($enableSearch !== TRUE) {
return '';
}
$searchText = isset($_REQUEST['Search']) ? $_REQUEST['Search'] :
'Suchbegriff';
$fields = new FieldSet(
new FocusBlurTextField("Search", "Suche", $searchText)
);
$actions = new FieldSet(
#new FormAction('results', '»')
new ImageFormAction('results', 'Suchen', $this->ThemeDir() . '/
images/suchen.png')
);
return new EOSSearchForm($this, "SearchForm", $fields, $actions);
}
/**
* Process and render search results
*/
function results($data, $form){
$data = $_REQUEST;
$data['Search'] = htmlentities($data['Search'], ENT_QUOTES,
'UTF-8');
//var_dump($data['Search']);
$data = array(
'Results' => $form->getResults(),
'Query' => $form->getSearchQuery(),
'Title' => 'Search Results'
);
return $this->customise($data)->renderWith(array('Page_results',
'Page'));
> >> For more options, visit this group athttp://
groups.google.com/group/silverstripe-dev?hl=en.
>
> > ---
> > Simon Welsh
> > Admin ofhttp://
simon.geek.nz/
> > For more options, visit this group athttp://
groups.google.com/group/silverstripe-dev?hl=en.
> For more options, visit this group athttp://
groups.google.com/group/silverstripe-dev?hl=en.