mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
FEATURE Microsoft Sequel Server fulltext searching.
FEATURE sqlsrv_num_rows emulation (set $query->forceNumRows = true;) git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@78493 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
059aa76eec
commit
d064eeb36b
@ -714,6 +714,97 @@ class MySQLDatabase extends Database {
|
||||
return $classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = Convert::raw2sql($keywords);
|
||||
$htmlEntityKeywords = htmlentities($keywords);
|
||||
$classesToSearch = explode(',', SearchForm::$classesToSearch);
|
||||
|
||||
$extraFilters = array('SiteTree' => '', 'File' => '');
|
||||
|
||||
if($booleanSearch) $boolean = "IN BOOLEAN MODE";
|
||||
|
||||
if($extraFilter) {
|
||||
$extraFilters['SiteTree'] = " AND $extraFilter";
|
||||
|
||||
if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter";
|
||||
else $extraFilters['File'] = $extraFilters['SiteTree'];
|
||||
}
|
||||
|
||||
// Always ensure that only pages with ShowInSearch = 1 can be searched
|
||||
$extraFilters['SiteTree'] .= " AND ShowInSearch <> 0";
|
||||
|
||||
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
|
||||
$limit = $start . ", " . (int) $pageLength;
|
||||
|
||||
$notMatch = $invertedMatch ? "NOT " : "";
|
||||
if($keywords) {
|
||||
$match['SiteTree'] = "
|
||||
MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)
|
||||
+ MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$htmlEntityKeywords' $boolean)
|
||||
";
|
||||
$match['File'] = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
|
||||
|
||||
// We make the relevance search by converting a boolean mode search into a normal one
|
||||
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
|
||||
$htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords);
|
||||
$relevance['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$htmlEntityRelevanceKeywords')";
|
||||
$relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
|
||||
} else {
|
||||
$relevance['SiteTree'] = $relevance['File'] = 1;
|
||||
$match['SiteTree'] = $match['File'] = "1 = 1";
|
||||
}
|
||||
|
||||
// Generate initial queries and base table names
|
||||
$baseClasses = array('SiteTree' => '', 'File' => '');
|
||||
foreach($classesToSearch as $class) {
|
||||
$queries[$class] = singleton($class)->extendedSQL($notMatch . $match[$class] . $extraFilters[$class], "");
|
||||
$baseClasses[$class] = reset($queries[$class]->from);
|
||||
}
|
||||
|
||||
// Make column selection lists
|
||||
$select = array(
|
||||
'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
|
||||
'File' => array("ClassName","$baseClasses[File].ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevance[File] AS Relevance","NULL AS CanViewType"),
|
||||
);
|
||||
|
||||
// Process queries
|
||||
foreach($classesToSearch as $class) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
foreach($records as $record)
|
||||
$objects[] = new $record['ClassName']($record);
|
||||
|
||||
if(isset($objects)) $doSet = new DataObjectSet($objects);
|
||||
else $doSet = new DataObjectSet();
|
||||
|
||||
$doSet->setPageLimits($start, $pageLength, $totalCount);
|
||||
return $doSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Because NOW() doesn't always work...
|
||||
* MSSQL, I'm looking at you
|
||||
|
@ -23,7 +23,7 @@ class SearchForm extends Form {
|
||||
/**
|
||||
* Classes to search
|
||||
*/
|
||||
protected $classesToSearch = array("SiteTree", "File");
|
||||
static public $classesToSearch = "SiteTree,File";
|
||||
|
||||
/**
|
||||
*
|
||||
@ -87,7 +87,7 @@ class SearchForm extends Form {
|
||||
*/
|
||||
public function getResults($pageLength = null, $data = null){
|
||||
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
|
||||
if(!isset($data)) $data = $_REQUEST;
|
||||
if(!isset($data) || !is_array($data)) $data = $_REQUEST;
|
||||
|
||||
// set language (if present)
|
||||
if(singleton('SiteTree')->hasExtension('Translatable') && isset($data['locale'])) {
|
||||
@ -112,9 +112,9 @@ class SearchForm extends Form {
|
||||
$keywords = $this->addStarsToKeywords($keywords);
|
||||
|
||||
if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) {
|
||||
$results = $this->searchEngine($keywords, $pageLength, "Relevance DESC", "", true);
|
||||
$results = DB::GetConn()->searchEngine($keywords, $pageLength, "Relevance DESC", "", true);
|
||||
} else {
|
||||
$results = $this->searchEngine($keywords, $pageLength);
|
||||
$results = DB::GetConn()->searchEngine($keywords, $pageLength);
|
||||
}
|
||||
|
||||
// filter by permission
|
||||
@ -148,99 +148,6 @@ class SearchForm extends Form {
|
||||
return implode(" ", $newWords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 = Convert::raw2sql($keywords);
|
||||
$htmlEntityKeywords = htmlentities($keywords);
|
||||
|
||||
$extraFilters = array('SiteTree' => '', 'File' => '');
|
||||
|
||||
if($booleanSearch) $boolean = "IN BOOLEAN MODE";
|
||||
|
||||
if($extraFilter) {
|
||||
$extraFilters['SiteTree'] = " AND $extraFilter";
|
||||
|
||||
if($alternativeFileFilter) $extraFilters['File'] = " AND $alternativeFileFilter";
|
||||
else $extraFilters['File'] = $extraFilters['SiteTree'];
|
||||
}
|
||||
|
||||
// Always ensure that only pages with ShowInSearch = 1 can be searched
|
||||
$extraFilters['SiteTree'] .= " AND ShowInSearch <> 0";
|
||||
|
||||
$start = isset($_GET['start']) ? (int)$_GET['start'] : 0;
|
||||
$limit = $start . ", " . (int) $pageLength;
|
||||
|
||||
$notMatch = $invertedMatch ? "NOT " : "";
|
||||
if($keywords) {
|
||||
$match['SiteTree'] = "
|
||||
MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$keywords' $boolean)
|
||||
+ MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$htmlEntityKeywords' $boolean)
|
||||
";
|
||||
$match['File'] = "MATCH (Filename, Title, Content) AGAINST ('$keywords' $boolean) AND ClassName = 'File'";
|
||||
|
||||
// We make the relevance search by converting a boolean mode search into a normal one
|
||||
$relevanceKeywords = str_replace(array('*','+','-'),'',$keywords);
|
||||
$htmlEntityRelevanceKeywords = str_replace(array('*','+','-'),'',$htmlEntityKeywords);
|
||||
$relevance['SiteTree'] = "MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$relevanceKeywords') + MATCH (Title, MenuTitle, Content, MetaTitle, MetaDescription, MetaKeywords) AGAINST ('$htmlEntityRelevanceKeywords')";
|
||||
$relevance['File'] = "MATCH (Filename, Title, Content) AGAINST ('$relevanceKeywords')";
|
||||
} else {
|
||||
$relevance['SiteTree'] = $relevance['File'] = 1;
|
||||
$match['SiteTree'] = $match['File'] = "1 = 1";
|
||||
}
|
||||
|
||||
// Generate initial queries and base table names
|
||||
$baseClasses = array('SiteTree' => '', 'File' => '');
|
||||
foreach($this->classesToSearch as $class) {
|
||||
$queries[$class] = singleton($class)->extendedSQL($notMatch . $match[$class] . $extraFilters[$class], "");
|
||||
$baseClasses[$class] = reset($queries[$class]->from);
|
||||
}
|
||||
|
||||
// Make column selection lists
|
||||
$select = array(
|
||||
'SiteTree' => array("ClassName","$baseClasses[SiteTree].ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevance[SiteTree] AS Relevance", "CanViewType"),
|
||||
'File' => array("ClassName","$baseClasses[File].ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevance[File] AS Relevance","NULL AS CanViewType"),
|
||||
);
|
||||
|
||||
// Process queries
|
||||
foreach($this->classesToSearch as $class) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
foreach($records as $record)
|
||||
$objects[] = new $record['ClassName']($record);
|
||||
|
||||
if(isset($objects)) $doSet = new DataObjectSet($objects);
|
||||
else $doSet = new DataObjectSet();
|
||||
|
||||
$doSet->setPageLimits($start, $pageLength, $totalCount);
|
||||
return $doSet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the search query for display in a "You searched for ..." sentence.
|
||||
*
|
||||
|
Loading…
Reference in New Issue
Block a user