Files
firstgarden-web-gnu/plugin/kakao5/kakao5.lib.php
2025-09-04 12:37:12 +09:00

449 lines
17 KiB
PHP

<?php
if (!defined('_GNUBOARD_')) exit;
include_once(G5_KAKAO5_PATH.'/_common.php');
require_once(G5_KAKAO5_PATH.'/kakao5_popbill.lib.php');
/*************************************************************************
**
** 알림톡 함수 모음
**
*************************************************************************/
/**
* 프리셋 코드를 사용하여 알림톡을 전송하는 함수
*/
function send_alimtalk_preset($preset_code, array $recipient, $conditions = [])
{
global $g5, $sender_hp, $member, $config;
// 알림톡 사용 설정 확인
if (empty($config['cf_kakaotalk_use'])) {
return array('success' => false, 'msg' => '알림톡 사용이 설정되어 있지 않습니다.');
}
// 프리셋 코드로 프리셋 정보 확인
$preset_info = get_alimtalk_preset_info($preset_code);
if (isset($preset_info['error'])) {
return array('success' => false, 'msg' => $preset_info['error'], 'data' => $preset_info);
}
$template_code = $preset_info['template_code']; // 템플릿 코드
// 수신자 정리 (전화번호 숫자만)
$receiver_hp = preg_replace('/[^0-9]/', '', $recipient['rcv'] ?? '');
$receiver_nm = $recipient['rcvnm'] ?? '';
// 수신자 정보 배열 구성
$messages = [['rcv' => $receiver_hp, 'rcvnm' => $receiver_nm]];
// 주문내역에서 mb_id 조회
if (empty($conditions['mb_id']) && !empty($conditions['od_id'])) {
$sql = "SELECT mb_id FROM {$g5['g5_shop_order_table']} WHERE od_id = '" . sql_escape_string($conditions['od_id']) . "' LIMIT 1";
$row = sql_fetch($sql);
if ($row && !empty($row['mb_id'])) {
$conditions['mb_id'] = $row['mb_id'];
} else {
$conditions['mb_id'] = $member['mb_id'] ?? 'GUEST';
}
}
// 전송요청번호 생성
$request_num = generate_alimtalk_request_id($conditions['mb_id'], $preset_code);
// 전송 내역 초기 저장
$history_id = save_alimtalk_history($preset_info['preset_id'], $template_code, $preset_info['alt_send'], $request_num, $receiver_nm, $receiver_hp, $conditions['mb_id']);
// 템플릿 정보 조회
$full_template_info = '';
if($config['cf_kakaotalk_use'] === 'popbill'){ // 팝빌
$full_template_info = get_popbill_template_info($template_code);
}
// 템플릿 정보를 못 불러 올 경우 - 발송 취소
if (is_array($full_template_info) && isset($full_template_info['error'])) {
// 탬플릿 정보 조회 실패: 알림톡 전송내역 업데이트
$messages = "템플릿 정보 조회 실패: ".$full_template_info['error'];
update_alimtalk_history($history_id, ['ph_log' => $messages]);
return array('success' => false, 'msg' => $messages, 'data' => $full_template_info);
}
// 템플릿 내용 변수 치환
$content = replace_alimtalk_content_vars($full_template_info->template, $conditions);
// 버튼 링크 치환
$buttons = set_alimtalk_button_links($full_template_info->btns, $conditions);
try {
// 알림톡 전송 정보
$data = [
'template_code' => $template_code,
'sender_hp' => $sender_hp,
'content' => $content,
'alt_content' => $content,
'alt_send' => ($preset_info['alt_send'] == '1') ? 'C' : null,
'messages' => $messages,
'reserveDT' => null,
'request_num' => $request_num,
'buttons' => $buttons,
'alt_subject' => $preset_info['preset_name']
];
$receipt_num = '';
if ($config['cf_kakaotalk_use'] === 'popbill') { // 팝빌 전송
$receipt_num = send_popbill_alimtalk($data);
}
// 전송 결과 처리
if ((is_array($receipt_num) && isset($receipt_num['error'])) || empty($receipt_num)) {
// 전송 실패: 알림톡 전송내역 업데이트
$error_message = is_array($receipt_num) && isset($receipt_num['error']) ? $receipt_num['error'] : '알림톡 전송 결과가 비어 있습니다.';
$messages = '알림톡 전송 실패하였습니다.\n' . $error_message;
update_alimtalk_history($history_id, ['ph_log' => $messages, 'ph_state' => 2]);
return array('success' => false, 'msg' => $messages, 'code' => (is_array($receipt_num) && isset($receipt_num['code']) ? $receipt_num['code'] : null));
} else {
// 전송 성공: 알림톡 전송내역 업데이트
$messages = '알림톡이 정상적으로 전송되었습니다.';
update_alimtalk_history($history_id, ['ph_receipt_num' => $receipt_num, 'ph_state' => 1, 'ph_log' => $content]);
return array('success' => true, 'msg' => $messages, 'receipt_num' => $receipt_num);
}
} catch (Exception $e) {
// 전송 오류: 알림톡 전송내역 업데이트
$messages = '알림톡 전송 중 오류가 발생하였습니다.\n' . $e->getMessage();
update_alimtalk_history($history_id, ['ph_log' => $messages, 'ph_state' => 2]);
return array('success' => false, 'msg' => $messages, 'code' => $e->getCode());
}
}
/**
* 프리셋 코드로 프리셋 정보 확인
*/
function get_alimtalk_preset_info($preset_code)
{
global $g5;
if (empty($preset_code)) {
return array('error' => '프리셋 코드가 입력되지 않았습니다.');
}
// 프리셋 코드로 프리셋 정보 조회
$sql = "SELECT * FROM {$g5['kakao5_preset_table']} WHERE kp_preset_code = '" . sql_escape_string($preset_code) . "'";
$preset = sql_fetch($sql);
if (!$preset) {
return array('error' => '해당 프리셋 코드(' . $preset_code . ')가 존재하지 않습니다.');
}
// 활성화 상태 확인
if ($preset['kp_active'] != '1') {
return array('error' => '프리셋(' . $preset['kp_preset_name'] . ')이 비활성화되어 있습니다.');
}
// 템플릿 코드 확인
if (empty($preset['kp_template_name'])) {
return array('error' => '프리셋(' . $preset['kp_preset_name'] . ')에 템플릿이 설정되지 않았습니다.');
}
// 모든 조건을 만족하면 프리셋 정보 반환
return array(
'success' => true,
'preset' => $preset,
'preset_id' => $preset['kp_id'],
'preset_name' => $preset['kp_preset_name'],
'preset_code' => $preset['kp_preset_code'],
'template_code' => $preset['kp_template_name'],
'alt_send' => $preset['kp_alt_send'],
'type' => $preset['kp_type']
);
}
/**
* 템플릿 내용 변수 치환
*/
function replace_alimtalk_content_vars($content, $conditions = [])
{
global $g5, $kakao5_preset_variable_list;
$replacements = [];
// 1. 템플릿에서 변수 추출
if (!preg_match_all('/#\{(.*?)\}/', $content, $matches) || empty($matches[1])) {
return $content;
}
$found_vars = array_unique($matches[1]);
// 2. 변수 정의 맵 캐싱
static $var_info_map = null;
if ($var_info_map === null) {
$var_info_map = [];
foreach ($kakao5_preset_variable_list as $category) {
foreach ($category['variables'] as $var) {
if (preg_match('/#\{(.*?)\}/', $var['name'], $match) && isset($match[1])) {
$var_info_map[$match[1]] = $var;
}
}
}
}
// 3. 쿼리 맵 구성 및 치환값 우선 결정
$query_map = [];
$var_to_query = [];
foreach ($found_vars as $var_name) {
$replacement_key = "#{{$var_name}}";
// 1순위: 변수 정의가 있고, $conditions에 column값이 있으면 바로 치환
if (isset($var_info_map[$var_name])) {
$var = $var_info_map[$var_name];
$column = $var['column'];
$table = $g5[$var['table'] ?? ''];
$condition_key = $var['condition_key'] ?? '';
if (isset($conditions[$column])) {
$replacements[$replacement_key] = $conditions[$column];
continue;
}
// 테이블명에 게시판과 같이 뒤에 붙는 변수가 있을 경우 사용
$table_placeholder = isset($var['table_placeholder']) ? trim($var['table_placeholder'], '{}') : '';
if ($table_placeholder && !empty($conditions[$table_placeholder])) {
$table .= $conditions[$table_placeholder];
}
// 2순위: 변수정의에 따라 DB 조회 필요
$where = '';
if(!empty($condition_key)) {
if (!isset($conditions[$condition_key])) {
$replacements[$replacement_key] = '';
continue;
}
$cond_val = sql_escape_string($conditions[$condition_key]);
$where = "{$condition_key} = '{$cond_val}'";
}
$query_key = "{$table}|{$where}";
if (!isset($query_map[$query_key])) {
$query_map[$query_key] = [
'table' => $table,
'where' => $where,
'columns' => [],
'is_price' => $var['is_price'] ?? false,
];
}
$query_map[$query_key]['columns'][$var_name] = $column;
$var_to_query[$var_name] = $query_key;
continue;
}
// 4. 조건값이 없으면 조회 불가 → 빈값
$replacements[$replacement_key] = '';
}
// 4. DB 조회 (필요한 경우만)
$query_results = [];
foreach ($query_map as $query_key => $info) {
$table = $info['table'];
$where = $info['where'];
$columns = array_unique(array_values($info['columns']));
$column_sql = implode(',', $columns);
$sql = "SELECT {$column_sql} FROM {$table}";
if (!empty($where)) {
$sql .= " WHERE {$where}";
}
$sql .= " LIMIT 1";
$query_results[$query_key] = sql_fetch($sql) ?: [];
}
// 5. DB 결과로 치환값 보완
foreach ($found_vars as $var_name) {
$replacement_key = "#{{$var_name}}";
if (isset($replacements[$replacement_key])) continue; // 이미 치환된 값 있음
if (isset($var_to_query[$var_name])) {
$query_key = $var_to_query[$var_name];
$column = $query_map[$query_key]['columns'][$var_name];
$value = $query_results[$query_key][$column] ?? '';
// is_price일경우 숫자(정수 또는 실수)라면 number_format 적용
if (isset($var_info_map[$var_name]['is_price']) && $var_info_map[$var_name]['is_price'] && is_numeric($value) && $value !== '') {
$value = number_format($value);
}
$replacements[$replacement_key] = $value;
} else {
$replacements[$replacement_key] = '';
}
}
return strtr($content, $replacements);
}
/**
* 버튼 링크 치환
*/
function set_alimtalk_button_links($btns, $conditions = [])
{
// [정의] $kakao5_preset_button_links - extend/kakao5.extend.php
global $kakao5_preset_button_links;
$buttons = [];
if (!empty($btns)) {
foreach ($btns as $idx => $btn) {
// 버튼의 u1, u2에 대해 #{...} 플레이스홀더를 찾아 알맞은 URL로 치환
foreach (['u1', 'u2'] as $field) {
if (isset($btn->$field)) {
if (preg_match('/#\{(.*?)\}/', $btn->$field, $match)) {
$placeholder = $match[0];
if (isset($kakao5_preset_button_links[$placeholder])) {
$url = $kakao5_preset_button_links[$placeholder]['url'];
// URL 내 {변수} 치환
if (preg_match_all('/\{(.*?)\}/', $url, $url_vars)) {
foreach ($url_vars[1] as $var_name) {
// 치환할 값이 없으면 빈 문자열 처리
$replace_val = $conditions[$var_name] ?? '';
// URL로 쓰일 수 있으므로 안전하게 인코딩
$url = str_replace('{' . $var_name . '}', urlencode($replace_val), $url);
}
}
$btn->$field = $url;
}
}
}
}
$buttons[] = (array)$btn;
}
}
return $buttons;
}
/**
* 전송요청번호 생성 (고유성 보장)
*/
function generate_alimtalk_request_id($mb_id, $preset_code)
{
$prefix = substr($preset_code, 0, 1); // 사용자 구분
$mb_hash = substr(md5($mb_id), 0, 4); // mb_id 해시 4자리
$dateTimeStr = date('ymdHis') . sprintf('%03d', (microtime(true) * 1000) % 1000); // 날짜(초) + 마이크로초(밀리초 3자리)
$requestNum = "{$prefix}{$mb_hash}{$dateTimeStr}";
return substr($requestNum, 0, 20);
}
/**
* 알림톡 프리셋 전송 이력 저장
*/
function save_alimtalk_history($preset_id, $template_code, $alt_send, $request_num, $rcvnm, $rcv, $mb_id = '')
{
global $g5;
$sql = "INSERT INTO {$g5['kakao5_preset_history_table']}
(mb_id, kp_id, ph_rcvnm, ph_rcv, ph_template_code, ph_alt_send, ph_request_num, ph_send_datetime, ph_state)
VALUES
('" . sql_escape_string($mb_id) . "',
'" . (int)$preset_id . "',
'" . sql_escape_string($rcvnm) . "',
'" . sql_escape_string($rcv) . "',
'" . sql_escape_string($template_code) . "',
'" . sql_escape_string($alt_send) . "',
'" . sql_escape_string($request_num) . "',
NOW(),
0)";
$result = sql_query($sql);
if ($result) {
return sql_insert_id();
}
return false;
}
/**
* 전송내역 업데이트
*/
function update_alimtalk_history($history_id, $update_data = [])
{
global $g5;
if (!$history_id) {
return false;
}
$set_arr = [];
// update_data가 들어오면 해당 값들로 업데이트
if (!empty($update_data) && is_array($update_data)) {
foreach ($update_data as $key => $val) {
$set_arr[] = sql_escape_string($key) . " = '" . sql_escape_string($val) . "'";
}
}
// 업데이트할 내용이 없음
if (empty($set_arr)) {
return false;
}
$sql = "UPDATE {$g5['kakao5_preset_history_table']}
SET " . implode(', ', $set_arr) . "
WHERE ph_id = '" . (int)$history_id . "'";
return sql_query($sql);
}
/**
* 알림톡용 상품명 생성 (2개 이상일 경우 '외 N건' 추가)
*/
function get_alimtalk_cart_item_name($od_id)
{
global $g5;
$sql = "SELECT it_name FROM {$g5['g5_shop_cart_table']} WHERE od_id = '" . sql_escape_string($od_id) . "'";
$res = sql_query($sql);
$names = array();
while ($row = sql_fetch_array($res)) $names[] = $row['it_name'];
if (!$names) return '';
return $names[0] . ($names[1] ? ' 외 ' . (count($names)-1) . '건' : '');
}
/**
* 관리자 정보로 알림톡 발송
*
* @param string $tpl 템플릿 코드 (예: AD-OR01)
* @param string $type 관리자 유형 (super|group|board)
* @param array $conditions 치환 변수 배열
* @param array $otherTypes 발송 중복 확인용 관리자 유형 ['super', 'group', 'board']
* @return array|false send_alimtalk_preset 반환값 또는 false
*/
function send_admin_alimtalk($tpl, $type = 'super', $conditions = [], $otherTypes = [])
{
$admin = get_admin($type);
// 연락처가 없으면 발송하지 않음
if (empty($admin['mb_hp'])) return false;
// 다른 관리자 정보가 겹치는 게 있으면 발송 안함
if(!empty($otherTypes)){
foreach($otherTypes as $otherType){
// 자기 자신은 비교하지 않음
if ($otherType == $type) continue;
$other = get_admin($otherType, 'mb_id, mb_hp');
if (empty($other)) continue;
// 다른 역할과 동일 인물(또는 동일 번호)이라면 발송하지 않음
$sameId = !empty($admin['mb_id']) && !empty($other['mb_id']) && $admin['mb_id'] == $other['mb_id'];
$sameHp = !empty($other['mb_hp']) && $admin['mb_hp'] == $other['mb_hp'];
if ($sameId || $sameHp) return false;
}
}
return send_alimtalk_preset(
$tpl,
[
'rcv' => $admin['mb_hp'],
'rcvnm' => $admin['mb_name'] ?? ''
],
$conditions
);
}