config_file = $dataDir . '/data/_site/config_search.php';
		$this->GetConfig();
		if( $this->Admin() ){
			return;
		}
		//admin popup or visitor
		$_REQUEST += array('q' => '');
		if( \gp\tool::LoggedIn() && isset($_REQUEST['gpx_content']) && $_REQUEST['gpx_content'] == 'gpabox' ){
			$this->AdminSearch();
		}else{
			$this->Search();
		}
	}
	public function AdminSearch(){
		global $langmessage;
		$this->gpabox = true;
		$this->show_stats = true;
		$this->search_hidden = true;
		echo '
';
		echo '
';
		echo '
';
		echo '
';
		if( !empty($_REQUEST['q']) ){
			$this->RunQuery();
		}
		echo '
';
		if( $this->search_count > 0 ){
			echo '
' . $this->search_count . ' files searched
';
		}
		echo '
';
		echo '
 ';
	}
	public function Search(){
		global $langmessage;
		$placeholder = $langmessage['Search'] . '…';
		$custom_text = \gp\tool\Output::SelectText('Search');
		if( $custom_text != '' ){
			$placeholder = htmlspecialchars($custom_text) . '…';
		}
		echo '';
		echo '
' . \gp\tool\Output::GetAddonText('Search') . '
';
		echo '';
		if( \gp\tool::LoggedIn() ){
			$this->search_hidden = true;
		}else{
			$this->search_hidden = $this->search_config['search_hidden'];
		}
		$this->RunQuery();
		if( \gp\tool::LoggedIn() ){
			echo \gp\tool::Link(
				'special_gpsearch',
				$langmessage['configuration'],
				'cmd=config',
				['data-cmd' => 'gpabox', 'class' => 'search-config-link']
			);
		}
		echo '';
	}
	public static function Gadget(){
		global $langmessage;
		$query = '';
		if( isset($_GET['q']) ){
			$query = $_GET['q'];
		}
		$placeholder = $langmessage['Search'] . '…';
		$custom_text = \gp\tool\Output::SelectText('Search');
		if( $custom_text != '' ){
			$placeholder = htmlspecialchars($custom_text) . '…';
		}
		echo '';
		echo \gp\tool\Output::GetAddonText('Search');
		echo '
';
		echo '';
	}
	public function RunQuery(){
		if( !empty($_REQUEST['q']) ){
			$this->SearchPattern();
			$this->SearchPages();
			\gp\tool\Plugins::Action('Search', array($this));
		}
		$this->ShowResults();
	}
	public function ShowResults(){
		global $langmessage;
		if( !count($this->results) ){
			if( !empty($_REQUEST['q']) ){
				echo '';
				echo \gp\tool\Output::GetAddonText($langmessage['search_no_results']);
				echo '
';
			}
			return;
		}
		$this->RemoveDups();
		usort( $this->results, array($this, 'sort') );
		$total			= count($this->results);
		$len			= 20;
		$total_pages	= ceil($total/$len);
		$current_page	= self::ReqPage('pg', $total_pages );
		$start			= $current_page * $len;
		$end			= min($start+$len, $total);
		$this->results = array_slice($this->results, $start, $len, true);
		echo '';
		echo sprintf($langmessage['SHOWING'], ($start + 1), $end, $total);
		echo '
';
		echo '';
		foreach($this->results as $result){
			echo '
';
			echo isset($result['link']) ? $result['link'] : \gp\tool::Link(
					$result['slug'], 
					$result['label'], 
					'highlight=' . rawurlencode($_REQUEST['q'])
				); // $result['query']
			if( $this->show_stats ){
				echo ' ' .  $result['matches'] . '' ;
			}
			echo '
';
			echo $result['content'];
			if( $this->show_stats ){
				echo ' ';
				echo $result['matches'] . ' match(es) out of ' . $result['words'] . ' words ';
				echo ' ';
			}
			echo '';
		}
		echo '
 ';
		$attr = 'class="page-link"';
		if( $this->gpabox ){
			$attr .= ' data-cmd="gpabox"';
			echo '
';
		}
		$query = 'q=' . rawurlencode($_REQUEST['q']);
		self::PaginationLinks($current_page, $total_pages, 'special_gpsearch', $query, 'pg', $attr);
	}
	/**
	 * Get the requested page number
	 *
	 * @param string $key
	 * @param int $total_pages
	 */
	public static function ReqPage($key='pg', $total_pages=null){
		if( isset($_REQUEST[$key]) ){
			$pg = (int)$_REQUEST[$key];
			if( !is_null($total_pages) && $total_pages > 0 ){
				$pg	= min($pg, $total_pages - 1);
			}
			return max(0,$pg);
		}
		return 0;
	}
	/**
	 * Pagination links
	 *
	 */
	public static function PaginationLinks($current_page, $total_pages, $slug, $query, $page_key='pg', $attr=''){
		global $langmessage;
		if( $total_pages < 1 ){
			return;
		}
		$prev_text		= $langmessage['Previous'];
		$next_text		= $langmessage['Next'];
		$current_text	= $langmessage['Current Page'];
		echo '';
	}
	public static function PaginationLink($slug, $label, $query, $page_key, $attr, $page){
		if( $page > 0){
			$query .= '&' . $page_key . '=' . $page;
		}
		echo '' . \gp\tool::Link($slug, $label, $query, $attr) . '';
	}
	/**
	 * Remove duplicate matches
	 *
	 */
	public function RemoveDups(){
		$links = array();
		foreach($this->results as $key => $result){
			$link	= isset($result['url']) ? $result['url'] : \gp\tool::GetUrl($result['slug'], $result['query']);
			// $link	= mb_strtolower($link);
			if( in_array($link,$links) ){
				unset($this->results[$key]);
			}else{
				$links[] = $link;
			}
		}
	}
	public function Sort($resulta,$resultb){
		return $resulta['strength'] < $resultb['strength'];
	}
	public function SearchPattern(){
		// $query = mb_strtolower($_REQUEST['q']);
		$query = $_REQUEST['q'];
		// Search for the exact query when it is doubled quoted
		if (substr($query, 0, 1) == '"' && substr($query, -1) == '"') {
			$query = substr($query, 1, -1);
			$words = array($query);
		} else {
			preg_match_all("/\S+/", $query, $words);
			$words = array_unique($words[0]);
		}
		$sub_pattern1 = $sub_pattern2 = array();
		foreach($words as $word){
			$sub_pattern1[] = '\b' . preg_quote($word, '#') . '\b';
			$sub_pattern2[] = preg_quote($word, '#');
		}
		$this->search_pattern = '#(?:(' . implode('|', $sub_pattern1) . ')|(' . implode('|', $sub_pattern2) . '))#Siu';
	}
	public function Admin(){
		global $langmessage;
		if( !\gp\tool::LoggedIn() ){
			return false;
		}
		$this->page->admin_links[] = array(
			'special_gpsearch',
			$langmessage['configuration'],
			'cmd=config',
			[ 'data-cmd' => 'gpabox', 'class' => 'admin-link-search-config' ]
		);
		$cmd = \gp\tool::GetCommand();
		switch($cmd){
			case 'save_config':
				if( $this->SaveConfig() ){
					break;
				}
				return true;
			case 'config':
				$this->Config($this->search_config);
				return true;
		}
		return false;
	}
	/**
	 * Get the search configuration
	 *
	 */
	public function GetConfig(){
		$this->search_config 	= \gp\tool\Files::Get($this->config_file, 'search_config');
		$this->search_config	+= array('search_hidden' => false);
	}
	public function SaveConfig(){
		global $langmessage;
		if( isset($_POST['search_hidden']) ){
			$search_config['search_hidden'] = true;
		}else{
			$search_config['search_hidden'] = false;
		}
		if( \gp\tool\Files::SaveData($this->config_file, 'search_config', $search_config) ){
			msg($langmessage['SAVED']);
			$this->search_config = $search_config;
			return true;
		}
		msg($langmessage['OOPS']);
		$this->Config($_POST);
		return false;
	}
	public function Config($array=array()){
		global $langmessage, $addonFolderName, $gp_index;
		echo '' . $langmessage['Search'] . ' » ' . $langmessage['configuration'] . '
';
		echo '';
	}
	public function SearchPages(){
		global $gp_index;
		ob_start();
		foreach($gp_index as $title => $index){
			if( \gp\tool::SpecialOrAdmin($title) === false ){
				$this->SearchPage($title, $index);
			}
		}
		ob_get_clean();
	}
	public function SearchPage($title, $index){
		global $gp_menu, $gp_titles;
		//search hidden?
		if( !$this->search_hidden && !isset($gp_menu[$index]) ){
			return;
		}
		//private pages
		if( !\gp\tool::LoggedIn() && isset($gp_titles[$index]['vis']) ){
			return;
		}
		$full_path		= \gp\tool\Files::PageFile($title);
		$file_sections	= \gp\tool\Files::Get($full_path, 'file_sections');
		if( !$file_sections ){
			return;
		}
		$content		= \gp\tool\Output\Sections::Render($file_sections, $title, \gp\tool\Files::$last_stats);
		$label			= \gp\tool::GetLabel($title);
		$this->FindString($content, $label, $title);
	}
	public function FindString(&$content, $label, $slug, $link_query=''){
		$this->search_count++;
		//search all of the content include html
		// $content = mb_strtolower($content);
		$content		= $label . ' ' . $content;
		$match_count	= preg_match_all($this->search_pattern, $content, $matches, PREG_OFFSET_CAPTURE);
		if( $match_count < 1 ){
			return;
		}
		$words 			= str_word_count($content);
		$strength		= $this->Strength($matches,$words);
		//format content, remove html
		$label_len		= strlen($label);
		$content		= preg_replace('##si', '', $content); 
		$content		= preg_replace('##si', '', $content); 
		$content		= substr($content, $label_len);
		$content		= str_replace('>', '> ', $content);
		$content		= preg_replace('/\s+/', ' ', $content);
		$content		= strip_tags($content);
		preg_match($this->search_pattern, $content, $matches, PREG_OFFSET_CAPTURE);
		$start			= isset($matches[0][1]) ? $matches[0][1] : 0;
		//find a space at the beginning to start from
		$i = 0;
		do{
			$i++;
			$start_offset = $i * 10;
			$start = max(0, $start - $start_offset);
			$trimmed = substr($content, $start, 300);
			$space = strpos($trimmed, ' ');
			if( $space < $start_offset ){
				$content = substr($trimmed, $space);
				break;
			}
		}while( ($start-$start_offset) > 0);
		//find a space at the end
		if( strlen($content) > 250 ){
			$space2 = strpos($content, ' ', $space + 220);
			if( $space2 > 0 ){
				$content = substr($content, 0, $space2);
			}
		}
		$result = array();
		$result['label']		= $label;
		$result['slug']			= $slug;
		$result['query']		= $link_query;
		$result['content']		= preg_replace($this->search_pattern, '\1\2', $content);
		$result['words']		= $words;
		$result['matches']		= $match_count;
		$result['strength']		= $strength;
		$this->results[]		= $result;
	}
	function Strength($matches, $len){
		//space around search terms
		$factor = 0;
		foreach((array)$matches[1] as $match){
			if( is_array($match) && $match[1] >= 0 ){
				$factor += 3;
			}
		}
		//no space around search term
		foreach((array)$matches[2] as $match){
			if( is_array($match) && $match[1] >= 0 ){
				$factor += 1;
			}
		}
		$strength = $factor / $len;
		return round($strength, 8);
	}
}