<?php
	require_once(dirname(__FILE__).'/class.search.parent.php');

	class AKB_SEARCH extends AKB_SEARCH_PARENT
	{
		var $_query = '';
		var $_query1 = '';
		var $_cats = array();
		var $_page = 1;
		var $_pages = 0;
		var $start = 0;
		var $_end = 0;
		var $maxResults = RESULTS_PER_PAGE;
		var $constraintFunctions = array (
			'constrainByRated',
		);
		var $searchCategories = Array();

		/**
		* Constructor
		* Setup class specific variables
		*
		* @return void
		*/
		function AKB_SEARCH()
		{
			parent::AKB_SEARCH_PARENT();
			// Get the search query (if any)
			$this->_SetPage();
			$this->_SetQuery();
			$this->_SetBounds();

			$GLOBALS['HideMessage'] = "none";
		}

		/**
		* _GetPages
		* Get the number of pages of search results
		*
		* @return int
		*/
		function _GetPages()
		{
			return $this->_pages;
		}

		/**
		* _SetBounds
		* Set the start and end of search results
		*
		* @return void
		*/
		function _SetBounds()
		{
			if ($this->_GetPage() == 1) {
				$this->start = 0;
				$this->_end = RESULTS_PER_PAGE;
			} else {
				$this->start = ($this->_GetPage()-1) * RESULTS_PER_PAGE;
				$this->_end = $this->start + RESULTS_PER_PAGE;
			}
		}

		/**
		* _GetStart
		* Return the start row of search results
		*
		* @return int
		*/
		function _GetStart()
		{
			return $this->start;
		}

		/**
		* _GetEnd
		* Return the end row of search results
		*
		* @return int
		*/
		function _GetEnd()
		{
			return $this->_end;
		}

		/**
		* _SetPage
		* Set the pageid for the current page
		*
		* @return void
		*/
		function _SetPage()
		{
			$arrVals = array();
			$vals = $_SERVER['REQUEST_URI'];

			$pos = strpos($vals, '?');
			$vals = substr($vals, $pos+1, strlen($vals));
			parse_str($vals, $arrVals);

			if (isset($arrVals['page'])) {
				$this->_page = $arrVals['page'];
			}

			if (!isset($arrVals['as']) && !isset($_POST['SearchType'])) {
				$_SESSION['SearchCategories'] = null;
			}
		}

		/**
		* _GetPage
		* Return the page number of the current page
		*
		* @return int
		*/
		function _GetPage()
		{
			return $this->_page;
		}

		/**
		* _GetQuery
		* Get the search string
		*
		* @return string
		*/
		function _GetQuery()
		{
			return $this->_query;
		}

		/**
		* _SetQuery
		* Set the query variable for the search string
		*
		* @return true
		*/
		function _SetQuery()
		{
			// Work out the search query
			if (!$GLOBALS['cleanUrls'] && isset($_GET['q'])) {
				$this->_query = $_GET['q'];
			} else if (!empty($_REQUEST['Query'])) {
				$this->_query = $_REQUEST['Query'];
			} else {
				// Get the part of the url after the last / but before the
				// query string
				$ruri_base = $_SERVER['REQUEST_URI'];
				$request_uri = (substr($ruri_base,-1,strlen($ruri_base)) == "/") ? substr($ruri_base,0,-1) : $ruri_base;
				$url_parts = parse_url($request_uri);
				$path_parts = explode('/', $url_parts['path']);
				$query = array_pop($path_parts);
				if (empty($query) || $query == 'search' || $query == 'search.php') {
					$query = '';
				}
				$this->_query = urldecode($query);
			}
			$this->_query1 = strtolower($this->_query);

			// Work out which categories the search is restricted to
			if (isset($_POST['Categories']) && is_array($_POST['Categories'])) {
				if (!in_array(0, $_POST['Categories'])) {
					$additional_cats = array();

					$this->_cats = array_map(array($GLOBALS['AKB_DB'], 'Quote'), $_POST['Categories']);
					$this->searchCategories = array_map(array($GLOBALS['AKB_DB'], 'Quote'), $_POST['Categories']);

					// Search any subcategories the user has access to of the
					// categories they have selected to search
					foreach ($this->searchCategories as $catid) {
						$additional_cats = array_merge($additional_cats, $GLOBALS['AKB_CLASS_HELPER']->getAccessibleCats($catid));
					}
					$this->searchCategories = array_unique(array_merge($this->searchCategories, $additional_cats));
					$_SESSION['SearchCategories'] = $this->searchCategories;
				} else {
					$_SESSION['SearchCategories'] = null;
				}
			} elseif (isset($_SESSION['SearchCategories']) && $_SESSION['SearchCategories'] !== null) {
				$this->searchCategories = $_SESSION['SearchCategories'];
				$this->_cats = $_SESSION['SearchCategories'];
			}
			$this->_query = $GLOBALS['AKB_CLASS_TEMPLATE']->DisablePlaceholders($this->_query);
			$this->_query1 = $GLOBALS['AKB_CLASS_TEMPLATE']->DisablePlaceholders($this->_query1);
		}

		/**
		* HandlePage
		* The default page handler
		*
		* @return void
		*/
		function HandlePage()
		{
			$GLOBALS['HideSubList'] = 'none';
			$GLOBALS['HideErrorPanel'] = 'none';
			$GLOBALS['HideSearchDivider'] = 'none';
			$GLOBALS['CatCrumbs'] = "none";

			if (isset($_GET['as'])) {
				$GLOBALS['SearchFormAction'] = GetUrl('search', null, 'as=1');
			} else {
				$GLOBALS['SearchFormAction'] = GetUrl('search');
			}
			
			$this->ShowResults();			
		}

		/**
		* ShowResults
		* Show the search results
		*
		* @return void
		*/
		function ShowResults()
		{
			$numResults = 0;
			$searchData = '';
			$arrDone = array();
			$arrOutput = array();
			$SNIPPETS = '';
			$maxStringLen = 80;
			if (empty($this->_cats) || $this->_cats[0] == 0) {
				$GLOBALS['AllSel'] = 'selected="selected"';
				unset($this->_cats);
				$this->_cats = array();
			}

			$GLOBALS['HidePanels'] = array (
				'SubmitCommentPanel',
				'ViewQuestionPanel',
				'QuestionSubscribePanel',
				'QuestionUnsubscribePanel',
				'QuestionAuthPanel',
			);
			
			$this->searchString = $this->_query;
			
			//Sort out the custom fields
			$this->customFieldData = $this->processCustomFieldData();
			$this->doCustomFields = (bool)$this->customFieldData;			
			
			$this->questSearch(true);

			$GLOBALS['Query'] = htmlspecialchars($this->_query, ENT_QUOTES, $GLOBALS['charset']);

			$numResults = count($this->searchResults);
			if (isset($_POST['ratedOp'])) {
				$GLOBALS['ratedOps'] = $GLOBALS['AKB_CLASS_HELPER']->getNumericCompareOptions($_POST['ratedOp']);
			} else {
				$GLOBALS['ratedOps'] = $GLOBALS['AKB_CLASS_HELPER']->getNumericCompareOptions();
			}
			if (isset($_POST['rated'])) {
				$GLOBALS['ratedOptions'] = $GLOBALS['AKB_CLASS_HELPER']->getRatedBracketOptions($_POST['rated']);
			} else {
				$GLOBALS['ratedOptions'] = $GLOBALS['AKB_CLASS_HELPER']->getRatedBracketOptions();
			}

			$this->SaveSearchString($this->searchString, $numResults);
			if (isset($_SESSION['SearchWords'])) {
				unset($_SESSION['SearchWords']);
			}
			$_SESSION['SearchWords'] = array();

			$count = $this->start;

			$GLOBALS['Start'] = $this->_GetStart()+1;

			$output = "";

			if ($this->searchResults) {
				$output = $GLOBALS['AKB_CLASS_TEMPLATE']->GetSnippet('SearchResultHeader');
			}
			foreach ($this->searchResults as $r) {
					if ($count < $this->_end && $count >= $this->start && $count < $numResults) {
						$GLOBALS['questionid'] = $r['questionid'];
						$GLOBALS['urlsafetitle'] = $GLOBALS['AKB_CLASS_HELPER']->_MakeURLSafe($r['title']);
						$GLOBALS['summary'] = $GLOBALS['AKB_CLASS_TEMPLATE']->DisablePlaceholders($this->GetSummary($r['answer']));
						$GLOBALS['title'] = htmlspecialchars($r['title'], ENT_QUOTES, $GLOBALS['charset']);

						$arr = Array();

						if (isset($_SESSION['SearchWords'])) {
							foreach ($_SESSION['SearchWords'] as $word) {
								$arr[] = Array(
									'word' => $word,
									'token' => uniqid('')
								);
							}
						}

						if (strlen($GLOBALS['title']) > $maxStringLen) {
							$GLOBALS['title'] = substr($GLOBALS['title'], 0, $maxStringLen) . '...';
						}
						$GLOBALS['title'] = $GLOBALS['AKB_CLASS_TEMPLATE']->DisablePlaceholders(ReplaceString($GLOBALS['title'], $arr, 'SearchTermCallback'));

						$GLOBALS['Alt'] = $GLOBALS['AKB_CLASS_TEMPLATE']->DisablePlaceholders(htmlspecialchars($r['title'], ENT_QUOTES, $GLOBALS['charset']));
						
						if ($r['plugin'] == 'Question') {
							$GLOBALS['href'] = GetUrl('question', $r['questionid']);
							if ($GLOBALS['cleanUrls']) {
								$GLOBALS['href'] .= $GLOBALS['AKB_CLASS_HELPER']->_MakeURLSafe($r['title']);
							}
							$GLOBALS['types'] = GetLang('article');
						} else {
							$q = new API_QUESTION();
							$q -> load($r['questionid']);
							$q -> title = $title =	htmlspecialchars($q -> title, ENT_QUOTES, $GLOBALS['charset']);
							$data = base64_encode(sprintf('%s|%s', $r['questionid'], basename($r['href'])));
							$GLOBALS['href'] = $GLOBALS['pathToKB'] . '/getattachment.php?data=' . urlencode($data);
							$link = GetUrl('question', $r['questionid']);
							if ($GLOBALS['cleanUrls']) {
								$link .= $GLOBALS['AKB_CLASS_HELPER']->_MakeURLSafe($q->title);
							}
							$displayTitle = $q->title;
							
							if (strlen($displayTitle) > $maxStringLen) {
								$displayTitle = substr($displayTitle, 0, $maxStringLen) . '...';
							}
							$link = '<a title="'.$q->title.'" style="font-size:11px;" href="' . $link . '">'.$displayTitle.'</a>';
							$GLOBALS['types'] = sprintf(GetLang('attachment'), $link);
						}
						// Retrieve the images for results
						$GLOBALS['Result_Image'] = $r['plugin'].'.gif';
						
						$output .= $GLOBALS['AKB_CLASS_TEMPLATE']->GetSnippet('SearchResult');
					}
					$count++;
			}

			if ($this->searchResults) {
				$output .= $GLOBALS['AKB_CLASS_TEMPLATE']->GetSnippet('SearchResultFooter');
			}
			
			// Set how many pages of results were found
			$this->_pages = ceil($numResults / RESULTS_PER_PAGE);

			$GLOBALS['SearchGrid'] = $output;

			if ($this->searchType == 1) {
				$GLOBALS['WordSel'] = 'SELECTED';
			} else {
				$GLOBALS['PhraseSel'] = 'SELECTED';
			}

			$GLOBALS['SearchCategoryOptions'] = $GLOBALS['AKB_CLASS_HELPER']->GetCategoryOptions($this->_cats);

			if (($this->_query == '') && (!$this->doCustomFields)) {
				$GLOBALS['HideSearchResults'] = 'none';
				$GLOBALS['HideSeperator'] = 'none';
			} else {
				if (!$this->doCustomFields) {
					$GLOBALS['ResultHeading'] = sprintf(GetLang('searchSearchResultsFor'), htmlspecialchars($this->_query, ENT_QUOTES, $GLOBALS['charset']));
				} else {
					$customFieldsStr = "";
					
					$GLOBALS['SearchLink'] = GetUrl("search");
					
					//We need to get all the appropriate custom field objects here as we need to show at least the name.
					$_cfObj = new API_CUSTOMFIELD();
					$cfObjArray = $_cfObj->loadMulti($this->customFieldData,false,true);
					
					foreach ($this->customFieldData as $cfid=>$cfDataArray) {
						if (is_array($cfDataArray)) {
							if ($cfDataArray[0] != "") {
								$customFieldsStr .= $cfObjArray[$cfid]->name.": ";
							} else {
								continue;
							}
							
							//Loop through each of the individual pieces of custom field data to culminate the string.
							foreach ($cfDataArray as $cfData) {
								$GLOBALS['CFLink'] = "cfid=".$cfid."&cfdata=".$cfData;
								$GLOBALS['CFLinkText'] = $cfData;
								$customFieldsStr .= $GLOBALS['AKB_CLASS_TEMPLATE']->GetSnippet('CustomFieldsFELink').", ";
							}
							
							$customFieldsStr = substr($customFieldsStr,0,-2)." ";
						} else if ($cfDataArray != "") {
							$customFieldsStr .= $cfObjArray[$cfid]->name.": ";
							
							$GLOBALS['CFLink'] = "cfid=".$cfid."&cfdata=".$cfDataArray;
							$GLOBALS['CFLinkText'] = $cfDataArray;
							$customFieldsStr .= $GLOBALS['AKB_CLASS_TEMPLATE']->GetSnippet('CustomFieldsFELink');
						}
					}
					
					$GLOBALS['ResultHeading'] = sprintf(GetLang('searchSearchResultsForCustomField'), $customFieldsStr);
				}

				if ($numResults == 0) {
					if (isset($_SESSION['SearchWords'])) {
						unset($_SESSION['SearchWords']);
					}
					$GLOBALS['SearchResultIntro'] = GetLang('searchNoResults');
				} else {
					if ($numResults == 1) {
						$GLOBALS['SearchResultIntro'] = GetLang('searchNumberOfQuestionsForQuery1');
					} else {
						$GLOBALS['SearchResultIntro'] = sprintf(GetLang('searchNumberOfQuestionsForQuery2'), $numResults);
					}
				}
			}

			// Do we need to hide the advanced search panel?
			$arrVals = array();
			$vals = $_SERVER['REQUEST_URI'];
			$pos = strpos($vals, '?');
			$vals = substr($vals, $pos+1, strlen($vals));
			parse_str($vals, $arrVals);

			if (!isset($arrVals['as']) && !isset($_POST['SearchType'])) {
				$GLOBALS['HideAdvancedSearch'] = 'none';
				$GLOBALS['HideSeperator'] = 'none';
			}
			
			// Setup the navigation
			$adv = isset($arrVals['as']) ? 'as=1' : '';
			$seperator = (isset($arrVals['as']) || !$GLOBALS['cleanUrls']) ? '&amp;' : '?';
			$link = GetUrl('search', $this->_GetQuery(), $adv);

			$GLOBALS['Nav'] = BuildNav($link, $this->_GetPages(), $this->_GetPage(), $seperator);
	
			if ($this->GetTotalCustomField() < 1) {
				$GLOBALS['HidePanels'][] = 'SearchCustomFields';
			}
			$title = GetLang('hpSearch').' :: '.strip_tags($GLOBALS['Query']);
			$GLOBALS['AKB_CLASS_TEMPLATE']->SetPageTitle($title);
			$GLOBALS['AKB_CLASS_TEMPLATE']->SetTemplate('Questions');
			$GLOBALS['AKB_CLASS_TEMPLATE']->ParseTemplate();
		}
		
		/**
		* processCustomFieldData
		* Process the custom fields from post data.
		* Specifically this deals with data from POST vars (Advanced Search) and querystring (From the Article Details Page)
		*
		* @return void
		*/		
		function processCustomFieldData() {
			if ((isset($_REQUEST['cfmulti'])) || ((isset($_REQUEST['cfid'])) && (isset($_REQUEST['cfdata'])))) {
				//Check to make sure we should be doing anything.
				//Needed to add cfmulti here as PHP has issues with isset on html arrays. is_array works fine...
				
				//Need to fork out here, if the ISSelectReplacement was used then this gets precedence over cfid
				if (isset($_REQUEST['ISSelectReplacement_cfid'])) {
					$cfidMixed = $_REQUEST['ISSelectReplacement_cfid'];
				} else if (isset($_REQUEST['cfid'])) {
					$cfidMixed = $_REQUEST['cfid'];
				} else {
					//Shouldn't even be in here.
					return false;
				}
				
				if (is_array($cfidMixed)) {
					//If there are multiple ID's to search on.
					
					//We do some validation here, if this is an array but there is no data we want to return false.
					$empty = true;
					foreach ($cfidMixed as $cfArray) {
						if (is_array($cfArray)) {
							foreach ($cfArray as $data) {
								if (trim($data) != "") {
									$empty = false;
									break;
								}
							}
						} else {
							if (trim($cfArray) != "") {
								$empty = false;
								break;
							}
						}
					}
					
					if ($empty) {
						return false;						
					}
					return $cfidMixed;					
				} else {
					//Just one set to search on.
					$cfid = (int)$_REQUEST['cfid'];
					$cfdata = trim($_REQUEST['cfdata']);
					
					if (($cfid) && ($cfdata != "")) {
						return array($cfid=>array($cfdata));
					} else {
						return false;
					}
				}
			}
			
			return false;
		}	
		
		/**
		* GetTotalCustomField
		* Get the number of existance custom fields in KB
		*
		* @return Integer The number of total custom fields
		*/
		function GetTotalCustomField () {
			$query = sprintf("SELECT COUNT(customfieldid) as num from %scustomfields", $GLOBALS['tablePrefix']);
			$totCF = $GLOBALS['AKB_DB']->FetchOne($query);
			return $totCF;
		}

		/**
		* Cleans raw search term of any special characters
		*
		* @param string &$itemName
		*
		* @return void
		*/
		function CleanSearchWords(&$itemName) {
			$itemName = str_replace("?","",$itemName);
			$itemName = str_replace(",","",$itemName);
		}

		/**
		* Get the text to display as the summary for a question
		*
		* @param string $test the text to summarise
		*
		* @return string The summary
		*/
		function GetSummary($text)
		{
			$contextSummary = '';
			$summaryLength = 200;
			$wordsFound = 0;

			$text = preg_replace('%<(style|script).*?</\1>%si', ' ', $text);
			$text = preg_replace('%<(br|div|td|tr|li|p).*?>%si', ' ', $text);
			$text = str_replace('&nbsp;', ' ', $text);
			$text = str_replace('\n', ' ', $text);
			$text = strip_tags($text);
			$arrWords = explode(" ", $this->searchString);
			$appearingWords = 0;
			$offsets = array();
			$start = 0;

			array_walk($arrWords,array(&$this, "CleanSearchWords"));

			// Work out how many of our keywords appear in the summary
			foreach ($arrWords as $key => $word) {
				if (empty($word)) {
					continue;
				}

				$pos = strpos($text, $word);
				if ($pos !== false) {
					$appearingWords++;
					$offsets[$word] = $pos;
				} else {
					unset($arrWords[$key]);
				}

				if (!in_array($word, $_SESSION['SearchWords'])) {
					$_SESSION['SearchWords'][] = $word;
				}
			}

			asort($offsets);

			// Work out how long each summary segment should be
			if ($appearingWords == 0) {
				$length = $summaryLength;
			} else {
				$length = ceil($summaryLength / $appearingWords);
			}

			// Grab each of our segments
			foreach ($offsets as $word => $pos) {
				// If this word is covered in another words summary, skip it
				if (!isset($offsets[$word])) {
					continue;
				}

				$start = max($pos - ceil(0.5 * $length), 0);
				foreach ($offsets as $w => $p) {
					if ($w == $word) {
						continue;
					}
					// If this word is in the summary of another word
					if (($start + $length) > $p) {
						unset($offsets[$w]);
					}
				}

				if ($start > 0) {
					$contextSummary .= "...";
				}

				if ($GLOBALS['charset'] == 'utf-8') {
					$contextSummary .= utf8_substr($text, $start, $length);
				} else {
					$contextSummary .= substr($text, $start, $length);
				}
			}

			// If no words appear in the summary (i.e. the title or keywords matched,
			// just display the start of the summary otherwise highlight the search terms
			if ($appearingWords == 0) {
				if ($GLOBALS['charset'] == 'utf-8') {
					$contextSummary = utf8_substr($text, 0, $summaryLength);
				} else {
					$contextSummary = substr($text, 0, $summaryLength);
				}
			} else {
				$arr = Array();
				if (isset($_SESSION['SearchWords'])) {
					foreach ($_SESSION['SearchWords'] as $word) {
						$arr[] = Array(
							'word' => $word,
							'token' => uniqid('')
						);
					}
				}
				$contextSummary = ReplaceString($contextSummary, $arr, 'SearchTermCallback');
			}
			if ($start + $length < strlen($text)) {
				$contextSummary .= "...";
			}

			return $contextSummary;
		}

		/**
		* SaveSearchString
		* Save the search string to the database so we can get stats about it
		*
		* @param string $str the string to save to the database
		* @param int $num the number of search results returned for that string
		*
		* @return bool false if str is empty or num isnt an int
		*/
		function SaveSearchString($str='', $num=0)
		{
			// If the string is empty return
			if (empty($str)) {
				return false;
			}

			// If the num isn't an integer
			if ((int) $num != $num) {
				return false;
			}

			$query = sprintf("SELECT amount from %ssearchhistory WHERE searchstring='%s'", $GLOBALS['tablePrefix'], $GLOBALS['AKB_DB']->Quote($str));
			$amount = $GLOBALS['AKB_DB']->FetchOne($query);

			if ($amount == 0) {
				$query = sprintf("INSERT INTO %ssearchhistory (searchstring, numresults) VALUES ('%s', %d)", $GLOBALS['tablePrefix'], $GLOBALS['AKB_DB']->Quote($str), $num);
			} else {
				$query = sprintf("UPDATE %ssearchhistory SET amount=amount+1, numresults=%d where searchstring='%s'", $GLOBALS['tablePrefix'], $num, $GLOBALS['AKB_DB']->Quote($str));
			}
			$GLOBALS['AKB_DB']->Query($query);
		}

		/**
		* constrainByRated
		* generate an sql fragment for handling constrining a search by a rating
		*
		* @return string the string to do the constraint
		*/
		function constrainByRated()
		{
			// Make sure there is a rating to compare to
			if (!isset($_POST['rated']) || $_POST['rated'] == '') {
				return '';
			}

			// Make sure the rating is valid
			if (!in_array($_POST['rated'], array('All', '0', '25', '50', '75'))) {
				return '';
			}

			// Make the vars available for when we print the results
			$GLOBALS['rated'] = $_POST['rated'];

			if ($_POST['rated'] == 'All') {
				$query = '';
			} else {
				$query = ' AND posvotes/(posvotes+negvotes)*100 BETWEEN '.$_POST['rated'].' AND '.($_POST['rated']+25)."\n";
			}

			return $query;
		}
	}
?>
