<?php

mb_internal_encoding("UTF-8");

$get_mb_encoding_translation = ini_get("mbstring.encoding_translation");
$get_mb_internal_encoding = ini_get("mbstring.internal_encoding");

ini_set('memory_limit', '128M');
ini_set("mbstring.internal_encoding", "UTF-8");
ini_set("mbstring.http_input", "UTF-8");
ini_set("mbstring.http_output", "UTF-8");

/* /////////////////////////////////////////////////////////////////////////////////////////////////
  KONICA MINOLTA User's Guide
  Search Rresults PHP
  Modified : 2018-02-10
  ///////////////////////////////////////////////////////////////////////////////////////////////// */



/* * ************************************
 * 定数
 */
// sharedフォルダ
define("SHARED_DIR", "shared/js/");

// sysoutフォルダ
define("JS_SYSOUT_DIR", "shared/sysout/");

// インデックスファイルPHP
define("INDEX_FILE_PHP", JS_SYSOUT_DIR . "search_index.inc");

// インデックスファイルJS
define("INDEX_FILE_JS", JS_SYSOUT_DIR . "search_index.js");

//*************************************



////////////////////////////////////////////////////////////////////////////////


/* * ************************************************************************
 * データ読み込み関数
 */


/*
 * インデックスファイル読み込み
 */

function loadIndex($name) {

	if (file_exists(INDEX_FILE_PHP)) {
		$books = "";
		$search_index = array();
		require_once(INDEX_FILE_PHP);

		//変数books読み込み
		if ("books" == $name) {
			echo $books;
		}
		//検索インデックス読み込み
		else if ("search_index" == $name) {
			return $search_index;
		}
	} else if (file_exists(INDEX_FILE_JS)) {
		$loaddata = file_get_contents(INDEX_FILE_JS);

		//変数books読み込み
		if ("books" == $name) {
			$booksPT = "/var books\s*=\s*(\[[^\n]+\]);/u";
			$booksTextArr = array();
			preg_match($booksPT, $loaddata, $booksTextArr);
			echo $booksTextArr[1];
		}
		//検索インデックス読み込み
		else if ("search_index" == $name) {

			$pat = array("/\R/u", "/;\s*var\s+search_index/u");
			$rep = array(" ", ";\nvar search_index");
			$loaddata = preg_replace($pat, $rep, $loaddata);

			$matches = array();
			
			//PP
//			preg_match("/[\$]\.extend\s*?\([^\)]+\)/u", $loaddata, $matches);
//			preg_match_all("/(search_index_[^,\s\)]+?)[,\s\)]/u", $matches[0], $matches);
//			$targerArr = $matches[1];
//			$matches = array();
//			$indexPT = "/^var (search_index_[^=]+)\s*=\s*\{.+?\}.+$/mu";
//			preg_match_all($indexPT, $loaddata, $matches, PREG_SET_ORDER);
//			$indexTextArr = array();
//			$pat2 = array("/^var search_index_[^=]+\s*=/mu", "/;$/mu");
//			$rep2 = array("", "");
//			foreach ($matches as $val) {
//				$indexTextArr[$val[1]] = preg_replace($pat2, $rep2, $val[0]);
//			}
//			$search_index = array();
//			foreach ($targerArr as $value) {
//				$search_index = array_merge($search_index, json_decode($indexTextArr[$value], true));
//			}
			
			//OP
			preg_match("/^var search_index\s*=.+$/mu", $loaddata, $matches);
			$pat2 = array("/^var search_index\s*=\s*/mu", "/;\s*$/mu");
			$rep2 = array("", "");
			$search_index = json_decode(preg_replace($pat2, $rep2, $matches[0]), true);
			
			//戻し
			return $search_index;
		}
	}
	//読み込むファイルがない場合エラー
	else {
		header("HTTP/1.0 204 Index file Not Found.");
		exit;
	}
}

/* * ************************************************************************
 * 検索クラス
 */

class search {

	private $results = array();
	private $conf = array();

	/*
	 * 1 初期実行
	 */

	public function __construct($conf) {
		if (isset($conf["sData"]["k"])) {
			foreach ($conf["sData"] as $key => $val) {
				if ($key != "k") {
					$v = (preg_match("/^(1|true)$/iu", $val)) ? true : false;
					if ($key == "ul") {
						$conf["charUpLo"] = $v;
					}
					if ($key == "zh") {
						$conf["zenHan"] = $v;
					}
				}
			}
			$this->conf = (array) $conf;

			$results = array();
			$results["keywords"] = $this->_getKeywordArr($conf["sData"]["k"]);
			$results["keywordsLength"] = count($results["keywords"]);
			$results["filters"] = (isset($conf["sData"]["f"])) ? explode(",", $conf["sData"]["f"]) : array();
			$results["filtersTitle"] = (isset($conf["sData"]["t"])) ? preg_match("/^(1|true)$/iu", $conf["sData"]["t"]) : false;

			$this->results = (array) $results;

			//検索実行
			$this->search();
		} else {
			echo '{"viewItems":[],"resultsLength":0}';
			exit;
		}
	}

	
	/*
	* 文字参照に置換 逆の場合は第二引数に"rev"を入れる
	*/
	private function _repEntity($str, $rev){
		$entityChar = array(
			"&" => array("&amp;","&#38;"), //[&]を一番最初に処理しないと2重になる
			"<" => array("&lt;","&#60;"), 
			">" => array("&gt;","&#62;"), 
			'"' => array("&quot;","&#34;"),
			"'" => array("&apos;","&#39;")
		);
		$r = $str;
		foreach ($entityChar as $key => $value) {
			if(isset($rev) && $rev != "rev"){
				$r = preg_replace("/".$key."/u", $value[0], $r);
			}else{
				$r = preg_replace('/&((nb|en|em|thin)sp|#(160|8194|8195|8201));/u', " ", $r);
				$r = preg_replace('/(' . implode("|", $value) . ')/u', $key, $r);
			}
		}
		return $r;
	}
	
	
	/*
	 * セパレータ文字配列生成
	 */

	private function _sepStr($text) {
		$conf = $this->conf;

		$temp = array();
		preg_match_all("/(" . $conf["splitStrBase"] . ")/u", $text, $temp);
		$temp = $temp[0];
		$str = explode("|", $conf["splitStrBase"]);
		if (count($temp)) {
			return array_values(array_diff($str, $temp));
		} else {
			return $str;
		}
	}

	/*
	 * 入力キーワードを配列に分離
	 */

	private function _getKeywordArr($inputWord) {
		$sep = $this->_sepStr($inputWord);
		$arr = array();
		$phraseArr = array();
		$temp = $inputWord;

		preg_match_all('/"([^"]+)"/u', $temp, $phraseArr);
		$phraseArr = $phraseArr[1];
		$temp = preg_replace('/"([^"]+)"/', $sep[0], $temp);

		$arr = preg_split("/[\s　]+/u", $temp);
		$arr = array_values(array_filter($arr, "strlen"));
		foreach ($arr as &$val) {
			if ($val == $sep[0]) {
				$val = array_shift($phraseArr);
			}
		}
		return $arr;
	}

	/*
	 * 全角半角正規表現生成　＆　エスケープ　a => (a|ａ)、ＡＢ => (Ａ|A)(Ｂ|B)、［ => (［|\[)
	 */

	private function _zenHanRegExp($str) {//**************************************
		$chars = preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY);

		foreach ($chars as &$char) {
			if (preg_match('/("|”)/u', $char)) {
				$char = '(”|")';
			} else if (preg_match("/(’|')/u", $char)) {
				$char = "(’|')";
			} else if (preg_match("/(\\\\|￥)/u", $char)) {
				$char = "(￥|\\\\)";
			} else if (preg_match("/(~|～)/u", $char)) {
				$char = "(～|~)";
			} else if (preg_match("/( |　)/u", $char)) {
				$char = "( |　)";
			} else {
				$zen = mb_convert_kana($char, "KVA");
				$han = mb_convert_kana($char, "kva");
				$char = "(" . $zen . "|" . preg_quote($han, "/") . ")";
			}
		}
		return implode("", $chars);
	}

	/*
	 * 正規表現エスケープ処理
	 */

	private function _escapeRegex($s) {
		//return preg_replace("/[\[\]{}()*+?\\^$|#;\.]/u", "\\$0", $s);
		return preg_quote($s, '/');
	}

	/*
	 * ユーザーフォント削除
	 */

	private function _ufrm($d) {
		$conf = $this->conf;
		return preg_replace($conf["ufPattern"] . "u", "", $d);
	}

	/*
	 * 検索用正規表現作成
	 */

	private function _createfindreg() {
		$conf = $this->conf;
		$results = $this->results;

		$regsFind = array();
		$keys = $results["keywords"];
		$reFlag = ($conf["charUpLo"]) ? "" : "i";
		foreach ($keys as $val) {
			try {
				$temp = "";
				if ($conf["zenHan"]) {
					$temp = $this->_escapeRegex($val);
				} else {
					$temp = $this->_zenHanRegExp($val);
				}
				$regsFind[] = "/" . $temp . "/u" . $reFlag;
			} catch (Exception $e) {
				
			}
		}
		//検索の正規表現配列をセット
		$results["regsFind"] = $regsFind;

		$this->results = (array) $results;
	}

	/*
	 * スコア計算
	 */

	private function _calcScore($hitType, $loopIni, $hitIndex, $hit) {
		$conf = $this->conf;
		$dScore = array();
		$results = $this->results;

		$demeritPoints = $hitPoint = $point = 0;

		if ($hit) {
			$demeritPoints = max($hitIndex * $conf["score"][$hitType . "_indexPoint"], $conf["score"]["dePointMax"]);
			$hitPoint = $conf["score"][$hitType] * ($results["keywordsLength"] - $loopIni);
			$point = $hitPoint + $demeritPoints;
		}
		$dScore["_" . $hitType . "_1_HitPoint"] = $hitPoint;
		$dScore["_" . $hitType . "_2_DePoint"] = $demeritPoints;
		$dScore["_" . $hitType . "_3_Formula"] = $hitPoint . " - " . abs($demeritPoints) . " = " . $point;

		$point = max($point, 0);

		$dScore["_" . $hitType . "_4_Point"] = $point;

		return array($point, $dScore);
	}

	/* **********************************************************************
	 * 2 検索実行
	 */

	private function search() {
		//検索用正規表現作成
		$this->_createfindreg();

		$conf = $this->conf;
		$results = $this->results;

		$result = array();
		$resultsIndex = array();

		//対象インデックス作成
		$searchIndex = array();
		foreach ($conf["index"] as $key => &$indexItem) {
			
			$indexItem["title"] = $this->_repEntity($indexItem["title"], "rev");
			$indexItem["text"] = $this->_repEntity($indexItem["text"], "rev");
			
			//スコア０点付加
			$indexItem["_score"] = (isset($indexItem["_score"])) ? $indexItem["_score"] : 0;
			//スコア詳細配列
			$indexItem["_scoreDetail"] = array();
			//全対象 or 絞り込み対象
			if (count($results["filters"]) == 0 || in_array($indexItem["book_key"], $results["filters"])) {
				$searchIndex[$key] = $indexItem;
			}
		}

		function margeDitailScore($a, $b) {
			foreach ($b as $key => $val) {
				$a[$key] = $val;
			}
			return $a;
		}

		//検索
		foreach ($results["regsFind"] as $findregIni => $findReg) {//キーワードループ
			//and検索の第２検索語以降
			if ($conf["andSearch"] && $findregIni > 0) {
				$searchIndex = $result;
				$result = array();
			}
			//and検索の第１検索語、もしくはor検索
			else {
				$result = $resultsIndex;
			}

			foreach ($searchIndex as $key => $indexItem) {//対象インデックスループ
				$hit = false;
				$score = 0;

				//スコア詳細オブジェクト設定
				$ditailScore = array();

				//タイトルサーチ
				if (preg_match($findReg, $this->_ufrm($indexItem["title"]))) {
					$temp = preg_split($findReg, $indexItem["title"], 2);
					$hitIndex = mb_strlen($temp[0]);
					$hit = true;
					list($point, $dScore) = $this->_calcScore("title", $findregIni, $hitIndex, true);
					$score += $point;
					$ditailScore = margeDitailScore($ditailScore, $dScore);
				} else {
					list($point, $dScore) = $this->_calcScore("title", $findregIni, 0, false);
					$ditailScore = margeDitailScore($ditailScore, $dScore);
				}
				//「タイトルのみ」のチェックがない場合は本文をサーチ
				if (!$results["filtersTitle"]) {
					if (preg_match($findReg, $this->_ufrm($indexItem["text"]))) {
						$temp = preg_split($findReg, $indexItem["text"], 2);
						$hitIndex = mb_strlen($temp[0]);
						$hit = true;
						list($point, $dScore) = $this->_calcScore("text", $findregIni, $hitIndex, true);
						$score += $point;
						$ditailScore = margeDitailScore($ditailScore, $dScore);
					} else {
						list($point, $dScore) = $this->_calcScore("text", $findregIni, 0, false);
						$ditailScore = margeDitailScore($ditailScore, $dScore);
					}
				}

				//ヒット
				if ($hit) {
					//カテゴリー加算
					$addPoint = 0;
					if (in_array($indexItem["book_key"], $conf["score"]["add_pointCT"])) {
						$addPoint = $conf["score"]["add_point"];
						$score += $addPoint;
					}
					$ditailScore["_add_Point"] = $addPoint;

					//スコア加算
					$indexItem["_score"] += $score;
					$ditailScore["Word"] = $results["keywords"][$findregIni];
					$ditailScore["Word_Total"] = $score;

					$indexItem["_scoreDetail"][$findregIni] = $ditailScore;

					//結果オブジェクトへマージ
					$result[$key] = $indexItem;
				}
			}
			$resultsIndex = $result;
		}

		$results["resultsLength"] = count($result);
		$this->results = (array) $results;

		$this->_viewItems($result);
	}

	/*
	 * 結果項目の説明トリミング
	 */

	private function _clamping($item) {
		$conf = $this->conf;
		$results = $this->results;

		//$ufRe = new RegExp(conf.ufPattern.source, "g");
		$userFont = array();
		$clamp = array("front" => 0, "rear" => -1);
		$hitPoint = 0;
		$item["clamp"] = array("front" => false, "rear" => false);

		//ユーザーフォントをセパレーター文字に置き換えておく。文字数計算のため１文字に置き換えておく。
		$re = preg_replace("/(^[^\/]*|[^\/]*$)/u", "", $conf["ufPattern"]) . "u";
		if (preg_match($re, $item["text"])) {
			$sepStr = $this->_sepStr($item["text"]);
			$item["text"] = preg_replace_callback($re, function($matches)use($sepStr, &$userFont) {
				$userFont[] = $matches[0];
				return $sepStr[0];
			}, $item["text"]);
		}

		foreach ($results["regsFind"] as $reg) {
			$temp = preg_split($reg, $item["text"], 2);
			if (isset($temp[0])) {
				$hitPoint = max($clamp["front"], mb_strlen($temp[0]));
				break;
			}
		}
		if (mb_strlen($item["text"]) == $hitPoint) {
			$hitPoint = 0;
		}

		$clamp["front"] = max($hitPoint - $conf["clampFrontPoint"], 0);
		$clamp["front"] = min($clamp["front"], max(mb_strlen($item["text"]) - $conf["clampDescLength"], 0));
		$clamp["rear"] = $clamp["front"] + $conf["clampDescLength"];

		$item["clamp"]["front"] = $clamp["front"] > 0;
		$item["clamp"]["rear"] = $clamp["rear"] < mb_strlen($item["text"]);

		$clampStrings = array(
			"before" => mb_substr($item["text"], 0, $clamp["front"])
			, "clamp" => mb_substr($item["text"], $clamp["front"], $conf["clampDescLength"])
			, "after" => mb_substr($item["text"], $clamp["rear"])
		);

		if (count($userFont)) {
			$ufNum = array();
			foreach ($clampStrings as $str) {
				$matchs = array();
				$ufNum[] = preg_match_all("/" . $sepStr[0] . "/u", $str, $matchs);
			}

			for ($i = $ufNum[0], $len = $ufNum[0] + $ufNum[1]; $i < $len; $i++) {
				$clampStrings["clamp"] = preg_replace("/" . $sepStr[0] . "/u", $userFont[$i], $clampStrings["clamp"], 1);
			}
		}
		$item["text"] = $clampStrings["clamp"];

		return $item;
	}

	/*
	 * ソート
	 */

	private function _sortResults($resultsIndex) {
		$arr = array();
		$i = 0;
		foreach ($resultsIndex as $key => &$val) {
			$val["_file"] = $key;
			$val["i"] = $i;
			$arr[] = $val;
			$i++;
		}

		function cmp($a, $b) {
			if ($a["_score"] > $b["_score"]) {
				return -1;
			} else if ($a["_score"] < $b["_score"]) {
				return 1;
			} else {
				return $a["i"] < $b["i"] ? -1 : 1;
			}
		}

		uasort($arr, "cmp");

		foreach ($arr as &$val) {
			unset($val["i"]);
		}

		return $arr;
	}

	/*
	 * 3 検索結果を戻す
	 */

	private function _viewItems($resultsIndex) {
		$conf = $this->conf;
		$results = $this->results;

		$vCheck = $conf["visitedCheck"];
		$viewItems = array();

		if ($results["resultsLength"]) {
			$sortResultsArr = array_values($this->_sortResults($resultsIndex));

			if ($vCheck["filter"]) {//選択した結果項目のみ表示の処理
				$checkResultsArr = array();

				foreach ($sortResultsArr as $val) {
					foreach ($vCheck["check"] as $val2) {
						if (preg_match("/^" . $val2 . "/u", $val["_file"])) {
							$checkResultsArr[] = $val;
							break;
						}
					}
				}

				$maxPage = ceil(count($checkResultsArr) / $conf["itemByPage"]);
				$getPage = $conf["sData"]["pc"];
				$loopStart = max((min($getPage, $maxPage) - 1), 0) * $conf["itemByPage"];
				$loopStop = $loopStart + $conf["itemByPage"];
				for ($i = $loopStart; $i < $loopStop && isset($checkResultsArr[$i]); $i++) {
					$viewItems[] = $this->_clamping($checkResultsArr[$i]);
				}
			} else {//通常表示の処理
				$loopStart = ($conf["sData"]["p"] - 1) * $conf["itemByPage"];
				$loopStop = $loopStart + $conf["itemByPage"];
				for ($i = $loopStart; $i < $loopStop && isset($sortResultsArr[$i]); $i++) {
					$viewItems[] = $this->_clamping($sortResultsArr[$i]);
				}
			}
		}

		$returnItem = array(
			"viewItems" => $viewItems
			, "resultsLength" => $results["resultsLength"]
		);

		echo json_encode($returnItem);
		exit;
	}

}

//End class search
// ************************************************************************





/* * **********************************************************************
 * 処理振り分け
 */

function setget() {
	global $get_mb_encoding_translation, $get_mb_internal_encoding;
	if (isset($_GET)) {
		$get_data = $_GET;
	} else {
		header("Location: search_result.html");
	}
	foreach ($get_data as $key => &$v) {
		if ($get_mb_encoding_translation == 1 && $get_mb_internal_encoding) {
			$v = mb_convert_encoding($v, "UTF-8", $get_mb_internal_encoding);
		}
		$v = mb_convert_encoding($v, "UTF-8", "auto");
		if ($key == "conf") {
			$v = json_decode($v, true);
		}
	}
	return $get_data;
}

$get = setget();


if (isset($get["type"])) {

	if ("getBooks" == $get["type"]) {
		if (isset($get["name"])) {
			loadIndex($get["name"]);
		} else {
			header("HTTP/1.0 501 Request Error.");
			exit;
		}
	} else if ("getViewItems" == $get["type"]) {
		if (isset($get["conf"])) {
			$conf = $get["conf"];

			//Stringを数値データに変換
			foreach ($conf as $key => &$val) {
				if (preg_match("/^(clampDescLength|clampFrontPoint|itemByPage)$/u", $key)) {
					$val = (int) $val;
				}
				if (preg_match("/^(sData|score)$/u", $key)) {
					foreach ($val as $k2 => &$v2) {
						if (preg_match("/^(p|pc|title|title_indexPoint|text|text_indexPoint|dePointMax|add_point)$/u", $k2)) {
							$v2 = (int) $v2;
						}
					}
				}
			}

			$conf["index"] = loadIndex("search_index");
			
			new search($conf);
		} else {
			header("HTTP/1.0 501 Request Error.");
			exit;
		}
	} else {
		header("HTTP/1.0 501 Request Error.");
		exit;
	}
} else {
	header("HTTP/1.0 501 Request Error.");
	exit;
}


