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,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;
}
}
@@ -0,0 +1,166 @@
<?php
if (!defined('_GNUBOARD_'))
exit;
/**
* 주문 설정값을 가져옵니다.
* @param string $key 설정 키
* @param string $default 기본값
* @return string 설정값
*/
function get_order_config($key, $default = '')
{
$result = sql_fetch("SELECT config_value FROM order_config WHERE config_key = '" . sql_real_escape_string($key) . "'");
return $result ? $result['config_value'] : $default;
}
/**
* 알림 메시지를 처리합니다 (발송 모드에 따라 로그 또는 실제 발송)
* @param string $template_key 템플릿 키
* @param array $variables 치환할 변수들
* @param string $type 'email' 또는 'sms'
* @return bool 성공 여부
*/
function send_notification_alert($template_key, $variables = [], $type = 'email')
{
// 발송 모드 확인
$notification_mode = get_order_config('notification_mode', 'log');
// 템플릿 조회
$table = ($type == 'sms') ? 'order_sms_templates' : 'order_mail_templates';
$template = sql_fetch("SELECT * FROM {$table} WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
if (!$template) {
return false;
}
// 변수 치환
$content = $template['content'];
if ($type == 'email') {
$subject = $template['subject'];
foreach ($variables as $key => $value) {
$subject = str_replace('{' . $key . '}', $value, $subject);
$content = str_replace('{' . $key . '}', $value, $content);
}
$message = "제목: {$subject}\\n\\n내용: {$content}";
} else {
foreach ($variables as $key => $value) {
$content = str_replace('{' . $key . '}', $value, $content);
}
$message = $content;
}
if ($notification_mode == 'send') {
// 실제 발송 모드
if ($type == 'email') {
// 실제 이메일 발송 (여기에 실제 발송 코드 구현)
// mailer($to, $subject, $content, $from);
error_log("[EMAIL SENT] {$template['template_name']}: {$message}");
} else {
// 실제 SMS 발송 (여기에 실제 발송 코드 구현)
// sms_send($phone, $content);
error_log("[SMS SENT] {$template['template_name']}: {$message}");
}
} else {
// 로그 모드 - JavaScript 알림창으로 표시
echo "<script>alert('[{$type}] {$template['template_name']}\\n\\n{$message}');</script>";
error_log("[NOTIFICATION LOG] [{$type}] {$template['template_name']}: {$message}");
}
return true;
}
/**
* 고객에게 알림을 발송합니다.
* @param string $template_key 템플릿 키
* @param array $customer_data 고객 데이터
* @return bool 성공 여부
*/
function notify_customer($template_key, $customer_data)
{
// 이메일 발송
$email_result = send_notification_alert($template_key, $customer_data, 'email');
// SMS 발송
$sms_result = send_notification_alert($template_key, $customer_data, 'sms');
return $email_result && $sms_result;
}
/**
* 대리점에게 알림을 발송합니다.
* @param string $template_key 템플릿 키
* @param array $agent_data 대리점 데이터
* @return bool 성공 여부
*/
function notify_agent($template_key, $agent_data)
{
// 이메일 발송
$email_result = send_notification_alert($template_key, $agent_data, 'email');
// SMS 발송
$sms_result = send_notification_alert($template_key, $agent_data, 'sms');
return $email_result && $sms_result;
}
/**
* 관리자에게 알림을 발송합니다.
* @param string $template_key 템플릿 키
* @param array $admin_data 관리자 데이터
* @return bool 성공 여부
*/
function notify_admin($template_key, $admin_data)
{
// 이메일 발송
$email_result = send_notification_alert($template_key, $admin_data, 'email');
return $email_result;
}
/**
* 견적 상태 변경 시 알림을 발송합니다.
* @param int $wr_id 게시물 ID
* @param string $old_status 이전 상태
* @param string $new_status 새 상태
* @return bool 성공 여부
*/
function send_status_change_notification($wr_id, $old_status, $new_status)
{
// 게시물 정보 조회
$post = sql_fetch("SELECT * FROM g5_write_order WHERE wr_id = '{$wr_id}'");
if (!$post)
return false;
// 상태별 알림 처리
switch ($new_status) {
case '작성완료':
// 고객에게 완료 알림
notify_customer('customer_request_complete', [
'customer_name' => $post['wr_name'],
'request_title' => $post['wr_subject'],
'request_date' => date('Y-m-d', strtotime($post['wr_datetime']))
]);
// 대리점들에게 새 견적 요청 알림
$agents = sql_query("SELECT mb_id, mb_name FROM g5_member WHERE mb_level IN (5,6,7)");
while ($agent = sql_fetch_array($agents)) {
notify_agent('agent_new_request', [
'agent_name' => $agent['mb_name'],
'customer_name' => $post['wr_name'],
'request_title' => $post['wr_subject'],
'request_date' => date('Y-m-d', strtotime($post['wr_datetime'])),
'write_url' => G5_BBS_URL . '/write.php?w=r&bo_table=order&wr_id=' . $wr_id
]);
}
break;
case '견적채택':
// 선택된 대리점과 고객에게 알림
// (구체적인 로직은 견적 선택 시점에서 처리)
break;
}
return true;
}
?>
@@ -0,0 +1,320 @@
<?php
if (!defined('_GNUBOARD_'))
exit;
// ❗ [핵심] 필요한 클래스와 라이브러리를 포함합니다.
if (file_exists(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php')) {
require_once(G5_ADMIN_PATH . '/mail_manage/classes/MailSender.php');
}
if (file_exists(G5_PLUGIN_PATH . '/sms5/sms5.lib.php')) {
include_once(G5_PLUGIN_PATH . '/sms5/sms5.lib.php');
}
/**
* 통합 메일/SMS 발송 클래스
* 기존 mail_manage, sms_admin 시스템을 활용하여 발송 및 이력 기록
*/
class NotificationSender
{
private $g5;
public function __construct()
{
global $g5;
$this->g5 = $g5;
}
/**
* ❗ [핵심 수정] 템플릿 기반 메인 발송 함수
* @param array $params 발송 파라미터
* @return array 발송 결과
*/
public function send($params)
{
// 파라미터 검증
$validated = $this->validateParams($params);
if (!$validated['success']) {
return $validated;
}
// 대상 회원 조회
$members = $this->getTargetMembers($params);
if (empty($members)) {
return ['success' => false, 'message' => '발송 대상 회원이 없습니다.'];
}
$results = [
'success' => true,
'total_targets' => count($members),
'sms_success' => 0,
'sms_fail' => 0,
'email_success' => 0,
'email_fail' => 0,
'message' => ''
];
// SMS 발송
if (!empty($params['sms_template_key'])) {
$sms_result = $this->sendSMS($members, $params['sms_template_key'], $params['vars'] ?? []);
$results['sms_success'] = $sms_result['success'];
$results['sms_fail'] = $sms_result['fail'];
}
// 이메일 발송
if (!empty($params['email_template_key'])) {
$email_result = $this->sendEmail($members, $params['email_template_key'], $params['vars'] ?? []);
$results['email_success'] = $email_result['success'];
$results['email_fail'] = $email_result['fail'];
}
$results['message'] = $this->generateResultMessage($results);
return $results;
}
/**
* ❗ [핵심 수정] 템플릿 기반 파라미터 검증
*/
private function validateParams($params)
{
if (empty($params['target_type'])) {
return ['success' => false, 'message' => "필수 파라미터 'target_type'이 누락되었습니다."];
}
if ($params['target_type'] === 'single' && empty($params['member_id'])) {
return ['success' => false, 'message' => '단일 발송 시 회원 ID(member_id)가 필요합니다.'];
}
if ($params['target_type'] === 'bulk' && empty($params['member_levels'])) {
return ['success' => false, 'message' => '대량 발송 시 회원 레벨(member_levels)이 필요합니다.'];
}
if (empty($params['sms_template_key']) && empty($params['email_template_key'])) {
return ['success' => false, 'message' => 'SMS 또는 이메일 템플릿 키 중 하나는 반드시 필요합니다.'];
}
return ['success' => true];
}
/**
* 대상 회원 조회
*/
private function getTargetMembers($params)
{
$members = [];
$member_table = $this->g5['member_table'];
if ($params['target_type'] === 'single') {
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_id = '" . sql_real_escape_string($params['member_id']) . "' AND mb_leave_date = '' AND mb_intercept_date = ''";
} else {
$levels = array_map('intval', $params['member_levels']);
$level_condition = implode(',', $levels);
$sql = "SELECT mb_id, mb_name, mb_hp, mb_email, mb_sms, mb_mailling FROM `{$member_table}` WHERE mb_level IN ({$level_condition}) AND mb_leave_date = '' AND mb_intercept_date = ''";
}
$this->write_debug_log("[SMS 발송 시작] sql '{$sql}'");
$result = sql_query($sql);
while ($row = sql_fetch_array($result)) {
$members[] = $row;
}
return $members;
}
/**
* ❗ [핵심 수정] SMS 발송 (템플릿 및 변수 처리, 이력 기록 포함)
*/
private function sendSMS($members, $template_key, $common_vars)
{
$success = 0;
$fail = 0;
$notification_mode = get_order_config('notification_mode', 'log');
$is_test_mode = ($notification_mode !== 'send');
$sizeof_members = count($members);
$template = sql_fetch("SELECT * FROM `sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
if (!$template) {
$this->write_debug_log("[SMS 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
return ['success' => 0, 'fail' => count($members)];
}
$count= count($members);
if ($is_test_mode) {
// --- 개발 모드: 로그 파일에만 기록 ---
foreach ($members as $member) {
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
$personal_message = $template['content'];
foreach ($personal_vars as $key => $value) {
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
}
$this->write_debug_log("[SMS LOG] To: {$member['mb_hp']}, Content: {$personal_message}");
$success++;
} else {
$empty = !empty($member['mb_hp']);
$this->write_debug_log("[SMS 발송오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
$fail++;
}
}
} else {
// --- 실제 발송 모드: DB 기록 및 실제 발송 ---
$sms_config = sql_fetch("SELECT * FROM {$this->g5['sms5_config_table']}");
$send_phone = $sms_config['cf_phone'];
$wr_message = sql_real_escape_string($template['content']);
$wr_reply = sql_real_escape_string($send_phone);
sql_query("INSERT INTO {$this->g5['sms5_write_table']} (wr_message, wr_reply, wr_total, wr_datetime) VALUES ('{$wr_message}', '{$wr_reply}', '" . count($members) . "', '" . G5_TIME_YMDHIS . "')");
$wr_no = sql_insert_id();
$SMS = null;
if (class_exists('SMS5')) {
$SMS = new SMS5;
$SMS->SMS_con($sms_config['cf_sms_ip'], $sms_config['cf_sms_id'], $sms_config['cf_sms_pw'], $sms_config['cf_sms_port']);
} else {
$this->write_debug_log("[SMS 발송 오류] SMS5 클래스를 찾을 수 없습니다. 실제 발송을 건너뜁니다.");
}
foreach ($members as $member) {
if ($member['mb_sms'] && !empty($member['mb_hp'])) {
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
$personal_message = $template['content'];
foreach ($personal_vars as $key => $value) {
$personal_message = str_replace('{' . $key . '}', $value, $personal_message);
}
$result_code = 'Fail';
$result_msg = 'SMS5 클래스가 없어 발송할 수 없습니다.';
$hs_status = '0';
if ($SMS) {
$SMS->Add($member['mb_hp'], $send_phone, '', $personal_message);
$SMS->Send();
$result_arr = $SMS->Result;
$result_code = 'Fail';
$result_msg = '서버로부터 응답이 없습니다.';
if(!empty($result_arr)){
$result_parts = explode(':', $result_arr[0]);
if(count($result_parts) > 1 && strpos($result_parts[1], 'Error') === false) {
$result_code = 'Success';
$result_msg = $result_parts[1];
} else {
$result_msg = $result_arr[0];
}
}
$hs_status = ($result_code == 'Success') ? '1' : '0';
$SMS->Init();
}
sql_query("INSERT INTO {$this->g5['sms5_history_table']} (wr_no, mb_id, hs_name, hs_hp, hs_datetime, hs_status, hs_message) VALUES ('{$wr_no}', '{$member['mb_id']}', '{$member['mb_name']}', '{$member['mb_hp']}', '" . G5_TIME_YMDHIS . "', '{$hs_status}', '{$result_msg}')");
if ($hs_status == '1') $success++;
else $fail++;
} else {
$fail++;
}
}
}
return ['success' => $success, 'fail' => $fail];
}
/**
* ❗ [핵심 수정] 이메일 발송 (MailSender 클래스 활용)
*/
private function sendEmail($members, $template_key, $common_vars)
{
$success = 0;
$fail = 0;
$notification_mode = get_order_config('notification_mode', 'log');
$is_test_mode = ($notification_mode !== 'send');
$sizeof_members = count($members);
if ($is_test_mode) {
// --- 개발 모드: 로그 파일에만 기록 ---
$template = sql_fetch("SELECT * FROM `mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'");
if (!$template) {
$this->write_debug_log("[EMAIL 발송 오류] 템플릿 '{$template_key}'을(를) 찾을 수 없습니다.");
return ['success' => 0, 'fail' => count($members)];
}
foreach ($members as $member) {
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
$subject = $template['subject'];
$content = $template['content'];
foreach ($personal_vars as $key => $value) {
$search = '{' . $key . '}';
$subject = str_replace($search, $value, $subject);
$content = str_replace($search, $value, $content);
}
$this->write_debug_log("[EMAIL LOG] To: {$member['mb_email']}, Subject: {$subject}, Content: {$content}");
$success++;
} else {
$fail++;
$empty = !empty($member['mb_email']);
$this->write_debug_log("[EMAIL 발송 오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
}
}
} else {
// --- 실제 발송 모드: MailSender 호출 ---
if (!class_exists('MailSender')) {
$this->write_debug_log("[EMAIL 발송 오류] MailSender 클래스를 찾을 수 없습니다.");
return ['success' => 0, 'fail' => count($members)];
}
$mailSender = new MailSender();
foreach ($members as $member) {
if ($member['mb_mailling'] && !empty($member['mb_email'])) {
$personal_vars = array_merge($common_vars, ['이름' => $member['mb_name'], 'agent_name' => $member['mb_name'], 'dealer_name' => $member['mb_name']]);
// MailSender의 send 메소드는 내부적으로 템플릿 조회 및 변수 치환을 모두 처리합니다.
$send_result = $mailSender->send($template_key, $member['mb_email'], $personal_vars);
if ($send_result) $success++;
else $fail++;
} else {
$fail++;
}
}
}
return ['success' => $success, 'fail' => $fail];
}
/**
* 결과 메시지 생성
*/
private function generateResultMessage($results)
{
$message = "발송이 완료되었습니다.\n\n";
$message .= "전체 대상: " . $results['total_targets'] . "\n\n";
if (isset($results['sms_success'])) {
$message .= "SMS 발송 결과: 성공 " . $results['sms_success'] . "건, 실패 " . $results['sms_fail'] . "\n";
}
if (isset($results['email_success'])) {
$message .= "이메일 발송 결과: 성공 " . $results['email_success'] . "건, 실패 " . $results['email_fail'] . "\n";
}
return $message;
}
/**
* ❗ [핵심 수정] 디버그 로그 기록 함수 (권한 문제 해결)
*/
private function write_debug_log($message)
{
$log_dir = G5_PATH . '/log';
// 1. 디렉토리 존재 여부 확인 및 생성
if (!is_dir($log_dir)) {
if (!@mkdir($log_dir, 0755, true) && !is_dir($log_dir)) {
error_log("--- NotificationSender ERROR: 디버그 로그 디렉토리 생성 실패. '{$log_dir}' 경로를 확인하거나 수동으로 생성 후 웹서버 쓰기 권한을 부여해주세요.");
return;
}
}
// 2. 디렉토리 쓰기 권한 확인
if (!is_writable($log_dir)) {
error_log("--- NotificationSender ERROR: 디버그 로그 쓰기 오류. '{$log_dir}' 디렉토리에 쓰기 권한이 없습니다. 웹서버의 폴더 권한을 확인해주세요.");
return;
}
// 3. 로그 파일에 내용 기록
$log_file = $log_dir . '/notification_debug.log';
$log_message = date("[Y-m-d H:i:s]") . " " . $message . "\n";
if (file_put_contents($log_file, $log_message, FILE_APPEND | LOCK_EX) === false) {
error_log("--- NotificationSender ERROR: 디버그 로그 파일 쓰기 실패. '{$log_file}' 파일에 내용을 쓸 수 없습니다.");
}
}
}
@@ -0,0 +1,134 @@
<?php
if (!defined('_GNUBOARD_'))
exit;
if (!function_exists('get_status_manager')) {
/**
* StatusManager 인스턴스 가져오기
* @return StatusManager
*/
function get_status_manager()
{
static $status_manager = null;
if ($status_manager === null) {
if (!class_exists('StatusManager')) {
require_once G5_PATH . '/adm/order_manage/classes/StatusManager.class.php';
}
$status_manager = new StatusManager();
}
return $status_manager;
}
}
/**
* 상태 코드를 화면 표시용 이름으로 변환합니다.
* @param string $status_code 실제 상태 코드
* @return string 화면 표시용 이름
*/
function get_status_display_name($status_code)
{
$config_key = $status_code;
$result = sql_fetch("SELECT config_value FROM order_config WHERE config_key = '" . sql_real_escape_string($config_key) . "'");
return $result ? $result['config_value'] : $status_code;
}
/**
* 모든 상태 코드와 표시명을 가져옵니다.
* @return array 상태 코드 => 표시명 배열
*/
function get_all_status_names()
{
$statuses = [];
$result = sql_query("SELECT config_key, config_value FROM order_config WHERE config_key LIKE 'status_%' ORDER BY config_key ASC");
while ($row = sql_fetch_array($result)) {
$status_code = str_replace('status_', '', $row['config_key']);
$statuses[$status_code] = $row['config_value'];
}
return $statuses;
}
/**
* 사용자 권한별 상태 전환 규칙을 가져옵니다.
* @param string $user_role 사용자 역할 (customer, agent, admin)
* @return array 상태 전환 규칙
*/
function get_status_transitions($user_role)
{
$transitions = [];
switch ($user_role) {
case 'customer':
$transitions = [
'견적신청중' => ['작성완료'],
'작성완료' => [], // 고객은 작성완료 후 변경 불가
'견적채택' => ['입금예정'], // 견적 선택 시 자동 전환
'입금예정' => [], // 관리자만 변경 가능
'입금확인' => [],
'다운로드' => []
];
break;
case 'agent':
$transitions = [
'견적제안' => ['견적채택', '견적취소'],
'견적채택' => [],
'견적취소' => []
];
break;
case 'admin':
$transitions = [
'견적신청중' => ['작성완료'],
'작성완료' => ['견적신청중', '입금예정'], // 되돌리기 가능
'견적채택' => ['입금예정'],
'입금예정' => ['입금확인'],
'입금확인' => ['다운로드'],
'다운로드' => ['견적신청중'], // 루프백
'견적제안' => ['견적채택', '견적취소'],
'견적취소' => ['견적제안'] // 되돌리기 가능
];
break;
}
return $transitions;
}
/**
* 상태 전환이 유효한지 확인합니다.
* @param string $current_status 현재 상태
* @param string $new_status 새 상태
* @param string $user_role 사용자 역할
* @return bool 전환 가능 여부
*/
function is_valid_status_transition($current_status, $new_status, $user_role)
{
$transitions = get_status_transitions($user_role);
if (!isset($transitions[$current_status])) {
return false;
}
return in_array($new_status, $transitions[$current_status]);
}
/**
* 상태별 CSS 클래스를 가져옵니다.
* @param string $status_code 상태 코드
* @return string CSS 클래스명
*/
function get_status_css_class($status_code)
{
$css_classes = [
'견적신청중' => 'btn-request',
'작성완료' => 'btn-offer',
'견적채택' => 'btn-select',
'입금예정' => 'btn-deposit-ready',
'입금확인' => 'btn-deposit-done',
'다운로드' => 'btn-excel',
'견적제안' => 'btn-offer',
'견적취소' => 'btn-cancel'
];
return $css_classes[$status_code] ?? 'btn-request';
}
?>
@@ -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();
}