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); // 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_sms_templates) -> 기본 테이블(sms_templates) 순서로 조회 $template = null; // 1. 지정 테이블 조회 $check_table = sql_query("SHOW TABLES LIKE 'consultant_sms_templates'", false); if (sql_num_rows($check_table) > 0) { $template = sql_fetch("SELECT * FROM `consultant_sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'"); if ($template) { // 필드명 통일 (기본 테이블과 필드명이 다를 수 있음) $template['content'] = $template['template_content']; } } // 2. 기본 테이블 조회 (지정 테이블에 없을 경우) if (!$template) { $template = sql_fetch("SELECT * FROM `sms_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'"); } // 3. 둘 다 없으면 에러 처리 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); // 💡 [수정] 템플릿 조회 로직 변경: 지정 테이블(consultant_mail_templates) -> 기본 테이블(mail_templates) 순서로 조회 $template = null; // 1. 지정 테이블 조회 $check_table = sql_query("SHOW TABLES LIKE 'consultant_mail_templates'", false); if (sql_num_rows($check_table) > 0) { $template = sql_fetch("SELECT * FROM `consultant_mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'"); if ($template) { // 필드명 통일 $template['subject'] = $template['template_subject']; $template['content'] = $template['template_content']; } } // 2. 기본 테이블 조회 (지정 테이블에 없을 경우) if (!$template) { $template = sql_fetch("SELECT * FROM `mail_templates` WHERE template_key = '" . sql_real_escape_string($template_key) . "'"); } // 3. 둘 다 없으면 에러 처리 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']]); // 💡 [수정] MailSender가 템플릿 키로 조회하는 방식일 수 있으므로, // 커스텀 템플릿 내용을 직접 전달하거나 MailSender를 수정해야 할 수 있음. // 여기서는 MailSender가 템플릿 키를 받아서 내부적으로 처리한다고 가정하고, // 만약 커스텀 템플릿을 사용해야 한다면 MailSender의 동작 방식에 따라 수정이 필요함. // 현재 구조상 MailSender::send()는 템플릿 키를 받으므로, // MailSender 내부에서도 동일한 우선순위 로직이 필요하거나, // 여기서 내용을 다 만들어서 보내는 방식(sendDirect 등)이 있다면 그걸 써야 함. // 일단 기존 로직 유지하되, MailSender가 커스텀 테이블을 인지하지 못할 수 있음을 주석으로 남김. // 만약 MailSender가 내용을 직접 받는 메소드가 없다면, // 여기서 내용을 치환해서 보내는 로직을 직접 구현해야 할 수도 있음. // 하지만 요청사항은 "지정 테이블을 읽고 없으면 기본 테이블을 읽어서 발송"이므로, // 위에서 $template을 구했으니, 내용을 직접 치환해서 메일 발송 함수(mailer)를 직접 호출하는 것이 안전함. $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); } // G5 기본 mailer 함수 사용 (MailSender 의존성 제거 또는 우회) include_once(G5_LIB_PATH.'/mailer.lib.php'); mailer($this->g5['title'], $this->g5['admin_email'], $member['mb_email'], $subject, $content, 1); $success++; } 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}' 파일에 내용을 쓸 수 없습니다."); } } }