// ###################################################### // FlexSearchForm - modified by Stephan Dale 01/05/2006 # // ###################################################### // ----------------------- // Snippet: FlexSearchForm // ----------------------- // Version: 0.6j // Date: 2005.02.01 // jaredc@honeydewdesign.com // // This snippet was designed to create a search form // that is highly flexible in how it is presented. It // can be used as both a small, subtle, persistent // search field element, as well as present the search // results. All elements are branded with classes // for easy CSS styling. // // 2006.02.06 added extract functionality by Marc Hinse, mh@madeyourweb.com // some extract functionality credits go to Joomla Open Source CMS // 2006.05.01 Modified by Stephan Dale for http://mindSpill.net. Email: modx(a)mindspill.net // Set config, markup changes and altered form action to be relative to site root (i.e. begin with a forward slash). // Added access control, i.e. hide private documents if the user doesn't have permission to view them. This functionality uses code lifted from the MODx getDocumentObject method in document.parser.class.inc.php. // Added better support for weblinks. // &useWeblink - [ true | false ] If true and the doc is of type 'reference', link to the doc's weblink (it's 'content' field) rather than to the doc itself. Defaults to false. // &weblinkClass - [ string ] CSS class for a weblink's anchor. Defaults to "weblink". // 2007.02.07 Modified by Stephan Dale for http://mindSpill.net. Email: modx(a)mindspill.net // Minor mods to fix two problems where php complained about the PrepareSearchContent function either declared // twice or missing. This was done by testing whether the function exists before declaring it and moving the // declarations to the top of the file. // CONFIGURE // $useWeblink [ true | false ] // If true and the doc has type 'reference', link to doc's weblink rather than the doc itself. // Defaults to false. $useWeblink = (!isset($useWeblink)) ? false : ($useWeblink==true); // $weblinkClass [ string ] // CSS class for a weblink's anchor. Defaults to "weblink". $weblinkClass = (!isset($weblinkClass)) ? 'weblink' : "$weblinkClass"; // MAIN SNIPPET SETUP OPTIONS // -------------------------- // $searchStyle [ 'relevance' | 'partial' ] // This option allows you to decide to use a faster, // relevance sorted search ('relevance') which WILL NOT // inlclude partial matches. Or use a slower, but // more inclusive search ('partial') that will include // partial matches. Results will NOT be sorted based on // relevance. // // This option contributed by Rich from Snappy Graffix Media to // allow partial matching and LIKE matching of the search term. // sam@snappygraffix.com $searchStyle = 'relevance'; // $useAllWords [ true | false ] // If you want only documents which contain ALL words in the // search string, set to true. Otherwise, the search will return // all pages with ONE or more of the search words (which can be // a LOT more pages). $useAllWords = true; // $showSearchWithResults [1 | 0] // If you would like to turn off the search // form when showing results you can set // this to false. Can also be set in template // by using the snippet variable: FSF_showForm // like this (1=true, 0=false): // [[FlexSearchForm?FSF_showForm=0]] $showSearchWithResults = 1; // $resultsPage [int] // The default behavior is to show the results on // the current page, but you may define the results // page any way you like. The priority is: // // 1- snippet variable - set in page template like this: // [[FlexSearchForm?FSF_landing=int]] // where int is the page id number of the page you want // your results on // 2- querystring variable FSF_form // 3- variable set here // 4- use current page // // This is VERY handy when you want to put the search form in // a discrete and/or small place on your page- like a side // column, but don't want all your results to show up there! // Set to results page or leave 0 as default $resultsPage = 0; // $showResults [1 | 0] (as passed in snippet variable ONLY) // You can define whether this snippet will show the results // of the search with it. Do this by assigning the snippet // variable FSF_showResults as: // [[FlexSearchForm?FSF_showResults=0]] // // This is useful in situations where you want to show the // search results in a different place than the search form. // In that type of situation, you would include two of these // snippets on the same page, one showing results, and one // showing the form. // // Default is true. // $grabMax [ int ] // Set to the max number of records you would like on // each page. Set to 0 if unlimited. $grabMax = 10; // $pageLinkSeparator [ string ] // What you want, if anything, between your page link numbers $pageLinkSeparator = " | "; // $stripHTML [ true | false ] // Allow HTML characters in the search? Probably not. $stripHTML = true; // $stripSnip [ true | false ] // Strip out snippet calls etc from the search string? $stripSnip = true; // $stripSnippets [ true | false ] // Strip out snippet names so users will not be able to "search" // to see what snippets are used in your content. This is a // security benefit, as users will not be able to search for what pages // use specific snippets. $stripSnippets = true; // $xhtmlStrict [ true | false ] // If you want this form to validate as XHTML Strict compliance, then // this needs to be true, but IE has a fieldset bug that I don't know // a workaround for. So you can set this to false to avoid it. $xhtmlStrict = true; // $minChars [ int ] // Minimum number of characters to require for a word to be valid for // searching. MySQL will typically NOT search for words with less than // 4 characters (relevance mode). If you have $useAllWords = true and // a three or fewer word appears in the search string, the results will // always be 0. Setting this drops those words from the search in THAT // CIRCUMSTANCE ONLY (relevance mode, useAllWords=true). $minChars = 4; // LANGUAGE AND LABELS // -------------------------- // $resultsIntroFailure // If nothing is found for the search. You should give the user // some indication that the search was a failure. $resultsIntroFailure = 'There were no search results. Please try using more general terms to get more results.'; //$resultsIntroFailure = 'Leider keine Ergebnisse. Bitte versuchen Sie es mit einem allgemeineren Begriff.'; // $searchButtonText [string] // Label the search button what // you wish $searchButtonText = 'Go!'; //$searchButtonText = 'Los!'; // $boxText [ string ] // What, if anything, you want to have in the search box when the // form first loads. When clicked, it will disappear. This uses // JavaScript. If you don't want this feature or the JavaScript, // just set to empty string: '' $boxText = 'Search here...'; //$boxText = 'Suchen...'; // $introMessage [ string ] // This is the text that will show up if the person arrives // at the search page without having done a search first. // You can leave this as an empty string if you like. $introMessage = 'Please enter a search term to begin your search.'; //$introMessage = ''; // $resultsFoundTextSingle, $resultsFoundTextMultiple [ string patttern ] // The string that will tell the user how many results were found. // two variables will be provided at runtime: // %d The number of results found (integer) // %s The search string itself. $resultsFoundTextSingle = '%d result found for "%s".'; $resultsFoundTextMultiple = '%d results found for "%s".'; //$resultsFoundTextSingle = '%d Ergebnis gefunden für "%s".'; //$resultsFoundTextMultiple = '%d Ergebnisse gefunden für "%s".'; // $paginationTextSinglePage and $paginationTextMultiplePages [ string ] // The text that comes before the links to other pages. In this // example, "Result pages: " was the $paginationTextMultiplePages: // Result pages: 1 | 2 | 3 | 4 // Page numbers will only appear if there is more than one page. $paginationTextSinglePage = ''; $paginationTextMultiplePages = 'Result pages: '; //$paginationTextSinglePage = ''; //$paginationTextMultiplePages = 'Ergebnisseiten: '; // $extract [true|false] // show the search terms highlighted in a little extract (like Google) $extract=true; // Styles // These styles are included in this snippet: // // .FSF_form{} // .FSF_input {} // .FSF_submit {} // // .FSF_SearchResults {} // .FSF_resultsIntroFailure{} // .FSF_result {} // .FSF_resultLink {} // .FSF_resultDescription {} // .FSF_pagination // .FSF_intro // .FSF_extract // .FSF_highlight1, FSF_highlight2... the different styles (e.g. colors) for highlighting the search terms. // ------------- // End configure // ------------- if (!function_exists('PrepareSearchContent')) { function PrepareSearchContent( $text, $length=200, $search ) { // strips tags won't remove the actual jscript $text = preg_replace( "']*>.*?'si", "", $text ); $text = preg_replace( '/{.+?}/', '', $text); //$text = preg_replace( '/]*>([^<]+)<\/a>/is','\2', $text ); // replace line breaking tags with whitespace $text = preg_replace( "'<(br[^/>]*?/|hr[^/>]*?/|/(div|h[1-6]|li|p|td))>'si", ' ', $text ); return SmartSubstr( strip_tags( $text ), $length, $search ); } } /** * returns substring of characters around a searchword * @param string The source string * @param int Number of chars to return * @param string The searchword to select around * @return string */ if (!function_exists('SmartSubstr')) { function SmartSubstr($text, $length=200, $search) { $wordpos = strpos(strtolower($text), strtolower($search)); $halfside = intval($wordpos - $length/2 - strlen($search)); if ($wordpos && $halfside > 0) { return '...' . substr($text, $halfside, $length) . '...'; } else { return substr( $text, 0, $length); } } } // establish whether to show the form or not $showSearchWithResults = (isset($FSF_showForm))? $FSF_showForm : $showSearchWithResults; // establish whether to show the results or not $showResults = (isset($FSF_showResults))? $FSF_showResults : true; // establish results page if (isset($FSF_landing)){ // set in snippet $searchAction = "[~".$FSF_landing."~]"; } elseif ($resultsPage > 0){ // locally set $searchAction = "[~".$resultsPage."~]"; } else { //otherwise $searchAction = "[~".$modx->documentIdentifier."~]"; } // Set newline variable $newline = "\n"; // Initialize search string $searchString = ''; // CLEAN SEARCH STRING if ( isset($_POST['search']) || isset($_GET['FSF_search']) ){ // Prefer post to get $searchString = (isset($_POST['search']))? $_POST['search'] : urldecode($_GET['FSF_search']) ; // block sensitive search patterns $searchString = ( $searchString != "{{" && $searchString != "[[" && $searchString != "[!" && $searchString != "[(" && $searchString != "[~" && $searchString != "[*" ) ? $searchString : "" ; // Remove dangerous tags and such // Strip HTML too if ($stripHTML){ $searchString = strip_tags($searchString); } // Regular expressions of things to remove from search string $modRegExArray[] = '~\[\[(.*?)\]\]~'; // [[snippets]] $modRegExArray[] = '~\[!(.*?)!\]~'; // [!noCacheSnippets!] $modRegExArray[] = '!\[\~(.*?)\~\]!is'; // [~links~] $modRegExArray[] = '~\[\((.*?)\)\]~'; // [(settings)] $modRegExArray[] = '~{{(.*?)}}~'; // {{chunks}} $modRegExArray[] = '~\[\*(.*?)\*\]~'; // [*attributes*] // Remove modx sensitive tags if ($stripSnip){ foreach ($modRegExArray as $mReg){ $searchString = preg_replace($mReg,'',$searchString); } } // Remove snippet names if ($stripSnippets && $searchString != ''){ // get all the snippet names $tbl = $modx->dbConfig['dbase'] . "." . $modx->dbConfig['table_prefix'] . "site_snippets"; $snippetSql = "SELECT $tbl.name FROM $tbl;"; $snippetRs = $modx->dbQuery($snippetSql); $snippetCount = $modx->recordCount($snippetRs); $snippetNameArray = array(); for ($s = 0; $s < $snippetCount; $s++){ $thisSnippetRow = $modx->fetchRow($snippetRs); $snippetNameArray[] = strtolower($thisSnippetRow['name']); } // Split search into strings $searchWords = explode(' ',$searchString); $cleansedWords = ''; foreach($searchWords as $word){ if ($word != '' && !in_array(strtolower($word),$snippetNameArray) && ((!$useAllWords) || ($searchStyle == 'partial') || (strlen($word) >= $minChars && $useAllWords && $searchStyle == 'relevance')) ){ $cleansedWords .= $word.' '; } } // Remove last space $cleansedWords = substr($cleansedWords,0,(strlen($cleansedWords)-1)); $searchString = $cleansedWords; } } // End cleansing search string // check querystring $validSearch = ($searchString != '')? true : false ; //check for offset $offset = (isset($_GET['FSF_offset']))? $_GET['FSF_offset'] : 0; // initialize output $SearchForm = ''; // establish form if (($validSearch && ($showSearchWithResults)) || $showSearchWithResults){ $SearchForm .= '
'; $SearchForm .= ($xhtmlStrict)? '
Search' : '' ; // decide what goes in search box $searchBoxVal = ($searchString == '' && $boxText != '')? $boxText : $searchString ; $SearchForm .= '' : '/>'; $SearchForm .= ''; $SearchForm .= ($xhtmlStrict)? '
' : ''; $SearchForm .= '
'.$newline; } if ($showResults) { if($validSearch) { $search = explode(" ", $searchString); $tblsc = $modx->getFullTableName("site_content"); $tbldg = $modx->getFullTableName("document_groups"); // Get document groups for current user and write sql for document access control. if($docgrp = $modx->getUserDocGroups()) $docgrp = implode(",",$docgrp); $access = ($modx->isFrontend() ? "sc.privateweb=0":"1='".$_SESSION['mgrRole']."' OR sc.privatemgr=0"). (!$docgrp ? "":" OR dg.document_group IN ($docgrp)"); if ($searchStyle == 'partial'){ $sql = "SELECT sc.id, sc.pagetitle, sc.description, sc.content, sc.type "; // Added type so can later determine whether a resource is a weblink (aka reference) or not. $sql .= "FROM $tblsc sc LEFT JOIN $tbldg dg ON dg.document = sc.id WHERE "; if (count($search)>1 && $useAllWords){ foreach ($search as $searchTerm){ $sql .= "(sc.pagetitle LIKE '%$searchString%' OR sc.description LIKE '%$searchString%' OR sc.content LIKE '%$searchTerm%') AND "; } } else { $sql .= "(sc.pagetitle LIKE '%$searchString%' OR sc.description LIKE '%$searchString%' OR sc.content LIKE '%$searchString%') AND "; } $sql .= "sc.published = 1 AND sc.searchable=1 AND sc.deleted=0"; } else { $sql = "SELECT sc.id, sc.pagetitle, sc.description, sc.content, sc.type "; // Added type so can later determine whether a resource is a weblink (aka reference) or not. $sql .= "FROM $tblsc sc LEFT JOIN $tbldg dg ON dg.document = sc.id WHERE "; if (count($search)>1 && $useAllWords){ foreach ($search as $searchTerm){ $sql .= "MATCH (sc.pagetitle, sc.description, sc.content) AGAINST ('$searchTerm') AND "; } } else { $sql .= "MATCH (sc.pagetitle, sc.description, sc.content) AGAINST ('$searchString') AND "; } $sql .= "sc.published = 1 AND sc.searchable=1 AND sc.deleted=0"; } $sql .= " AND ($access)"; // Add access control to sql. $rs = $modx->dbQuery($sql); $limit = $modx->recordCount($rs); if($limit>0) { $SearchForm .= '
'.$newline; // pagination if ($grabMax > 0){ $numResultPages = ceil($limit/$grabMax); $resultPageLinks = ''; $resultPageLinks .= ($limit>$grabMax)? $paginationTextMultiplePages : $paginationTextSinglePage ; $resultPageLinkNumber = 1; for ($nrp=0;$nrp<$limit && $limit > $grabMax;$nrp+=$grabMax){ if($offset == ($resultPageLinkNumber-1)*$grabMax){ $resultPageLinks .= $resultPageLinkNumber; } else { $resultPageLinks .= '' . $resultPageLinkNumber . ''; } $resultPageLinks .= ($nrp+$grabMax < $limit)? $pageLinkSeparator : '' ; $resultPageLinkNumber++; } $resultPageLinks .= "".$newline; $SearchForm .= '

'; $resultsFoundText = ($limit > 1)? $resultsFoundTextMultiple : $resultsFoundTextSingle ; if ($extract) { $hits=1; $searchwords=''; foreach ($search as $words) { $searchwords.=''.$words.' '; $hits++; } $SearchForm .= sprintf($resultsFoundText,$limit,$searchwords); } else { $SearchForm .= sprintf($resultsFoundText,$limit,$searchString); } $SearchForm .= '
'.$resultPageLinks."

".$newline; } // end if grabMax // search results $useLimit = ($grabMax > 0)? $offset+$grabMax : $limit; for ($y = $offset; ($y < $useLimit) && ($y<$limit); $y++) { $moveToRow = mysql_data_seek($rs,$y); $SearchFormsrc=$modx->fetchRow($rs); if ($extract) { $text=$SearchFormsrc['content']; if (count($search)>1){ $count=1; $summary=''; foreach ($search as $searchTerm){ $summary .= PrepareSearchContent( $text, $length=200, $searchTerm ); $summary = preg_replace( '/' . preg_quote( $searchTerm, '/' ) . '/i', '\0', $summary ); $count++; } $text=$summary; } else { $search=$searchString; $text=PrepareSearchContent( $text, $length=200, $search ); $text = preg_replace( '/' . preg_quote( $searchString, '/' ) . '/i', '\0', $text ); } } $SearchForm.='
'.$newline; // Handle weblinks. $theLink = '[~'.$SearchFormsrc['id'].'~]'; $linkClass = ""; if (($SearchFormsrc['type']=='reference') && $useWeblink) { $theLink = $SearchFormsrc['content']; // Get the weblink content, which should be the URL that the link points to $linkClass = $weblinkClass; // Set the css class. } $SearchForm.='

' . $SearchFormsrc['pagetitle'] . "

".$newline; $SearchForm.=$SearchFormsrc['description']!='' ? '' . $SearchFormsrc['description'] . "".$newline : "" ; $SearchForm.='
'. $text . '
'.$newline; $SearchForm.='
'.$newline; } $SearchForm.='

'.$resultPageLinks.'

'; $SearchForm.='
'.$newline; } else { $SearchForm.='

'.$resultsIntroFailure.'

'; } // end if records found } else if (!$validSearch && isset($_POST['sub'])) { // message to show if search was performed but for something invalid $SearchForm .= '

'.$resultsIntroFailure.'

'; } else { // end if validSearch $SearchForm .= '

'.$introMessage.'

'; } // end if not validSearch } // end if showResults return $SearchForm;