From d064eeb36bc35ac15528f9587d54a7235a39c77e Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Fri, 5 Jun 2009 03:44:28 +0000 Subject: [PATCH] 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 --- core/model/MySQLDatabase.php | 91 +++++++++++++++++++++++++++++++ search/SearchForm.php | 101 ++--------------------------------- 2 files changed, 95 insertions(+), 97 deletions(-) diff --git a/core/model/MySQLDatabase.php b/core/model/MySQLDatabase.php index 08cd4060c..dea82bedc 100644 --- a/core/model/MySQLDatabase.php +++ b/core/model/MySQLDatabase.php @@ -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 diff --git a/search/SearchForm.php b/search/SearchForm.php index 5c7d258e4..9dcd0b42e 100755 --- a/search/SearchForm.php +++ b/search/SearchForm.php @@ -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. *