first commit 2

This commit is contained in:
hmw1001
2026-06-11 18:47:38 +09:00
parent c768729ce6
commit 6f534e33a6
11095 changed files with 1595758 additions and 0 deletions
@@ -0,0 +1,194 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
*/
class SchemaManager
{
private $sql_file_path;
private $results;
/**
* 생성자
* @param string $sql_file_path install.sql 파일의 절대 경로
*/
public function __construct($sql_file_path)
{
if (!file_exists($sql_file_path)) {
throw new Exception($sql_file_path . ' 파일을 찾을 수 없습니다.');
}
$this->sql_file_path = $sql_file_path;
$this->results = [
'created' => [],
'existing' => [],
'updated' => [],
'failed' => [],
'errors' => [],
];
}
/**
* 스키마 설치/업데이트를 실행합니다.
*/
public function execute()
{
$sql_statements = $this->parse_sql_file();
foreach ($sql_statements as $stmt) {
// CREATE TABLE 문인지 확인
if (preg_match('/^CREATE\s+TABLE/i', $stmt)) {
$schema = $this->parse_create_table_sql($stmt);
if ($schema && !empty($schema['name'])) {
$this->process_table_schema($stmt, $schema);
}
} else {
// CREATE TABLE 문이 아닌 다른 SQL 문 (e.g. INSERT, UPDATE)
sql_query($stmt, false);
}
}
}
/**
* 처리 결과를 반환합니다.
* @return array
*/
public function get_results()
{
return $this->results;
}
/**
* 테이블 스키마를 처리합니다. (생성 또는 업데이트)
* @param string $create_sql 전체 CREATE TABLE 구문
* @param array $schema 파싱된 스키마 정보
*/
private function process_table_schema($create_sql, $schema)
{
$table_name = $schema['name'];
if ($this->table_exists($table_name)) {
// 테이블이 존재하면, 컬럼 비교 및 추가
$this->results['existing'][] = $table_name;
$this->update_table_columns($table_name, $schema['columns']);
} else {
// 테이블이 존재하지 않으면, 새로 생성
if (sql_query($create_sql, false)) {
$this->results['created'][] = $table_name;
} else {
$this->results['failed'][] = $table_name;
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . sql_error();
}
}
}
/**
* 테이블의 컬럼 구조를 업데이트합니다.
* @param string $table_name
* @param array $target_columns .sql 파일에 정의된 컬럼 목록
*/
private function update_table_columns($table_name, $target_columns)
{
$current_columns = $this->get_current_columns($table_name);
$added_columns_in_table = [];
foreach ($target_columns as $col_name => $col_definition) {
// 현재 테이블에 해당 컬럼이 없으면 추가
if (!isset($current_columns[$col_name])) {
$alter_sql = "ALTER TABLE `{$table_name}` ADD COLUMN `{$col_name}` {$col_definition}";
if (sql_query($alter_sql, false)) {
$added_columns_in_table[] = $col_name;
} else {
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . sql_error();
}
}
}
if (!empty($added_columns_in_table)) {
$this->results['updated'][$table_name] = $added_columns_in_table;
}
}
/**
* SQL 파일을 읽고 각 구문으로 분리합니다.
* @return array
*/
private function parse_sql_file()
{
$sql = file_get_contents($this->sql_file_path);
// 주석 제거 (SQL 주석 '--' 와 C-style '/* ... */' 주석)
$sql = preg_replace('/--.*/', '', $sql);
$sql = preg_replace('!/\*.*?\*/!s', '', $sql);
$sql = trim($sql);
// 세미콜론(;)을 기준으로 쿼리 분리
return array_filter(array_map('trim', explode(';', $sql)));
}
/**
* 테이블 존재 여부를 확인합니다.
* @param string $table_name
* @return bool
*/
private function table_exists($table_name)
{
$res = sql_query("SHOW TABLES LIKE '{$table_name}'", false);
return sql_num_rows($res) > 0;
}
/**
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
* @param string $table_name
* @return array
*/
private function get_current_columns($table_name)
{
$res = sql_query("SHOW COLUMNS FROM `{$table_name}`", false);
$columns = [];
while ($row = sql_fetch_array($res)) {
$columns[$row['Field']] = true;
}
return $columns;
}
/**
* CREATE TABLE SQL 구문에서 테이블명과 컬럼 정의를 파싱합니다.
* @param string $query CREATE TABLE 구문
* @return array|null
*/
private function parse_create_table_sql($query)
{
$table_name = '';
if (preg_match('/CREATE\s+TABLE(?:\s+IF\s+NOT\s+EXISTS)?\s+`?(\w+)`?/i', $query, $matches)) {
$table_name = $matches[1];
} else {
return null;
}
// 괄호 안의 내용만 추출
$start = strpos($query, '(');
$end = strrpos($query, ')');
if ($start === false || $end === false) {
return ['name' => $table_name, 'columns' => []];
}
$content = substr($query, $start + 1, $end - $start - 1);
// 줄 단위로 분리
$lines = explode("\n", $content);
$columns = [];
foreach ($lines as $line) {
$line = trim($line, " \t\n\r\0\x0B,"); // 양쪽 공백과 마지막 쉼표 제거
// 컬럼 정의 라인인지 확인 (첫 단어가 `column_name` 형태)
if (preg_match('/^`(\w+)`\s+(.*)/i', $line, $match)) {
$col_name = $match[1];
$col_definition = $match[2];
$columns[$col_name] = $col_definition;
}
}
return ['name' => $table_name, 'columns' => $columns];
}
}
@@ -0,0 +1,133 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* UI 리소스 관리 클래스
* Singleton 패턴을 사용하여 인스턴스를 한 번만 생성하고,
* 불러온 데이터를 캐시하여 DB 조회를 최소화합니다.
*/
class UiManager
{
private static $instance = null;
private $resources = []; // 데이터를 캐시할 배열
// 외부에서 new 키워드로 인스턴스 생성을 막음
private function __construct() {}
/**
* 클래스의 유일한 인스턴스를 반환합니다.
* @return UiManager
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
/**
* 'LABEL' 타입의 UI 텍스트를 가져옵니다.
* @param string $resource_code 리소스 코드
* @param string $lang 언어 코드 (기본값: 'ko')
* @return string 라벨 텍스트 (없으면 resource_code 반환)
*/
public function get_label($resource_code, $lang = 'ko')
{
// 캐시 확인
if (isset($this->resources['labels'][$lang][$resource_code])) {
return $this->resources['labels'][$lang][$resource_code];
}
global $g5;
$resource_code_escaped = sql_real_escape_string($resource_code);
$lang_escaped = sql_real_escape_string($lang);
$sql = "SELECT B.cl_name
FROM {$g5['ui_manager_table']} AS A
LEFT JOIN {$g5['common_lang_table']} AS B
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = '{$lang_escaped}')
WHERE A.resource_code = '{$resource_code_escaped}' AND A.resource_type = 'LABEL'";
$row = sql_fetch($sql);
$label_text = $row['cl_name'] ?? $resource_code;
// 결과 캐시
$this->resources['labels'][$lang][$resource_code] = $label_text;
return $label_text;
}
/**
* 'DATA' 타입의 옵션 목록을 배열로 가져옵니다.
* @param string $resource_code 리소스 코드
* @param string $lang 언어 코드 (기본값: 'ko')
* @return array 옵션 목록 배열
*/
public function get_data($resource_code, $lang = 'ko')
{
// 캐시 확인
if (isset($this->resources['data'][$lang][$resource_code])) {
return $this->resources['data'][$lang][$resource_code];
}
global $g5;
$resource_code_escaped = sql_real_escape_string($resource_code);
$lang_escaped = sql_real_escape_string($lang);
$sql_um = "SELECT um_id FROM {$g5['ui_manager_table']} WHERE resource_code = '{$resource_code_escaped}' AND resource_type = 'DATA'";
$um_row = sql_fetch($sql_um);
if (!isset($um_row['um_id'])) {
$this->resources['data'][$lang][$resource_code] = []; // 빈 결과도 캐시
return [];
}
$um_id = $um_row['um_id'];
$sql = "SELECT A.fc_id, A.parent_id, A.fc_key, A.fc_order, B.cl_name
FROM {$g5['form_category_table']} AS A
LEFT JOIN {$g5['common_lang_table']} AS B
ON (A.fc_id = B.target_id AND B.target_table = '{$g5['form_category_table']}' AND B.lang_code = '{$lang_escaped}')
WHERE A.um_id = '{$um_id}' AND A.is_used = 1 AND A.is_deleted = 0
ORDER BY A.fc_order, A.fc_id";
$result = sql_query($sql);
$options = [];
while ($row = sql_fetch_array($result)) {
$options[] = $row;
}
// 결과 캐시
$this->resources['data'][$lang][$resource_code] = $options;
return $options;
}
/**
* 'DATA' 타입 리소스를 사용하여 HTML <select> 태그를 생성합니다.
* @param string $resource_code 리소스 코드
* @param string $select_name <select> 태그의 name 속성
* @param string $selected_value 미리 선택될 옵션의 값(fc_key)
* @param string $attributes <select> 태그에 추가할 HTML 속성 (e.g., 'id="my-id" class="my-class"')
* @param string $lang 언어 코드 (기본값: 'ko')
* @return string 생성된 HTML <select> 태그
*/
public function render_select($resource_code, $select_name, $selected_value = '', $attributes = '', $lang = 'ko')
{
$options = $this->get_data($resource_code, $lang);
if (empty($options)) {
return "<select name=\"{$select_name}\" {$attributes}><option value=\"\">옵션 없음</option></select>";
}
$html = "<select name=\"{$select_name}\" {$attributes}>";
$html .= "<option value=\"\">선택</option>";
foreach ($options as $option) {
$selected = ($option['fc_key'] == $selected_value) ? ' selected' : '';
$html .= "<option value=\"" . htmlspecialchars($option['fc_key']) . "\"{$selected}>" . htmlspecialchars($option['cl_name']) . "</option>";
}
$html .= "</select>";
return $html;
}
}
+106
View File
@@ -0,0 +1,106 @@
<?php
if (!defined('_GNUBOARD_')) exit;
/**
* UI 리소스 관리자 클래스 인스턴스를 반환하는 헬퍼 함수
* @return UiManager
*/
function ui_manager() {
// 클래스가 아직 로드되지 않았다면 인스턴스 생성
if (!class_exists('UiManager')) {
// UiManager 클래스 정의
class UiManager
{
private static $instance = null;
private $resources = []; // 데이터를 캐시할 배열
private function __construct() {}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function get_label($resource_code, $lang = 'ko')
{
if (isset($this->resources['labels'][$lang][$resource_code])) {
return $this->resources['labels'][$lang][$resource_code];
}
global $g5;
$resource_code_escaped = sql_real_escape_string($resource_code);
$lang_escaped = sql_real_escape_string($lang);
$sql = "SELECT B.cl_name
FROM {$g5['ui_manager_table']} AS A
LEFT JOIN {$g5['common_lang_table']} AS B
ON (A.um_id = B.target_id AND B.target_table = '{$g5['ui_manager_table']}' AND B.lang_code = '{$lang_escaped}')
WHERE A.resource_code = '{$resource_code_escaped}' AND A.resource_type = 'LABEL'";
$row = sql_fetch($sql);
$label_text = $row['cl_name'] ?? $resource_code;
$this->resources['labels'][$lang][$resource_code] = $label_text;
return $label_text;
}
public function get_data($resource_code, $lang = 'ko')
{
if (isset($this->resources['data'][$lang][$resource_code])) {
return $this->resources['data'][$lang][$resource_code];
}
global $g5;
$resource_code_escaped = sql_real_escape_string($resource_code);
$lang_escaped = sql_real_escape_string($lang);
$sql_um = "SELECT um_id FROM {$g5['ui_manager_table']} WHERE resource_code = '{$resource_code_escaped}' AND resource_type = 'DATA'";
$um_row = sql_fetch($sql_um);
if (!isset($um_row['um_id'])) {
$this->resources['data'][$lang][$resource_code] = [];
return [];
}
$um_id = $um_row['um_id'];
$sql = "SELECT A.fc_id, A.parent_id, A.fc_key, A.fc_order, B.cl_name
FROM {$g5['form_category_table']} AS A
LEFT JOIN {$g5['common_lang_table']} AS B
ON (A.fc_id = B.target_id AND B.target_table = '{$g5['form_category_table']}' AND B.lang_code = '{$lang_escaped}')
WHERE A.um_id = '{$um_id}' AND A.is_used = 1 AND A.is_deleted = 0
ORDER BY A.fc_order, A.fc_id";
$result = sql_query($sql);
$options = [];
while ($row = sql_fetch_array($result)) {
$options[] = $row;
}
$this->resources['data'][$lang][$resource_code] = $options;
return $options;
}
public function render_select($resource_code, $select_name, $selected_value = '', $attributes = '', $lang = 'ko')
{
$options = $this->get_data($resource_code, $lang);
if (empty($options)) {
return "<select name=\"".htmlspecialchars($select_name)."\" {$attributes}><option value=\"\">옵션 없음</option></select>";
}
$html = "<select name=\"".htmlspecialchars($select_name)."\" {$attributes}>";
$html .= "<option value=\"\">선택</option>";
foreach ($options as $option) {
$selected = ($option['fc_key'] == $selected_value) ? ' selected' : '';
$html .= "<option value=\"" . htmlspecialchars($option['fc_key']) . "\"{$selected}>" . htmlspecialchars($option['cl_name']) . "</option>";
}
$html .= "</select>";
return $html;
}
}
}
return UiManager::getInstance();
}