見出し画像

お絵かき掲示板 POTI-boardに検索機能を追加してみた

スレッド単位で検索するサンプルプログラムができました

外部プログラム search.php による検索機能の追加はできていました。
しかし、POTI-board本体を改造して検索可能にする事はできませんでした。

その解決策を探る事数日。
やっとスレッド単位の検索ができるサンプルプログラムを作る事ができました。

こんな検索ならどんな掲示板にもあるじゃない…と言われてしまったらそれまでなのですが…。
以下はやった事。

静的HTMLからPHPによる動的表示へ

POTI-boardは記事を静的HTML(拡張子 .html)で出力する掲示板です。
しかし、それでは検索結果を反映する事ができないので、動的に表示できるようにしました。

1ページに表示する件数

POTI-boardはツリーログを読み込んで上から順に10スレッド分の記事を表示しています。
その中から検索結果と一致したものだけ表示すると10スレッド中の1スレッドだったり2スレッドだったり、あるいは何も表示されないページができてしまいます。
それでは検索してふるいにかけてもうまく表示できないので別な方法で1ページに10スレッド表示する仕組みを作りました。

スレッドの数だけ全件ループ

スレッドの数だけ全件ループして、その中から検索にヒットしたものを配列にいれて貯めていく処理を追加しました。

あとは、その配列からその1ページ分だけ出力すれば…。
それなりにメモリを使う事になるので、最大で120スレッドで収まるようにしました。

コード

記事表示の箇所を元に検索用に書き直したコードを載せておきます。
GitHubにも公開していているので、なにもここに貼る必要はないのかもしれませんが…。

// 記事検索
function search(){
	global $path;
	$_dat['oya']=[];
	$query=filter_input(INPUT_GET,'q');
	$query=urldecode($query);
	$query=htmlspecialchars($query,ENT_QUOTES,'utf-8',false);
	$radio =filter_input(INPUT_GET,'radio',FILTER_VALIDATE_INT);
	//クエリを検索窓に入ったままにする
	$dat['query']=$query;
	//ラジオボタンのチェック
	$dat['radio_chk1']='';//作者名
	$dat['radio_chk2']='';//完全一致
	$dat['radio_chk3']='';//本文題名	
	$query_l='&q='.urlencode($query);//クエリを次ページにgetで渡す
	if($query!==''&&$radio===3){//本文題名
		$query_l.='&radio=3';
		$dat['radio_chk3']='checked="checked"';
	}
	elseif($query!==''&&$radio===2){//完全一致
		$query_l.='&radio=2';
		$dat['radio_chk2']='checked="checked"';	
	}
	elseif($query!==''&&($radio===null||$radio===1)){//作者名
		$query_l.='&radio=1';
		$dat['radio_chk1']='checked="checked"';
	}
	else{//作者名	
		$query_l='';
		$dat['radio_chk1']='checked="checked"';
	}
	$dat['query_l']=$query_l;
	$page = filter_input(INPUT_GET, 'page',FILTER_VALIDATE_INT);
	$page=($page===null) ? 0 : $page;

	$tree = file(TREEFILE);
	$line = file(LOGFILE);
	$lineindex = get_lineindex($line); // 逆変換テーブル作成
	if(!$lineindex){
		error(MSG019);
	}
	$dat=array_merge($dat,form());
		$oya = 0;	//親記事のメイン添字
	foreach($tree as $i =>$val){//ツリー全件ループ
			if(!isset($tree[$i])){
				continue;
			}
			$treeline = explode(",", rtrim($tree[$i]));
			$disptree = $treeline[0];
			if(!isset($lineindex[$disptree])) continue;   //範囲外なら次の行
			$j=$lineindex[$disptree]; //該当記事を探して$jにセット
			$res = create_res($line[$j], ['pch' => 1]);
		
			//検索処理 スレッドの親
			$search_oya=search_res($res,$query,$radio);
			$res['disp_resform'] = check_elapsed_days($res); // ミニレスフォームの表示有無
			// ミニフォーム用
			$resub = USE_RESUB ? 'Re: ' . $res['sub'] : '';
			// レス省略
			$skipres = '';
			$s=count($treeline) - DSP_RES;
			if(ADMIN_NEWPOST&&!DSP_RES) {$skipres = $s - 1;}
			elseif($s<1 || !DSP_RES) {$s=1;}
			elseif($s>1) {$skipres = $s - 1;}
			//レス画像数調整
			if(RES_UPLOAD){
				//画像テーブル作成
				$imgline=array();
				foreach($treeline as $k => $disptree){
					if($k<$s){//レス表示件数
						continue;
					}
					if(!isset($lineindex[$disptree])) continue;
					$j=$lineindex[$disptree];
					list(,,,,,,,,,$rext,,,$rtime,,,) = explode(",", rtrim($line[$j]));
					$resimg = $path.$rtime.$rext;
					$imgline[] = ($rext && is_file($resimg)) ? 'img' : '0';
				}
				$resimgs = array_count_values($imgline);
				if(isset($resimgs['img'])){//未定義エラー対策
				while($resimgs['img'] > DSP_RESIMG){
					while($imgline[0]='0'){ //画像付きレスが出るまでシフト
						array_shift($imgline);
						$s++;
					}
					array_shift($imgline); //画像付きレス1つシフト
					$s++;
					$resimgs = array_count_values($imgline);
				}
				}
				if($s>1) {$skipres = $s - 1;}//再計算
			}
			// 親レス用の値
			$res['tab'] = $oya + 1; //TAB
			$logmax=(LOG_MAX>=1000) ? LOG_MAX : 1000;
			$res['limit'] = ($lineindex[$res['no']] >= $logmax * LOG_LIMIT / 100) ? true : false; // そろそろ消える。
			$res['skipres'] = $skipres ? $skipres : false;
			$res['resub'] = $resub;
			
			//レス作成
			$rres=array();
			$search_res=false;
			foreach($treeline as $k => $disptree){
				if($k<$s){//レス表示件数
					continue;
				}
				if(!isset($lineindex[$disptree])) continue;
				$j=$lineindex[$disptree];
				$_res = create_res($line[$j], ['pch' => 1]);
				//検索処理 スレッドのレス 
				if(!$search_res){
					$search_res=search_res($res,$query,$radio);
				}
	
				$rres[$oya][] = $_res;
			}
			if($search_oya||$search_res){//親またはレスが検索ヒットしていたら
				$_dat['oya'][$oya] = $res;//スレッドの親を配列に入れる
	
				// レス記事一括格納
				if($rres){//レスがある時
					$_dat['oya'][$oya]['res'] = $rres[$oya];//スレッドのレスを配列に入れる
				}
	
				$oya++;
				if($oya>(120-1)){//負荷が気になるので120スレッド以上はブレイク
					break;
				}
			}

		clearstatcache(); //キャッシュをクリア
	}
	$amount_of_one_page=[];
	foreach($_dat['oya'] as $k=> $val){//$_dat['oya']にはレスも含まれる
		if($k>=$page){
			$amount_of_one_page[]=$val;//全検索結果から1ページ分の検索結果を取得
		}	
		if($k>=$page+PAGE_DEF-1){//1ページ分取得してブレイク
			break;
		}
	}
	$count_thread=count($_dat['oya']);//みつかった全スレッドの数
	$dat['oya']=$amount_of_one_page;
	$dat['notfound']=false;
	if(!$count_thread){
		$dat['notfound']=true;//検索しても見つからなかった時に表示
	}
		$prev = $page - PAGE_DEF;
		$next = $page + PAGE_DEF;
		// 改ページ処理
		if($prev >= 0){
			$dat['prev'] = ($prev == 0) ? PHP_SELF.$query_l : '?page='.$prev.$query_l;
		}
		$paging = "";
		$dat['paging']=false;
		//表示しているページが20ページ以上または投稿数が少ない時はページ番号のリンクを制限しない
		$showAll = ($count_thread <= PAGE_DEF * 21 || $page >= PAGE_DEF*21);
		for($i = 0; $i < ($showAll ? $count_thread : PAGE_DEF * 22); $i += PAGE_DEF){
			$pn = $i ? $i / PAGE_DEF : 0; // page_number
			$paging .= ($page === $i)
				? str_replace("<PAGE>", $pn, NOW_PAGE) // 現在ページにはリンクを付けない
				: str_replace("<PURL>", ($i ? PHP_SELF."?page=".$i.$query_l:PHP_SELF."?page=0".$query_l),
					str_replace("<PAGE>", $i ? ($showAll || ($i !== PAGE_DEF * 21) ? $pn : "≫") : $pn, OTHER_PAGE));
					$dat['paging'] = $paging;
					if($oya >= PAGE_DEF && $count_thread > $next){
						$dat['next'] = PHP_SELF.'?page='.$next.$query_l;
					}
		}
		//改ページ分岐ここまで

		$dat['resform'] = RES_FORM ? true : false;
		 htmloutput(SKIN_DIR.MAINFILE,$dat);
}

search.phpの検索処理のコードを導入

親とレスの検索で二度同じ処理をしているので、その箇所を関数化しました。
search.phpのコードを再利用しました。

	//検索処理を関数化
    function search_res($res,$query,$radio){
    $query=mb_convert_kana($query, 'rn', 'UTF-8');
	$query=str_replace(array(" ", " "), "", $query);
	$query=str_replace("〜","~",$query);//波ダッシュを全角チルダに
	if($radio===1||$radio===2||$radio===null){
		$s_name=mb_convert_kana($res['name'], 'rn', 'UTF-8');//全角英数を半角に
		$s_name=str_replace(array(" ", " "), "", $s_name);
		$s_name=str_replace("〜","~", $s_name);//波ダッシュを全角チルダに
	}
	else{
		$s_sub=mb_convert_kana($res['sub'], 'rn', 'UTF-8');//全角英数を半角に
		$s_sub=str_replace(array(" ", " "), "", $s_sub);
		$s_sub=str_replace("〜","~", $s_sub);//波ダッシュを全角チルダに
		$s_com=mb_convert_kana($res['com'], 'rn', 'UTF-8');//全角英数を半角に
		$s_com=str_replace(array(" ", " "), "", $s_com);
		$s_com=str_replace("〜","~", $s_com);//波ダッシュを全角チルダに
	}
	return ($query===''||//空白なら
	$query!==''&&$radio===3&&stripos($s_com,$query)!==false||//本文を検索
	$query!==''&&$radio===3&&stripos($s_sub,$query)!==false||//題名を検索
	$query!==''&&($radio===1||$radio===null)&&stripos($s_name,$query)!==false||//作者名が含まれる
	$query!==''&&($radio===2&&$s_name===$query)//作者名完全一致
	);
}

全角英数と半角英数を区別しません。
たとえば、ABC(全角)とABC(半角)どちらで検索してもヒットします。

$query=str_replace("〜","~",$query);//波ダッシュを全角チルダに

波ダッシュと全角チルダは、よく似ていて目視では区別がつかないのに、別の文字コードなので、どちらで検索しても検索ヒットするようにしました。

ここでは検索のクエリも、検索する対象も全角チルダに統一しています。

この仕様は

search.phpと同じです。

検索対応版

とりあえず、POTI-board本体を改造して検索処理を追加する事はできました。


現時点ではあくまでもサポートもなにもないサンプルですが、ダウンロードはこちらから…。
お絵かき掲示板を新規したいからはこのサンプルではなく、改二をご利用ください。

お絵かき掲示板を新規設置したい方はこちら


お絵かき掲示板を新規設置したい方はこちらから。
POTI-board改二 (サポートあり版)のダウンロードはこちらです。


設置サポート掲示板もありますので、どうぞよろしくお願いいたします。


この記事が気に入ったらサポートをしてみませんか?