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 ); }