// ######################################################
// 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( "'<script[^>]*>.*?</script>'si", "", $text );
		$text = preg_replace( '/{.+?}/', '', $text);
		//$text = preg_replace( '/<a\s+.*?href="([^"]+)"[^>]*>([^<]+)<\/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 .= '<form class="FSF_form" action="'.$searchAction.'" method="post">';
  $SearchForm .= ($xhtmlStrict)? '<fieldset><legend>Search</legend>' : '' ;
  // decide what goes in search box
  $searchBoxVal = ($searchString == '' && $boxText != '')? $boxText : $searchString ;
  $SearchForm .= '<input class="FSF_input" type="text" name="search" value="'.$searchBoxVal.'"  ';
  $SearchForm .= ($boxText)? 'onfocus="this.value=(this.value==\''.$boxText.'\')? \'\' : this.value ;" />' : '/>';
  $SearchForm .= '<input class="FSF_submit" type="submit" name="sub" value="'.$searchButtonText.'" />';
  $SearchForm .= ($xhtmlStrict)? '</fieldset>' : '';
  $SearchForm .= '</form>'.$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 .= '<div class="FSF_searchResults">'.$newline;

      // pagination
      if ($grabMax > 0){
        $numResultPages = ceil($limit/$grabMax);
        $resultPageLinks = '<span class="FSF_pages">';
        $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 .= '<a href="[~' . $modx->documentObject['id'] . '~]&FSF_offset=' . $nrp . '&FSF_search=' . urlencode($searchString) . '">' . $resultPageLinkNumber . '</a>';
          }
          $resultPageLinks .= ($nrp+$grabMax < $limit)? $pageLinkSeparator : '' ;
          $resultPageLinkNumber++;
        }
        $resultPageLinks .= "</span>".$newline;
        $SearchForm .= '<p class="FSF_pagination">';
        $resultsFoundText = ($limit > 1)? $resultsFoundTextMultiple : $resultsFoundTextSingle ;
        if ($extract) {
        $hits=1;
        $searchwords='';
        foreach ($search as $words) {
        $searchwords.='<span class="FSF_highlight'.$hits.'">'.$words.'</span>&nbsp;';
        $hits++;
        }
        $SearchForm .= sprintf($resultsFoundText,$limit,$searchwords);
        } else {
        $SearchForm .= sprintf($resultsFoundText,$limit,$searchString);
        }
        $SearchForm .= '<br />'.$resultPageLinks."</p>".$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', '<span class="FSF_highlight'.$count.'">\0</span>', $summary );
           $count++;
           }
           $text=$summary;
        } else {
          $search=$searchString;
          $text=PrepareSearchContent( $text, $length=200, $search );
          $text = preg_replace( '/' . preg_quote( $searchString, '/' ) . '/i', '<span class="FSF_highlight1">\0</span>', $text );
          }
        }
        $SearchForm.='<div class="FSF_result">'.$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.='<h3><a class="FSF_resultLink '.$linkClass.'" href="'.$theLink.'" title="' . $SearchFormsrc['pagetitle'] . '">' . $SearchFormsrc['pagetitle'] . "</a></h3>".$newline;
        $SearchForm.=$SearchFormsrc['description']!='' ? '<span class="FSF_resultDescription">' . $SearchFormsrc['description'] . "</span>".$newline : "" ;
        $SearchForm.='<div class="FSF_extract">'. $text . '</div>'.$newline;
        $SearchForm.='</div><!--end FlexSearchResult-->'.$newline;
      }
      $SearchForm.='<p class="FSF_pagination">'.$resultPageLinks.'</p>';
      $SearchForm.='</div><!--end FlexSearchResults-->'.$newline;
    } else {
      $SearchForm.='<p class="FSF_resultsIntroFailure">'.$resultsIntroFailure.'</p>';
    } // end if records found

  } else if (!$validSearch && isset($_POST['sub'])) {

    // message to show if search was performed but for something invalid
    $SearchForm .= '<p class="FSF_resultsIntroFailure">'.$resultsIntroFailure.'</p>';
    
  } else { // end if validSearch

    $SearchForm .= '<p class="FSF_intro">'.$introMessage.'</p>';

  } // end if not validSearch
} // end if showResults

return $SearchForm;