<?php // (2024.12.20, 차재복, Cha Jae Bok, http://www.ktword.co.kr)
# 세션 스타트 (매 웹페이지 마다 필요)
    session_start(); 
# db 접속 (dbi)
	include_once "../base_utils/db_conn.php";
# (Ajax) 전달 파라미터 및 관련 함수 호출 목록
    $script_file = substr($_SERVER['SCRIPT_NAME'],strrpos($_SERVER['SCRIPT_NAME'],"/")+1);
//    if($script_file!='naviFetch.php') exit('ajax 형태가 아닌, 임의 외부 php로부터의 호출 의심');
	// (type, choice, id, no)
    $type = $_REQUEST['type'];
    $choice = $_REQUEST['choice'];
    $id = $_REQUEST['id'];
        if ( isset($_REQUEST['id']) and !empty($id) and !is_numeric($id) or $id<0 ) exit('잘못된 fetch id'); // 해킹방지 (수치>0)
    $no = $_REQUEST['no'];
        if ( isset($_REQUEST['no']) and !empty($no) and !is_numeric($no) or $no<0 ) exit('잘못된 fetch no'); // 해킹방지 (수치>0)
        $no = substr($no,0,10);  // 해킹 방지 (글자 수 제한)
	if($choice=='treeView' and isset($_REQUEST['id'])) {
		if (!isset($_REQUEST['type']) or $type == 'word' or $type == 'multi') 
            ajaxFetchChildWord ($id, $dbi);
		else if ($type == 'source') {
            include_once "naviFetch_source.php";
            ajaxFetchChildFile ($id, $dbi);
        } else if ($type == 'src_tree') {
            include_once "naviFetch_source.php";
            ajaxFetchChildSrcTree ($id, $dbi);
        } else if ($type == 'manual') ajaxFetchChildManual ($id, $dbi);
		else if ($type == 'db_table') ajaxFetchChildDB ($id, $dbi);
    } else if ($choice == 'ajaxFetchDetailSeparate' and isset($_REQUEST['no'])) {
        include_once '../navigation/naviFetch_utils.php';
        ajaxFetchDetailSeparate($no, $dbi);
    } else if ($choice == 'ajaxFetchWord' and isset($_REQUEST['no'])) {
        ajaxFetchWord($no, $dbi);
    // 분류 검색 
	} else if ($choice == 'bunryuSearch' and isset($_REQUEST['sh'])) {
	    // (해킹방어) 긴 쿼리 스트링이면 무조건 거부
    	if (strlen($_SERVER['QUERY_STRING'])>120) exit;
		// 해킹방어
	    $sh = $_REQUEST['sh'];
		if (strtolower($sh) !== 'c++') 
//        $sh = urldecode($sh);                                       // url decode
		$sh = strip_tags($sh);										// tag 제거
		$sh = mb_substr(trim($sh),0,40,'utf-8');					// 좌 우 공백 제거 및 글자수 제한
		$sh = str_replace(array('  ','\''),array(' ',' '),$sh);		// 연속 공백 제거
		$sh = str_ireplace('union all',' ',$sh);					// union all 제거
//        bunryu_search ($sh, $dbi);
        bunryu_search_v2 ($sh, $dbi);   // sql injection 해킹 공격 방어 (prepare statement)
    } else if ($choice == 'ajaxFetchSourceView' and isset($_REQUEST['id'])) {
        include_once "naviFetch_source.php";
        ajaxFetchSourceView($id, $dbi);
    } else if ($choice == 'srcScanDir') {
        include_once "naviFetch_source.php";
        srcScanDir($dbi);
    // 소스 검색 
	} else if ($choice == 'srcSearch' and isset($_REQUEST['sh'])) {
        include_once "naviFetch_source.php";
	    // (해킹방어) 긴 쿼리 스트링이면 무조건 거부
    	if (strlen($_SERVER['QUERY_STRING'])>120) exit;
		// 해킹방어
	    $sh = $_REQUEST['sh'];
		if (strtolower($sh) !== 'c++') 
        $sh = urldecode($sh);                                       // url decode
		$sh = strip_tags($sh);										// tag 제거
		$sh = mb_substr(trim($sh),0,40,'utf-8');					// 좌 우 공백 제거 및 글자수 제한
		$sh = str_replace(array('  ','\''),array(' ',' '),$sh);		// 연속 공백 제거
		$sh = str_ireplace('union all',' ',$sh);					// union all 제거
        srcSearch($sh, $dbi);   // sql injection 해킹 공격 방어
    // id,no => path,name (ajaxIdNo4PathName)
	} else if ($choice == 'ajaxIdNo4PathName' and (isset($_REQUEST['id']) or isset($_REQUEST['no']))) {
        ajaxIdNo4PathName($id,$no,$dbi);
    // default
    } else if ($script_file=='naviFetch.php') {
		echo json_encode( array('err_msg'=>'해당되는 선택 사항 없음'), JSON_UNESCAPED_UNICODE);
    }
# (Ajax : 용어해설 분류) 용어해설 분류 id에 대해, 해당 관련 no들과 자식 id들을 쿼리하여, JSON 변환시켜 출력 
	// (대상 테이블 : gubun_tree_v2, book_idx)
function ajaxFetchChildWord ($id, $dbi) {
	$query = "(select 'no' as item_type, titlename as name, no as id, 1 as rank, list_ord as ord, '0' as child, yoyak,
                    '0' as id_list 
					from book_idx where tree_id=$id)
				union all
			  (select 'id' as item_type, name, id, 2 as rank, sub_seq as ord, child, yoyak, 
                    path2node_v2 as id_list  
					from gubun_tree_v2 where parent=$id)
			  order by rank,ord,name";
	$result = mysqli_query($dbi, $query);
		if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
	if (!empty($err_msg)){
		$return = array('err_msg'=>$err_msg);
	} else {
		while (	$matched = mysqli_fetch_assoc($result) ) {
			$data[] = array('item_type'=>$matched['item_type'], 'id'=>$matched['id'], 'name'=>$matched['name'], 'child'=>$matched['child'], 'desc'=>$matched['yoyak'], 'parent' => $id, 'seq' =>$matched['ord'], 
            'idlist' =>$matched['id_list']);
		}
		if (count($data)<1) $return = array('err_msg'=>'해당 id 없음!');
		else $return = array('data' => $data);
	}
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
# (Ajax : 소스 관리 메뉴얼) 메뉴얼(reform) id에 대해, 해당 자식 id들을 쿼리하여, JSON 변환시켜 출력 
	// (대상 테이블 : reform)
function ajaxFetchChildManual ($id, $dbi) {
	$query = "select id,name,child,yoyak from reform where parent=$id order by sub_seq";
	$result = mysqli_query($dbi, $query);
		if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
	if (!empty($err_msg)){
		$return = array('err_msg'=>$err_msg);
	} else {
		while (	$matched = mysqli_fetch_assoc($result) ) {
			$data[] = array('id'=>$matched[id], 'name'=>$matched[name], 'child'=>$matched[child], 'desc'=>$matched[yoyak]);
		}
		if (count($data)<1) $return = array('err_msg'=>'해당 id 없음!');
		else $return = array('data' => $data);
	}
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
# (Ajax : db 테이블 및 칼럼) database table을 쿼리하여, JSON 변환시켜 출력 
	// (대상 테이블 : information_schema.tables)
function ajaxFetchChildDB ($id, $dbi) {
	if(!empty($_SESSION['db_name'])) $db_name = $_SESSION['db_name'];
	else $db_name = 'test';
//	$db_name = 'test';
	$query = "select (@row_number:=@row_number + 1) as id, 
						table_name as name, 
						table_comment as yoyak, 
						'1' as child,
						'table' as item_type
				from (select @row_number:=0) as t, information_schema.tables
				where table_schema='{$db_name}'";
	$result = mysqli_query($dbi,$query);
		if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
	if (!empty($err_msg)){
		$return = array('err_msg'=>$err_msg);
	} else {
		while (	$matched = mysqli_fetch_assoc($result) ) {
			// db 테이블
			if($id == 0) {
				$data[] = array('id'=>$matched['id'], 'name'=>$matched['name'], 'child'=>$matched['child'], 'desc'=>$matched['yoyak'], 'item_type' => $matched['item_type']);
			// 특정 테이블 내 칼럼
			} else if ($id == $matched['id']) {
				$query = "select (@row_number:=@row_number + 1) as id,
								concat(column_name,' : ',column_type) as name, 
								column_comment as yoyak,
								'0' as child,
								'field' as item_type	
							from (select @row_number:=0) as t, information_schema.columns 
							where table_name = '".$matched['name']."' 
									and table_schema = '{$db_name}'
							order by name";
				$result = mysqli_query($dbi,$query);
					if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
				while (	$matched = mysqli_fetch_assoc($result) ) {
					$data[] = array('id'=>$matched['id'], 'name'=>$matched['name'], 'child'=>$matched['child'], 'desc'=>$matched['yoyak'], 'item_type' => $matched['item_type']);
				}
			}
		}
		$return = array('data' => $data);
	}
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
# (PHP included) 해당 no(leaf node)로부터 path 가져오기 
	// (대상 테이블 : gubun_tree_v2, book_idx)
function phpFetchPath ($no, $dbi) {
	if(empty($no)) return;
	$query = "select 
					c.id as leaf_id, c.titlename as leaf_name, c.path2node_v2 as full_path,
					d.id as sub_id, d.name as sub_name, d.path2node_v2 as sub_path,
					e.no as no_1st, e.list_ord
				from 
					(select a.titlename,a.no,a.list_ord,b.id,b.name,b.path2node_v2 from book_idx a left join gubun_tree_v2 b on a.tree_id=b.id where a.no=$no) c 
					left join 
					gubun_tree_v2 d 
					on find_in_set(d.id,c.path2node_v2)
                    left join
                    book_idx e
                    on d.id=e.tree_id and e.list_ord=1 
				order by full_path,sub_path,list_ord";
	$result=mysqli_query($dbi,$query);
		if (mysqli_errno($dbi)) {echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n";}
	while (	$matched = mysqli_fetch_assoc($result) ) {
		// 적용 필드 (5) : leaf_id, leaf_name, sub_id, sub_name, no_1st
		$name = ( $matched['sub_id']=='0' ? 'Top' : $matched['sub_name'] );
//		$path_lines[$matched['leaf_id']]['upper_name'] = $matched['leaf_name'];
//		$path_lines[$matched['leaf_id']]['leaf_id'] = $matched['leaf_id'];
        if($matched['leaf_id'] == $matched['sub_id']) {
            $path_lines[$matched['leaf_id']]['upper_id'] = $matched['sub_id'];
            $path_lines[$matched['leaf_id']]['upper_name'] = $matched['sub_name'];
            $path_lines[$matched['leaf_id']]['sub_path'] = $matched['sub_path'];
        }
        $path_lines[$matched['leaf_id']]['url_str'][$matched['sub_id']] =
				"<span class='subMenu' data-id='".$matched['sub_id']."' data-direction='v' style='position:relative;'>
					<span class='bullet'>▷</span>
					<span class='title'>{$name}</span>
				 </span> 
                 ";
	}
/*
echo "<pre>"; 
var_dump($path_lines);
echo "</pre>";
exit;
*/
	$path = '';
	if(count($path_lines) > 0) 
		foreach($path_lines as $key => $value) {
			$path .= implode('',$value['url_str']);
			$path .= '<br>';
		}
	else 
		$path .= "해당 id에 대한 path 없음";
	return array('output'=>$path,'src_arr'=>$path_lines);
}
// 분류 검색
function bunryu_search ($sh, $dbi) {
    if(mb_strlen($sh)<2) {
        echo '최소 2자 이상';
        return;
    }
    if(mb_strlen($sh)>15) {
        echo '최대 15자 미만';
        return;
    }
    $sh = mysqli_real_escape_string($dbi,$sh);
	// 한글 (1),영어 (0) 구분
	$lang = ( preg_match("/[\x{ac00}-\x{d7a3}][\x{ac00}-\x{d7a3}]/u", $sh) ? 1 : 0 );
    if($lang==1) {  // 한글
        $sh_nospace = str_replace(' ','',$sh);
        $where_phrase = "REPLACE(a.name,' ','') like '%{$sh_nospace}%'
                            or REPLACE(b.titlename,' ','') like '%{$sh_nospace}%' 
                            or REPLACE(c.word,' ','') like '%{$sh_nospace}%'";
    } else {        // 영어
        if(mb_strlen($sh) > 3) {
            $where_phrase = "a.name like '%{$sh}%' or b.titlename like '%{$sh}%' or c.word like '%{$sh}%'";
        } else if(mb_strlen($sh) <= 3) {
            $where_phrase = "a.name like '{$sh}' or b.titlename like '{$sh}' or c.word like '{$sh}' or c.word like '{$sh} %'";
        }
    }
    $query = "select a.id,a.name,a.yoyak,a.path2node_v2,b.titlename,b.no,
                    @path:=getpath_v2(a.id),
                    substring_index(substring_index(@path,'|',2),'|',-1) as pre_ord,
                    substring_index(substring_index(@path,'|',3),'|',-1) as path_str
                    from gubun_tree_v2 a left join book_idx b on a.id=b.tree_id left join dict_word_list c on b.no=c.no 
                    where {$where_phrase}
                    order by pre_ord";
	$result=mysqli_query($dbi,$query);
		if (mysqli_errno($dbi)) {echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n";}
    $str = '';
    $prev = '';
    $i = 0;
	while (	$matched = mysqli_fetch_assoc($result) ) {
        if($prev_titlename == $matched['titlename']) continue;
        if($prev != $matched['name']) {
            if($i!=0) {
                $str = $str.' . . . ';
                $str = $str."</span>";
                $str = $str.'<br>';
            }
            $prev = $matched['name'];
            $str = $str."<span class='idPathFocusEvent' data-idpath='{$matched['path2node_v2']}' 
                            style='cursor:pointer;'>";
                $str = $str.($j+1).') ';
//                $str = $str.$matched['name'];
                $str = $str."<span style='text-decoration:underline'>".str_replace('::','>',$matched['path_str'])."</span>";
            $str = $str."   :   ";
            $j = $j + 1;
        } 
        if(!empty($matched['titlename'])) {
            $str = $str.$matched['titlename'].',   ';
            $prev_titlename = $matched['titlename'];
        }
        $i = $i + 1;
    }
    if(mysqli_num_rows($result) <= 0) {
        echo "검색 실패";
    } else {
        $str = $str.' . . . ';
        $str = $str."</span>";
        echo $str;
    }
}
// 분류 검색 ver.2 (sql injection 해킹 공격 방어)
function bunryu_search_v2 ($sh, $dbi) {
    if(mb_strlen($sh)<2) {
        echo '최소 2자 이상';
        return;
    }
    if(mb_strlen($sh)>20) {
        echo '최대 20자 미만';
        return;
    }
    $exclude = '/[.?*|^${}]/';  // '/[.?\+*|^$[\](){}]/'
    $sh = preg_replace($exclude, ' ', $sh);
//echo $sh;
//return;
    $sh = mysqli_real_escape_string($dbi, $sh);
    // sql 특정 구문을 php로 동적 구현
    $pattern_1 = '';    // where 구문에 들어갈 정규식 패턴 1
    $pattern_2 = '';    // where 구문에 들어갈 정규식 패턴 2
    $where = '';        // where 구문
    $placeholder = '';  // stmt에서 바인딩될 변수들의 데이터 유형을 나타내는 문자열
    $bindingArray = []; // stmt에서 바인딩될 문자열 배열
    sql_para($sh, $placeholder, $bindingArray, $score, $pattern_1, $pattern_2, $where);
//echo $placeholder;
//echo strlen($placeholder).' '.count($bindingArray);
//return;
    $query = "select a.id,a.name,a.yoyak,a.path2node_v2,b.titlename,b.no,c.word,
                    @path:=getpath_v2(a.id),    # 재귀적(recursive)
                    substring_index(substring_index(@path,'|',2),'|',-1) as pre_ord,
                    substring_index(substring_index(@path,'|',3),'|',-1) as path_str,
                    {$score} as priority
                    from gubun_tree_v2 a left join book_idx b on a.id=b.tree_id left join dict_word_list c on b.no=c.no 
                    where {$where}
                    order by
                        priority desc,
                        pre_ord, b.list_ord";
    $stmt = mysqli_prepare($dbi, $query);
//	    if (mysqli_errno($dbi)) { echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
	    if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
    mysqli_stmt_bind_param($stmt, $placeholder, ...$bindingArray);
        if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
    mysqli_stmt_execute($stmt);
//        if (mysqli_errno($dbi)) { echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
        if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
    $result = mysqli_stmt_get_result($stmt);
//        if (mysqli_errno($dbi)) { echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
        if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
    $prev_name = '';
    $j = 0; 
	while (	$matched = mysqli_fetch_assoc($result) ) {
        $name = $matched['name'];
        $titlename = $matched['titlename'];
        $word = $matched['word'];
        // 분류명 
        if($prev_name != $name) {
            $path_str_parts = explode('::', $matched['path_str']); // 문자열 분리
            $last_name = end($path_str_parts); // 마지막 항목 추출, 배열의 마지막 요소 가져오기
                $str_added = ''; 
                // href
                $str_added .= "<a href=\"javascript:idPathFocusDirect('".$matched['path2node_v2']."','','{$last_name}')\"
                    class='clickSpread' data-idpath='{$matched['path2node_v2']}' data-name='{$last_name}'
                    style='cursor:pointer;'>";  // idPathFocusEvent
                    // path 목록
                    $str_added .= "<span style='text-decoration:underline'>";
                        $str_added .= str_replace('::','>',$matched['path_str']);
                    $str_added .= "</span>";
                // 빈 칸
                $str_added .= "</a>   :   ";
            $str .= $str_added;
            $lines[$name]['path_html'] = $str_added;
            $lines[$name]['path_idpath'] = $matched['path2node_v2'];
            $lines[$name]['path_leafname'] = $last_name;
            if ($matched['priority'] > 0) $lines[$name]['priority'] = $matched['priority'];
            $prev_name = $name;
            $j = $j + 1;    // 줄 번호
        }
        
        // 대표명
        if($prev_titlename != $titlename) {
            // 대표명
            $str_added = ''; 
            $str_added .= "<a 
                        href=\"javascript:idPathFocusDirect('".$matched['path2node_v2']."','".$matched['no']."','".$matched['titlename']."')\" 
                        class='clickSpread' ";
            $str_added .= " title=''>";
            $str_added .= $titlename."</a>,   ";
            $str .= $str_added;
            $lines[$name]['items'][$titlename]['word_html'] = $str_added;
            $prev_titlename = $titlename;
        }
        // 용어명
        if(empty($lines[$name]['items'][$titlename]['no'])) {
            $lines[$name]['items'][$titlename]['no'] = $matched['no'];
            $lines[$name]['items'][$titlename]['name'] = $word;
        } 
        if(empty($lines[$name]['items'][$titlename]['word_list']))
            $lines[$name]['items'][$titlename]['word_list'] = $word;
        else 
            $lines[$name]['items'][$titlename]['word_list'] .= ', '.$word;
    }
//    $num_rows = mysqli_stmt_num_rows($stmt);
    mysqli_stmt_close($stmt);
    if(!empty($lines)) {
        $str = '';
        $j = 1;
        foreach($lines as $key=>$value) {
            if ($value['priority'] > 0) $str .= "<b>*";
                $str .= "$j) "; 
            if ($value['priority'] > 0) $str .= "</b>";
            $str .= $value['path_html'];
            foreach($value['items'] as $ikey=>$ivalue) {
                $ivalue['word_html'] = str_replace("title=''","title='{$ivalue['word_list']}'",$ivalue['word_html']);
                $str .= $ivalue['word_html'];
            }
            $str .= ' . . . '.'<br>';
            $j = $j + 1;
        }
    } else {
        $err_msg .= "검색 실패";
    }
    $return = array('str'=>$str, 'err_msg'=>$err_msg, 'lines'=>$lines); 
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
// sql 특정 구문을 php로 동적 구현
function sql_para($sh, &$placeholder, &$bindingArray, &$score, &$pattern_1, &$pattern_2, &$where) {
	// 한글 (1),영어 (0) 구분
	$lang = ( preg_match("/[\x{ac00}-\x{d7a3}][\x{ac00}-\x{d7a3}]/u", $sh) ? 1 : 0 );   // 1: 한글, 0 : 영어
    // 용어명(c.word)에서, 순수 문자열 만 추출 (예: `블록 [전산]` => `블록`)
    $pure = "replace(substring_index(c.word,'[',1),' ','')";
    $sh = str_replace(['-','='],' ',$sh);     // '-'를 ' '로 대치
    $varStrArr = explode(' ',$sh);      // 검색어를 빈 칸 구분자로 하는 부분문자열들의 배열로 변환
    $varStrArr = processStringArray($varStrArr);    // 변환된 부분문자열 배열 중 요소 문자열 길이가 1인 경우 처리 (예: c 언어)
    if($lang==1) {  // 한글 
        $score = "(case 
                       when a.name=? or replace(a.name,' ','')=replace(?,' ','') then 3
                       when b.titlename=? or replace(b.titlename,' ','')=replace(?,' ','') then 2
                       when trim(substring_index(c.word,'[',1))=? or replace(c.word,' ','')=replace(?,' ','') then 1
#                      when (c.word regexp substring_index(?,' ',1)) and 
#                           (c.word regexp substring_index(?,' ',-1)) then 1
                       else 0
                    end)";
//        $placeholder .= str_repeat('s',substr_count($score,'?'));
        $placeholder .= 'ssssss';
        array_push($bindingArray, $sh,$sh,$sh,$sh,$sh,$sh);
        // 배열 요소를 '(^| )요소($| )'로 감싸기
        $wrappedArray = array_map(function($item) {
            return '(^| )' . $item . '($| )';
        }, $varStrArr);
        // 구분자로 '|'를 사용하여 문자열로 합치기
        $pattern_1 = implode('|', $wrappedArray);   // 검색어를 빈 칸 구분자로 구분된 부분문자열로 변환 후, 정확한 단어별 매칭 패턴
        $pattern_2 = str_replace(' ','',$sh);       // 검색어 빈 칸 없앤 후에, 검색어를 앞,뒤부분 부분 매칭하기 위한 정규식 패턴
        $pattern_3 = "(^| )$sh";
        $where = "    
                    concat(     # 분류명,대표명,유사용어명을 한 줄로 표현
                            ' '     # 앞 빈 칸
                            ,       concat_ws(  # 결합 시 각 문자열 사이에 지정된 구분자를 삽입하는 함수
                                        ' '     # 결합 구분자를 `빈 칸`으로 정함
                                        , a.name, replace(a.name,' ','')    # 분류명
                                        , b.titlename, replace(b.titlename,' ','')  # 대표명
                                        , c.word, replace(c.word,' ','')   # 유사용어명
                                    )
                            , ' '       # 뒤 빈 칸 
                    )
                    REGEXP 
                    ?    # 검색어 정규표현식 패턴 (pattern_1)
                  or 
                    ? like concat(replace(c.word,' ',''),'%')   # 검색어 앞부분 부분 매칭을 위한 필드명 적용 (pattern_2)
                  or
                    ? like concat('%',replace(c.word,' ',''))   # 검색어 뒤부분 부분 매칭을 위한 필드명 적용 (pattern_2)
                  or
                    replace(c.word,' ','') regexp ?   # 검색어가 용어명 앞,뒤부분 매칭 (pattern_3)
                 ";   //
        $placeholder .= str_repeat('s',substr_count($where,'?'));
        array_push($bindingArray, $pattern_1,$pattern_2,$pattern_2,$pattern_3);
    } else {    // 영어 
        $score = "(case 
                       when a.name=? then 3
                       when b.titlename=? then 2
                       when trim(substring_index(c.word,'[',1))=? then 1
                       when (? regexp substring_index(c.word,' ',1)) and 
                                  (? regexp substring_index(c.word,' ',-1)) then 1
                       when (c.word regexp substring_index(?,' ',1)) and 
                                  (c.word regexp substring_index(?,' ',-1)) then 1
                       else 0
                    end)";
        $placeholder .= str_repeat('s',substr_count($score,'?'));
        array_push($bindingArray, $sh,$sh,$sh,$sh,$sh,$sh,$sh);
/*
        $array_sh = implode(' | ', $varStrArr);
        $pattern_1 = " {$array_sh} ";
*/
        // 배열 요소를 '(^| )요소($| )'로 감싸기
        $wrappedArray = array_map(function($item) {
            return '(^| )' . $item . '($| )';
        }, $varStrArr);
        // 구분자로 '|'를 사용하여 문자열로 합치기
        $pattern_1 = implode('|', $wrappedArray);   // 검색어를 빈 칸 구분자로 구분된 부분문자열로 변환 후, 정확한 단어별 매칭 패턴
        $where = " concat(
                            ' '
                            ,   concat_ws(
                                    ' '
                                    , replace(a.name,'-',' ')
                                    , replace(b.titlename,'-',' ')
                                    , replace(c.word,'-',' ')
                                )
                            , ' '
                    ) 
                    REGEXP
                    ?       # $pattern_1
                   or
                    ((? like concat(c.word,'%') or ? like concat('%',c.word)) and (char_length(c.word) > 3))
                        # 검색어 앞,뒤 부분 매칭을 위한 필드명 적용
                 ";
        $placeholder .= str_repeat('s',substr_count($where,'?'));
        array_push($bindingArray, $pattern_1,$sh,$sh);
    }
}
// 배열 중 요소 문자열 길이가 1인 경우
function processStringArray($arr) {
    $count = count($arr);
    // 첫 번째 조건 처리: 문자열 길이가 1 이하인 요소와 그 다음 요소 합치기
    for ($i = 0; $i < $count - 1; $i++) {
        if (strlen($arr[$i]) <= 1) {
            // 길이가 1 이하인 요소와 그 다음 요소를 합치고, 그 자리에 삽입
            $arr[$i] .= ' ' . $arr[$i + 1];
            // 합친 후, 다음 요소를 제거
            array_splice($arr, $i + 1, 1);
            // 배열의 길이가 하나 줄어듦, 따라서 인덱스를 하나 빼고 다음 요소로 넘어가야 함
            $count--;
            $i--; // 다음 요소를 확인할 수 있도록 인덱스를 다시 조정
        }
    }
    // 두 번째 조건 처리: 배열의 마지막 요소가 길이가 1 이하일 경우
    if (strlen($arr[$count - 1]) <= 1 && $count > 1) {
        $arr[$count - 2] .= ' ' . $arr[$count - 1];
        array_splice($arr, $count - 1, 1);
    }
    return $arr;
}
// 용어 설명 fetch 송출
function ajaxFetchWord($no, $dbi) {
	$query = "select abbr from cjb_dict where no={$no} limit 1";
	$result = mysqli_query($dbi,$query);
		if (mysqli_errno($dbi)) { $err_msg .= mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n"; }
    $matched = mysqli_fetch_assoc($result);
	if (!empty($err_msg)){
		$return = array('err_msg'=>$err_msg);
	} else {
		$return = array('abbr' => $matched['abbr']);
	}
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
function ajaxIdNo4PathName($id,$no,$dbi) {
    if(empty($no) and !empty($id)) $where = "b.id={$id}";
    else if(!empty($no) and empty($id)) $where = "a.no={$no}";
    else if(!empty($no) and !empty($id)) $where = "b.id={$id} and a.no={$no}";
    $query = "select a.no, a.titlename, b.id, b.path2node_v2, b.name
                from book_idx a left join gubun_tree_v2 b on a.tree_id=b.id
                where {$where}";
	$result=mysqli_query($dbi,$query);
		if (mysqli_errno($dbi)) {echo mysqli_errno($dbi)." : ".mysqli_error($dbi)."\n";}
	while (	$matched = mysqli_fetch_assoc($result) ) {
        $return[] = array('no'=>$matched['no'],'titlename'=>$matched['titlename'],'id'=>$matched['id'],'path'=>$matched['path2node_v2'],'name'=>$matched['name']);
    }
	echo json_encode($return, JSON_UNESCAPED_UNICODE);
}
?>