first commit 2
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
/**
|
||||
* SQL 파일을 기반으로 데이터베이스 스키마를 관리(생성/업데이트)하는 범용 클래스
|
||||
*/
|
||||
class SchemaManager
|
||||
{
|
||||
private $sql_file_path;
|
||||
private $results;
|
||||
private $conn; // DB 연결 객체를 저장할 변수
|
||||
|
||||
/**
|
||||
* 생성자
|
||||
* @param string $sql_file_path install.sql 파일의 절대 경로
|
||||
*/
|
||||
public function __construct($sql_file_path)
|
||||
{
|
||||
global $g5; // 그누보드 DB 연결 객체에 접근하기 위해 global 선언
|
||||
$this->conn = $g5['connect_db']; // DB 연결 객체를 저장
|
||||
|
||||
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)
|
||||
mysqli_query($this->conn, $stmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 처리 결과를 반환합니다.
|
||||
* @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 (mysqli_query($this->conn, $create_sql)) {
|
||||
$this->results['created'][] = $table_name;
|
||||
} else {
|
||||
$this->results['failed'][] = $table_name;
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블 생성 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 테이블의 컬럼 구조를 업데이트합니다.
|
||||
* @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 (mysqli_query($this->conn, $alter_sql)) {
|
||||
$added_columns_in_table[] = $col_name;
|
||||
} else {
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블에 '{$col_name}' 컬럼 추가 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
} else {
|
||||
// 💡 [핵심 수정] 컬럼이 이미 존재하면, 코멘트 등을 업데이트하기 위해 MODIFY 실행
|
||||
$alter_sql = "ALTER TABLE `{$table_name}` MODIFY COLUMN `{$col_name}` {$col_definition}";
|
||||
if (!mysqli_query($this->conn, $alter_sql)) {
|
||||
// MODIFY 실패 시 에러 기록
|
||||
$this->results['failed'][] = "{$table_name} (컬럼: {$col_name})";
|
||||
$this->results['errors'][] = "<strong>{$table_name} 테이블의 '{$col_name}' 컬럼 수정 실패</strong>: " . mysqli_error($this->conn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 = mysqli_query($this->conn, "SHOW TABLES LIKE '{$table_name}'");
|
||||
return mysqli_num_rows($res) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 DB에 있는 테이블의 컬럼 목록을 가져옵니다.
|
||||
* @param string $table_name
|
||||
* @return array
|
||||
*/
|
||||
private function get_current_columns($table_name)
|
||||
{
|
||||
$res = mysqli_query($this->conn, "SHOW COLUMNS FROM `{$table_name}`");
|
||||
$columns = [];
|
||||
while ($row = mysqli_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,441 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_'))
|
||||
exit;
|
||||
|
||||
// ❗ [핵심] NotificationSender 클래스 포함
|
||||
if (file_exists(__DIR__ . '/notification_sender.php')) {
|
||||
require_once(__DIR__ . '/notification_sender.php');
|
||||
}
|
||||
|
||||
/**
|
||||
* 주문 설정값을 가져옵니다.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
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}' 파일에 내용을 쓸 수 없습니다.");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* ❗ [핵심 수정] 1. 견적 상태 변경 시 알림 발송
|
||||
* @param int $wr_id 상태가 변경된 게시물 ID (원본 또는 답변글)
|
||||
* @param string $new_status 새로 변경된 상태
|
||||
*/
|
||||
function notify_by_status($wr_id, $new_status)
|
||||
{
|
||||
global $config;
|
||||
$post = sql_fetch("SELECT * FROM {$GLOBALS['g5']['write_prefix']}order WHERE wr_id = '{$wr_id}'");
|
||||
if (!$post) return false;
|
||||
write_debug_log("[notify_by_status] 원본 견적(estimate) 시작 : {$wr_id}");
|
||||
// 원본글 정보 조회
|
||||
$origin_post = sql_fetch("SELECT * FROM {$GLOBALS['g5']['write_prefix']}order WHERE wr_num = '{$post['wr_num']}' AND wr_reply = ''");
|
||||
if (!$origin_post) return false;
|
||||
$customer = get_member($origin_post['mb_id']);
|
||||
if (!$customer) return false;
|
||||
|
||||
// 템플릿에 사용할 공통 변수 (영문 키로 표준화)
|
||||
$common_vars = [
|
||||
'customer_name' => $customer['mb_name'],
|
||||
'request_title' => $origin_post['wr_subject'],
|
||||
'request_date' => date('Y-m-d', strtotime($origin_post['wr_datetime'])),
|
||||
'write_url' => get_pretty_url('order', $origin_post['wr_id']),
|
||||
'site_name' => $config['cf_title'] ?? '우리집창호', // 사이트 이름 추가
|
||||
];
|
||||
|
||||
if (!class_exists('NotificationSender')) return false;
|
||||
$sender = new NotificationSender();
|
||||
|
||||
// ❗ [추가] 결제 및 진행상태 관련 알림을 위한 공통 변수 처리
|
||||
$payment_statuses = ['계약금입금완료', '중도금입금예정', '중도금입금완료', '시공완료'];
|
||||
$payment_vars = [];
|
||||
$agent_member = null;
|
||||
$bid_dealer_id = null;
|
||||
|
||||
if (in_array($new_status, $payment_statuses)) {
|
||||
// ❗ [핵심 수정] estimate_bidding을 사용하지 않고, 채택된 답변글에서 직접 정보를 가져옵니다.
|
||||
// 1. 원본글의 estimate 정보를 가져옵니다. (시공일, 중도금일 등)
|
||||
$origin_estimate = sql_fetch("SELECT * FROM estimate WHERE wr_id = '{$origin_post['wr_id']}'");
|
||||
if (!$origin_estimate) {
|
||||
write_debug_log("[notify_by_status] 원본 견적(estimate) 정보를 찾을 수 없습니다. wr_id: {$origin_post['wr_id']}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 채택된 답변글(대리점 제안) 정보를 찾습니다.
|
||||
$selected_reply_sql = "
|
||||
SELECT
|
||||
w.mb_id as dealer_id,
|
||||
e.commission_fee as total_amount
|
||||
FROM
|
||||
{$GLOBALS['g5']['write_prefix']}order w
|
||||
JOIN
|
||||
estimate e ON w.wr_id = e.wr_id
|
||||
WHERE
|
||||
w.wr_num = '{$origin_post['wr_num']}'
|
||||
AND w.wr_reply != ''
|
||||
AND e.status = '견적채택'
|
||||
LIMIT 1
|
||||
";
|
||||
$selected_bid_info = sql_fetch($selected_reply_sql);
|
||||
|
||||
if ($selected_bid_info) {
|
||||
$bid_dealer_id = $selected_bid_info['dealer_id'];
|
||||
$agent_member = get_member($bid_dealer_id);
|
||||
$total_amount = (int) $selected_bid_info['total_amount']; // 답변글의 estimate.commission_fee를 총액으로 사용
|
||||
|
||||
$deposit_rate = (float) get_order_config('contract_deposit_rate', 10);
|
||||
$interim_rate = (float) get_order_config('middle_payment_rate', 40);
|
||||
$deposit_amount = (int) ($total_amount * $deposit_rate / 100);
|
||||
$interim_amount = (int) ($total_amount * $interim_rate / 100);
|
||||
|
||||
$payment_vars = array_merge($common_vars, [
|
||||
'deposit_amount' => number_format($deposit_amount) . '원',
|
||||
'interim_amount' => number_format($interim_amount) . '원',
|
||||
'agent_name' => $agent_member['mb_name'] ?? '담당 대리점',
|
||||
'construction_date' => $origin_estimate['temp_6'] ?? '미정',
|
||||
'interim_payment_date' => $origin_estimate['temp_7'] ?? '미정',
|
||||
'account_info' => get_order_config('payment_account_info', '계좌 정보 미설정'),
|
||||
]);
|
||||
}
|
||||
|
||||
if (empty($payment_vars)) { // 필수 정보가 없으면 발송 중단
|
||||
write_debug_log("[notify_by_status] 결제 정보 부족으로 '{$new_status}' 알림 발송을 건너뜁니다. wr_id: {$wr_id}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($new_status) {
|
||||
case '작성완료':
|
||||
// 1. 고객에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'customer_request_complete',
|
||||
'sms_template_key' => 'customer_request_complete',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
|
||||
// 2. 모든 대리점에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'bulk',
|
||||
'member_levels' => [9,10],
|
||||
'email_template_key' => 'agent_new_request',
|
||||
'sms_template_key' => 'agent_new_request',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '견적채택':
|
||||
$agent_member = get_member($post['mb_id']);
|
||||
write_debug_log("[notify_by_status] agent_member=> ".$agent_member['mb_id'].', name'.$agent_member['mb_name'].', id '.$post['mb_id'].', w_id '.$wr_id);
|
||||
if (!$agent_member) break;
|
||||
|
||||
// 🔥 [핵심 수정] 채택된 답변글(wr_id)의 estimate 레코드에서 최종 금액을 가져옵니다.
|
||||
$agent_estimate = sql_fetch("SELECT id, commission_fee FROM estimate WHERE wr_id = '{$post['wr_id']}'");
|
||||
$final_amount = $agent_estimate ? (int)$agent_estimate['commission_fee'] : 0;
|
||||
|
||||
// [수정] 입찰 정보를 바탕으로 변수를 설정합니다.
|
||||
$bid_vars = array_merge($common_vars, [
|
||||
'agent_name' => $agent_member['mb_name'],
|
||||
'dealer_name' => $agent_member['mb_name'], // 템플릿 호환용
|
||||
'deposit_amount' => number_format($final_amount) . '원', // 최종 금액을 입금 안내
|
||||
'bid_amount' => number_format($final_amount), // 템플릿 호환용
|
||||
'account_info' => get_order_config('payment_account_info', '계좌 정보 미설정'),
|
||||
'estimate_id' => $agent_estimate ? $agent_estimate['id'] : 'N/A',
|
||||
'selected_date' => date('Y-m-d H:i'),
|
||||
'estimate_url' => get_pretty_url('order', $origin_post['wr_id']),
|
||||
]);
|
||||
// 1. 채택된 대리점에게 알림 (축하 메시지)
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $agent_member['mb_id'],
|
||||
'email_template_key' => 'bid_selected_email',
|
||||
'sms_template_key' => 'bid_selected_sms',
|
||||
'vars' => $bid_vars,
|
||||
]);
|
||||
|
||||
// 2. 고객에게 알림 (선택 완료 및 입금 안내)
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'customer_quote_selected',
|
||||
'sms_template_key' => 'customer_quote_selected',
|
||||
'vars' => $bid_vars,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '계약금입금완료':
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'payment_deposit_complete_customer',
|
||||
'sms_template_key' => 'payment_deposit_complete_customer',
|
||||
'vars' => $payment_vars,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '중도금입금예정':
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'payment_interim_due_customer',
|
||||
'sms_template_key' => 'payment_interim_due_customer',
|
||||
'vars' => $payment_vars,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '중도금입금완료':
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'payment_interim_complete_customer',
|
||||
'sms_template_key' => 'payment_interim_complete_customer',
|
||||
'vars' => $payment_vars,
|
||||
]);
|
||||
break;
|
||||
|
||||
case '시공완료':
|
||||
// 고객에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'construction_complete_customer',
|
||||
'sms_template_key' => 'construction_complete_customer',
|
||||
'vars' => $payment_vars,
|
||||
]);
|
||||
|
||||
// 대리점에게 알림
|
||||
if ($bid_dealer_id) {
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $bid_dealer_id,
|
||||
'email_template_key' => 'construction_complete_agent',
|
||||
'sms_template_key' => 'construction_complete_agent',
|
||||
'vars' => $payment_vars,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [신규] 2. 입금 관련 알림 발송 (관리자, 고객)
|
||||
* @param int $wr_id 원본 게시물 ID
|
||||
* @param string $payment_status 입금 상태 (예: '계약금입금완료')
|
||||
* @param array $payment_info 추가 결제 정보 (금액 등)
|
||||
*/
|
||||
function notify_for_payment($wr_id, $payment_status, $payment_info = [])
|
||||
{
|
||||
$origin_post = sql_fetch("SELECT * FROM {$GLOBALS['g5']['write_prefix']}order WHERE wr_id = '{$wr_id}'");
|
||||
if (!$origin_post) return false;
|
||||
$customer = get_member($origin_post['mb_id']);
|
||||
if (!$customer) return false;
|
||||
|
||||
$common_vars = array_merge([
|
||||
'customer_name' => $customer['mb_name'],
|
||||
'estimate_subject' => $origin_post['wr_subject'],
|
||||
], $payment_info);
|
||||
|
||||
if (!class_exists('NotificationSender')) return false;
|
||||
$sender = new NotificationSender();
|
||||
|
||||
// 1. 고객에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $customer['mb_id'],
|
||||
'email_template_key' => 'payment_status_update_customer', // 예시 템플릿 키
|
||||
'sms_template_key' => 'payment_status_update_customer',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
|
||||
// 2. 관리자에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'bulk',
|
||||
'member_levels' => [10], // 관리자 레벨
|
||||
'email_template_key' => 'payment_status_update_admin', // 예시 템플릿 키
|
||||
'sms_template_key' => 'payment_status_update_admin',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [신규] 3. 전문가 방문 예약 관련 알림 발송 (고객, 전문가, 관리자)
|
||||
* @param int $reservation_id 예약 ID
|
||||
* @param string $visit_status 방문 상태 (예: '신규예약', '예약확정', '예약취소')
|
||||
*/
|
||||
function notify_for_expert_visit($reservation_id, $visit_status)
|
||||
{
|
||||
// 예약 정보 조회
|
||||
$reservation = sql_fetch("SELECT * FROM expert_visit_reservations WHERE id = '{$reservation_id}'");
|
||||
if (!$reservation) return false;
|
||||
|
||||
// 💡 [수정] 고객 정보 조회 (회원/비회원 구분)
|
||||
$customer = get_member($reservation['customer_id']);
|
||||
$is_guest = empty($customer['mb_id']);
|
||||
|
||||
// 비회원일 경우 예약 정보에서 직접 가져옴
|
||||
$customer_name = $is_guest ? $reservation['customer_name'] : $customer['mb_name'];
|
||||
$customer_hp = $is_guest ? $reservation['customer_phone'] : $customer['mb_hp'];
|
||||
$customer_email = $is_guest ? $reservation['customer_email'] : $customer['mb_email'];
|
||||
|
||||
$expert = get_member($reservation['expert_id']);
|
||||
|
||||
$common_vars = [
|
||||
'customer_name' => $customer_name,
|
||||
'visit_date' => $reservation['visit_date'],
|
||||
'visit_time' => $reservation['visit_time'],
|
||||
'expert_name' => $expert['mb_name'] ?? '미지정',
|
||||
];
|
||||
|
||||
if (!class_exists('NotificationSender')) return false;
|
||||
$sender = new NotificationSender();
|
||||
|
||||
switch ($visit_status) {
|
||||
case '신규예약':
|
||||
// 1. 고객에게 신청 완료 알림
|
||||
$send_params = [
|
||||
'email_template_key' => 'expert_visit_request_new',
|
||||
'sms_template_key' => 'expert_visit_request_new',
|
||||
'vars' => array_merge($common_vars, [
|
||||
'reservation_id' => $reservation_id,
|
||||
'expert_visit_fee' => number_format($reservation['payment_amount']) . '원',
|
||||
'account_info' => get_order_config('expert_visit_account_info', '계좌 정보 미설정'),
|
||||
]),
|
||||
];
|
||||
|
||||
if ($is_guest) {
|
||||
// 비회원: 직접 수신자 정보 지정
|
||||
$send_params['target_type'] = 'direct';
|
||||
$send_params['receivers'] = [
|
||||
[
|
||||
'mb_name' => $customer_name,
|
||||
'mb_hp' => $customer_hp,
|
||||
'mb_email' => $customer_email,
|
||||
'mb_sms' => 1, // SMS 수신 동의 가정
|
||||
'mb_mailling' => 1 // 메일 수신 동의 가정
|
||||
]
|
||||
];
|
||||
} else {
|
||||
// 회원: member_id 사용
|
||||
$send_params['target_type'] = 'single';
|
||||
$send_params['member_id'] = $customer['mb_id'];
|
||||
}
|
||||
$sender->send($send_params);
|
||||
|
||||
// 2. 관리자에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'bulk',
|
||||
'member_levels' => [10],
|
||||
'email_template_key' => 'admin_new_visit_request',
|
||||
'sms_template_key' => 'admin_new_visit_request',
|
||||
'vars' => array_merge($common_vars, [
|
||||
'customer_phone' => $customer_hp,
|
||||
'request_memo' => $reservation['request_memo'],
|
||||
]),
|
||||
]);
|
||||
break;
|
||||
|
||||
case '예약확정':
|
||||
// 1. 고객에게 예약 확정 알림
|
||||
$send_params = [
|
||||
'email_template_key' => 'expert_visit_confirmed',
|
||||
'sms_template_key' => 'expert_visit_confirmed',
|
||||
'vars' => $common_vars,
|
||||
];
|
||||
|
||||
if ($is_guest) {
|
||||
$send_params['target_type'] = 'direct';
|
||||
$send_params['receivers'] = [
|
||||
[
|
||||
'mb_name' => $customer_name,
|
||||
'mb_hp' => $customer_hp,
|
||||
'mb_email' => $customer_email,
|
||||
'mb_sms' => 1,
|
||||
'mb_mailling' => 1
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$send_params['target_type'] = 'single';
|
||||
$send_params['member_id'] = $customer['mb_id'];
|
||||
}
|
||||
$sender->send($send_params);
|
||||
|
||||
// 2. 배정된 전문가에게 알림
|
||||
if ($expert) {
|
||||
$sender->send([
|
||||
'target_type' => 'single',
|
||||
'member_id' => $expert['mb_id'],
|
||||
'email_template_key' => 'expert_new_assignment',
|
||||
'sms_template_key' => 'expert_new_assignment',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
}
|
||||
break;
|
||||
|
||||
case '예약취소':
|
||||
// 1. 고객에게 예약 취소 알림
|
||||
$send_params = [
|
||||
'email_template_key' => 'expert_visit_cancelled',
|
||||
'sms_template_key' => 'expert_visit_cancelled',
|
||||
'vars' => $common_vars,
|
||||
];
|
||||
|
||||
if ($is_guest) {
|
||||
$send_params['target_type'] = 'direct';
|
||||
$send_params['receivers'] = [
|
||||
[
|
||||
'mb_name' => $customer_name,
|
||||
'mb_hp' => $customer_hp,
|
||||
'mb_email' => $customer_email,
|
||||
'mb_sms' => 1,
|
||||
'mb_mailling' => 1
|
||||
]
|
||||
];
|
||||
} else {
|
||||
$send_params['target_type'] = 'single';
|
||||
$send_params['member_id'] = $customer['mb_id'];
|
||||
}
|
||||
$sender->send($send_params);
|
||||
|
||||
// 2. 관리자에게 알림
|
||||
$sender->send([
|
||||
'target_type' => 'bulk',
|
||||
'member_levels' => [10],
|
||||
'email_template_key' => 'admin_visit_cancelled',
|
||||
'sms_template_key' => 'admin_visit_cancelled',
|
||||
'vars' => $common_vars,
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
?>
|
||||
@@ -0,0 +1,380 @@
|
||||
<?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')) {
|
||||
// 💡 [수정] 라이브러리 원본 파일(icode.lms.lib.php 등)의 Deprecated 경고를 숨기기 위해 에러 리포팅 임시 조정
|
||||
// (PHP 8.0+에서 선택적 파라미터 선언 순서 문제로 인한 경고 회피)
|
||||
$old_reporting_level = error_reporting();
|
||||
error_reporting($old_reporting_level & ~E_DEPRECATED);
|
||||
|
||||
include_once(G5_PLUGIN_PATH . '/sms5/sms5.lib.php');
|
||||
|
||||
error_reporting($old_reporting_level);
|
||||
}
|
||||
|
||||
/**
|
||||
* 통합 메일/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 ($params['target_type'] === 'direct' && empty($params['receivers'])) {
|
||||
return ['success' => false, 'message' => '직접 발송 시 수신자 정보(receivers)가 필요합니다.'];
|
||||
}
|
||||
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 = ''";
|
||||
$this->write_debug_log("[SMS 발송 시작] sql '{$sql}'");
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$members[] = $row;
|
||||
}
|
||||
} elseif ($params['target_type'] === 'bulk') {
|
||||
$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;
|
||||
}
|
||||
} elseif ($params['target_type'] === 'direct') {
|
||||
// 직접 입력된 수신자 정보 사용
|
||||
$members = $params['receivers'];
|
||||
}
|
||||
|
||||
return $members;
|
||||
}
|
||||
|
||||
/**
|
||||
* ❗ [핵심 수정] SMS 발송 (템플릿 및 변수 처리, 이력 기록 포함)
|
||||
*/
|
||||
private function sendSMS($members, $template_key, $common_vars)
|
||||
{
|
||||
global $config; // config 전역 변수 사용
|
||||
|
||||
$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 `order_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';
|
||||
$hs_code = '';
|
||||
|
||||
if ($SMS) {
|
||||
// ❗ [수정] SMS/LMS 타입에 따라 함수 호출 분기 및 파라미터 순서 명시
|
||||
if($config['cf_sms_type'] == 'LMS') {
|
||||
// LMS: Add($strDest, $strCallBack, $strCaller, $strSubject, $strURL, $strData, $strDate, $nCount)
|
||||
// $strDest는 배열로 전달
|
||||
$SMS->Add(array($member['mb_hp']), $send_phone, '', '', '', $personal_message, '', 1);
|
||||
} else {
|
||||
// SMS: Add2($strDest, $strCallBack, $strCaller, $strURL, $strMessage, $strDate, $nCount)
|
||||
// $strDest는 배열(구조체)로 전달
|
||||
$dest = array(array('bk_hp' => $member['mb_hp'], 'bk_name' => $member['mb_name']));
|
||||
$SMS->Add2($dest, $send_phone, '', '', $personal_message, '', 1);
|
||||
}
|
||||
|
||||
$SMS->Send();
|
||||
$result_arr = $SMS->Result;
|
||||
$result_code = 'Fail';
|
||||
$result_msg = '서버로부터 응답이 없습니다.';
|
||||
|
||||
if(!empty($result_arr)){
|
||||
// 💡 [추가] 상세 에러 코드 처리 (sms_write_send.php 참고)
|
||||
foreach ($result_arr as $result) {
|
||||
list($phone, $code) = explode(":", $result);
|
||||
if (substr($code, 0, 5) == "Error") {
|
||||
$hs_code = substr($code, 6, 2);
|
||||
switch ($hs_code) {
|
||||
case '02': $result_msg = "형식이 잘못되어 전송이 실패하였습니다."; break;
|
||||
case '23': $result_msg = "데이터를 다시 확인해 주시기바랍니다."; break;
|
||||
case '97': $result_msg = "잔여코인이 부족합니다."; break;
|
||||
case '98': $result_msg = "사용기간이 만료되었습니다."; break;
|
||||
case '99': $result_msg = "인증 받지 못하였습니다. 계정을 다시 확인해 주세요."; break;
|
||||
default: $result_msg = "알 수 없는 오류로 전송이 실패하였습니다."; break;
|
||||
}
|
||||
$result_code = 'Fail';
|
||||
} else {
|
||||
$hs_code = $code;
|
||||
$result_msg = "전송했습니다.";
|
||||
$result_code = 'Success';
|
||||
}
|
||||
}
|
||||
}
|
||||
$hs_status = ($result_code == 'Success') ? '1' : '0';
|
||||
$SMS->Init();
|
||||
}
|
||||
|
||||
$mb_id = isset($member['mb_id']) ? $member['mb_id'] : ''; // 비회원일 경우 mb_id 없음
|
||||
sql_query("INSERT INTO {$this->g5['sms5_history_table']} (wr_no, mb_id, hs_name, hs_hp, hs_datetime, hs_status, hs_code, hs_message) VALUES ('{$wr_no}', '{$mb_id}', '{$member['mb_name']}', '{$member['mb_hp']}', '" . G5_TIME_YMDHIS . "', '{$hs_status}', '{$hs_code}', '{$result_msg}')");
|
||||
|
||||
if ($hs_status == '1') $success++;
|
||||
else $fail++;
|
||||
} else {
|
||||
$fail++;
|
||||
}
|
||||
}
|
||||
|
||||
// 💡 [추가] 발송 완료 후 마스터 테이블 업데이트
|
||||
sql_query("UPDATE {$this->g5['sms5_write_table']} SET wr_success = '{$success}', wr_failure = '{$fail}' WHERE wr_no = '{$wr_no}'");
|
||||
}
|
||||
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);
|
||||
$template = sql_fetch("SELECT * FROM `order_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)];
|
||||
}
|
||||
if ($is_test_mode) {
|
||||
// --- 개발 모드: 로그 파일에만 기록 ---
|
||||
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']]);
|
||||
$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);
|
||||
}
|
||||
if($mailSender->sendSimple($member['mb_email'], $subject,$content)){
|
||||
$success++;
|
||||
} else {
|
||||
$fail++;
|
||||
}
|
||||
|
||||
} else {
|
||||
$fail++;
|
||||
$empty = !empty($member['mb_email']);
|
||||
$this->write_debug_log("[EMAIL 발송 오류] 사용자 '{$member['mb_sms']}' '{$member['mb_hp']}' '$empty'");
|
||||
}
|
||||
}
|
||||
}
|
||||
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}' 파일에 내용을 쓸 수 없습니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user