Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9aadcf3e99 | ||
|
|
0d247c542c | ||
|
|
148d748c2d | ||
|
|
de7063192e | ||
|
|
e2d215c368 | ||
|
|
c4cd61c916 | ||
|
|
f7aa440a80 | ||
|
|
2517343d51 | ||
|
|
6dec001465 | ||
|
|
062b33dde3 | ||
|
|
97b5d7e122 | ||
|
|
40096220e2 | ||
|
|
7a80ae913d | ||
|
|
560aa0af47 | ||
|
|
6d4e75e1c9 | ||
|
|
77e0cda39b | ||
|
|
cf8fc646ea | ||
|
|
e769d11b79 | ||
|
|
b356b3710c | ||
|
|
1ec79e8e33 | ||
|
|
5bd9cd2d17 | ||
|
|
e4bd69ab82 | ||
|
|
ff8e8741b3 | ||
|
|
ded3349bfc | ||
|
|
93b4352891 | ||
|
|
53b954b47f | ||
|
|
3510a03f57 | ||
|
|
83e3c00a05 | ||
|
|
2adaa0a658 | ||
|
|
a9ba776ef0 | ||
|
|
76b1122430 | ||
|
|
313f1daeb0 | ||
|
|
873839add7 | ||
|
|
65875b7f35 | ||
|
|
fcc749bde3 | ||
|
|
639b788ad1 | ||
|
|
69709a79a7 | ||
|
|
672d05f968 | ||
|
|
d0f136cca0 | ||
|
|
fd303da77a | ||
|
|
e784186b0f | ||
|
|
e74486e171 | ||
|
|
545e6a504d | ||
|
|
1da82df500 | ||
|
|
5bdbf360b0 | ||
|
|
d111100856 | ||
|
|
7b3c2e845b |
@@ -1,3 +1,6 @@
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
*.php whitespace=blank-at-eol,blank-at-eof,space-before-tab,cr-at-eol
|
||||
|
||||
/tests export-ignore
|
||||
/bin export-ignore
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
/.htaccess
|
||||
data/
|
||||
test/
|
||||
/tests/
|
||||
/bin/
|
||||
sirgle/
|
||||
test.php
|
||||
*.key
|
||||
|
||||
@@ -65,8 +65,18 @@ cd gnuboard5
|
||||
|
||||
> 기존 사이트에 재설치하면 데이터가 삭제될 수 있습니다. 진행하기 전에 파일과 데이터베이스를 백업하십시오.
|
||||
|
||||
### 기존 설치 업데이트
|
||||
|
||||
소스 코드를 업데이트한 뒤 데이터베이스 변경이 포함된 버전은 관리자 메뉴의 **환경설정 → DB업그레이드**에서 변경 내용을 확인하고 명시적으로 실행합니다.
|
||||
|
||||
운영 절차와 마이그레이션 작성 규칙은 [데이터베이스 마이그레이션 문서](docs/database-migrations.md)를 확인하십시오.
|
||||
|
||||
## 문서 및 지원
|
||||
|
||||
- [5.6.391 쇼핑몰 분류 표시 수정 안내](docs/release-5.6.391.md)
|
||||
- [5.6.39 보안 업데이트 및 필수 DB업그레이드 안내](docs/release-5.6.39.md)
|
||||
|
||||
- [5.6.37 SIRK 전용 카카오페이 연동 종료 및 기존 거래 지원](docs/sirk-kakaopay-retirement.md)
|
||||
- [그누보드5 공식 페이지](https://sir.kr/main/g5/)
|
||||
- [그누보드5 공식 매뉴얼](https://sir.kr/manuals/g5/1)
|
||||
- [그누보드5 Q&A](https://sir.kr/questions?s_tag=%EA%B7%B8%EB%88%84%EB%B3%B4%EB%93%9C5)
|
||||
|
||||
@@ -478,6 +478,30 @@ function check_log_folder($log_path, $is_delete = true)
|
||||
}
|
||||
}
|
||||
|
||||
// 회원 본인확인 식별값과 인증 이력만 정리한다. 호출부에서 관리자 권한을 검증한다.
|
||||
function admin_clear_member_certification($mb_id)
|
||||
{
|
||||
global $g5;
|
||||
|
||||
$esc_mb_id = sql_real_escape_string($mb_id);
|
||||
$history_table = isset($g5['member_cert_history_table']) ? $g5['member_cert_history_table'] : G5_TABLE_PREFIX.'member_cert_history';
|
||||
|
||||
// 일반 회원정보(이름·연락처)와 이메일 인증 상태는 유지한다.
|
||||
if (!sql_query(" update {$g5['member_table']}
|
||||
set mb_certify = '', mb_adult = 0, mb_dupinfo = '', mb_birth = '', mb_sex = ''
|
||||
where mb_id = '{$esc_mb_id}' ", false)) {
|
||||
return false;
|
||||
}
|
||||
if (!sql_query(" delete from {$history_table} where mb_id = '{$esc_mb_id}' ", false)) {
|
||||
return false;
|
||||
}
|
||||
if (!sql_query(" delete from {$g5['cert_history_table']} where mb_id = '{$esc_mb_id}' ", false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// POST로 넘어온 토큰과 세션에 저장된 토큰 비교
|
||||
function check_admin_token()
|
||||
{
|
||||
|
||||
+1
-62
@@ -13,81 +13,20 @@ if (!$row['cnt']) {
|
||||
|
||||
$html_title = '게시판';
|
||||
|
||||
if (!isset($board['bo_device'])) {
|
||||
// 게시판 사용 필드 추가
|
||||
// both : pc, mobile 둘다 사용
|
||||
// pc : pc 전용 사용
|
||||
// mobile : mobile 전용 사용
|
||||
// none : 사용 안함
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_device` ENUM( 'both', 'pc', 'mobile' ) NOT NULL DEFAULT 'both' AFTER `bo_subject` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_mobile_skin'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_mobile_skin` VARCHAR(255) NOT NULL DEFAULT '' AFTER `bo_skin` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_gallery_width'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_gallery_width` INT NOT NULL AFTER `bo_gallery_cols`, ADD `bo_gallery_height` INT NOT NULL DEFAULT '0' AFTER `bo_gallery_width`, ADD `bo_mobile_gallery_width` INT NOT NULL DEFAULT '0' AFTER `bo_gallery_height`, ADD `bo_mobile_gallery_height` INT NOT NULL DEFAULT '0' AFTER `bo_mobile_gallery_width` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_mobile_subject_len'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_mobile_subject_len` INT(11) NOT NULL DEFAULT '0' AFTER `bo_subject_len` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_mobile_page_rows'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_mobile_page_rows` INT(11) NOT NULL DEFAULT '0' AFTER `bo_page_rows` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_mobile_content_head'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_mobile_content_head` TEXT NOT NULL AFTER `bo_content_head`, ADD `bo_mobile_content_tail` TEXT NOT NULL AFTER `bo_content_tail`", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_use_cert'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_use_cert` ENUM('','cert','adult') NOT NULL DEFAULT '' AFTER `bo_use_email` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_use_sns'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_use_sns` TINYINT NOT NULL DEFAULT '0' AFTER `bo_use_cert` ", false);
|
||||
|
||||
$result = sql_query(" select bo_table from `{$g5['board_table']}` ");
|
||||
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['write_prefix']}{$row['bo_table']}`
|
||||
ADD `wr_facebook_user` VARCHAR(255) NOT NULL DEFAULT '' AFTER `wr_ip`,
|
||||
ADD `wr_twitter_user` VARCHAR(255) NOT NULL DEFAULT '' AFTER `wr_facebook_user` ", false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['board_table']}` LIKE 'bo_use_cert' ";
|
||||
$row = sql_fetch($sql);
|
||||
if (strpos($row['Type'], 'hp-') === false) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` CHANGE `bo_use_cert` `bo_use_cert` ENUM('','cert','adult','hp-cert','hp-adult') NOT NULL DEFAULT '' ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_use_list_file'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_use_list_file` TINYINT NOT NULL DEFAULT '0' AFTER `bo_use_list_view` ", false);
|
||||
|
||||
$result = sql_query(" select bo_table from `{$g5['board_table']}` ");
|
||||
for ($i=0; $row=sql_fetch_array($result); $i++) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['write_prefix']}{$row['bo_table']}`
|
||||
ADD `wr_file` TINYINT NOT NULL DEFAULT '0' AFTER `wr_datetime` ", false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($board['bo_mobile_subject'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_mobile_subject` VARCHAR(255) NOT NULL DEFAULT '' AFTER `bo_subject` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_use_captcha'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_use_captcha` TINYINT NOT NULL DEFAULT '0' AFTER `bo_use_sns` ", false);
|
||||
}
|
||||
|
||||
if (!isset($board['bo_select_editor'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_table']}` ADD `bo_select_editor` VARCHAR(50) NOT NULL DEFAULT '' AFTER `bo_use_dhtml_editor` ", false);
|
||||
}
|
||||
|
||||
$board_default = array(
|
||||
'bo_mobile_subject'=>'',
|
||||
@@ -1500,4 +1439,4 @@ function fboardform_submit(f)
|
||||
</script>
|
||||
|
||||
<?php
|
||||
require_once './admin.tail.php';
|
||||
require_once './admin.tail.php';
|
||||
|
||||
@@ -31,9 +31,6 @@ if ($w == '') {
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.');
|
||||
}
|
||||
|
||||
if (!isset($group['gr_device'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['group_table']}` ADD `gr_device` ENUM('both','pc','mobile') NOT NULL DEFAULT 'both' AFTER `gr_subject` ", false);
|
||||
}
|
||||
|
||||
// 접근회원수
|
||||
$sql1 = " select count(*) as cnt from {$g5['group_member_table']} where gr_id = '{$gr_id}' ";
|
||||
|
||||
@@ -4,14 +4,6 @@ require_once './_common.php';
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
if (!isset($group['gr_device'])) {
|
||||
// 게시판 그룹 사용 필드 추가
|
||||
// both : pc, mobile 둘다 사용
|
||||
// pc : pc 전용 사용
|
||||
// mobile : mobile 전용 사용
|
||||
// none : 사용 안함
|
||||
sql_query(" ALTER TABLE `{$g5['group_table']}` ADD `gr_device` ENUM( 'both', 'pc', 'mobile' ) NOT NULL DEFAULT 'both' AFTER `gr_subject` ", false);
|
||||
}
|
||||
|
||||
$sql_common = " from {$g5['group_table']} ";
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
$sub_menu = "300200";
|
||||
require_once './_common.php';
|
||||
|
||||
sql_query(" ALTER TABLE {$g5['group_member_table']} CHANGE `gm_id` `gm_id` INT( 11 ) DEFAULT '0' NOT NULL AUTO_INCREMENT ", false);
|
||||
|
||||
if ($w == '') {
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
+9
-433
@@ -12,440 +12,9 @@ if ($is_admin != 'super') {
|
||||
$sql = " select * from {$g5['config_table']} limit 1";
|
||||
$config = sql_fetch($sql);
|
||||
|
||||
if (!isset($config['cf_add_script'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_add_script` TEXT NOT NULL AFTER `cf_admin_email_name` ",
|
||||
true
|
||||
);
|
||||
if (!isset($config['cf_email_certify_minutes'])) {
|
||||
alert('DB 업그레이드가 필요합니다.', G5_ADMIN_URL . '/dbupgrade.php');
|
||||
}
|
||||
|
||||
if (!isset($config['cf_mobile_new_skin'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_mobile_new_skin` VARCHAR(255) NOT NULL AFTER `cf_memo_send_point`,
|
||||
ADD `cf_mobile_search_skin` VARCHAR(255) NOT NULL AFTER `cf_mobile_new_skin`,
|
||||
ADD `cf_mobile_connect_skin` VARCHAR(255) NOT NULL AFTER `cf_mobile_search_skin`,
|
||||
ADD `cf_mobile_member_skin` VARCHAR(255) NOT NULL AFTER `cf_mobile_connect_skin` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (isset($config['cf_gcaptcha_mp3'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
CHANGE `cf_gcaptcha_mp3` `cf_captcha_mp3` VARCHAR(255) NOT NULL DEFAULT '' ",
|
||||
true
|
||||
);
|
||||
} elseif (!isset($config['cf_captcha_mp3'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_captcha_mp3` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_mobile_member_skin` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_editor'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_editor` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_captcha_mp3` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_googl_shorturl_apikey'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_googl_shorturl_apikey` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_captcha_mp3` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_mobile_pages'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_mobile_pages` INT(11) NOT NULL DEFAULT '0' AFTER `cf_write_pages` ",
|
||||
true
|
||||
);
|
||||
sql_query(" UPDATE `{$g5['config_table']}` SET cf_mobile_pages = '5' ", true);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_facebook_appid'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_facebook_appid` VARCHAR(255) NOT NULL AFTER `cf_googl_shorturl_apikey`,
|
||||
ADD `cf_facebook_secret` VARCHAR(255) NOT NULL AFTER `cf_facebook_appid`,
|
||||
ADD `cf_twitter_key` VARCHAR(255) NOT NULL AFTER `cf_facebook_secret`,
|
||||
ADD `cf_twitter_secret` VARCHAR(255) NOT NULL AFTER `cf_twitter_key` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// uniqid 테이블이 없을 경우 생성
|
||||
if (!sql_query(" DESC {$g5['uniqid_table']} ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['uniqid_table']}` (
|
||||
`uq_id` bigint(20) unsigned NOT NULL,
|
||||
`uq_ip` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`uq_id`)
|
||||
) ",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (!sql_query(" SELECT uq_ip from {$g5['uniqid_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE {$g5['uniqid_table']} ADD `uq_ip` VARCHAR(255) NOT NULL ");
|
||||
}
|
||||
|
||||
// 임시저장 테이블이 없을 경우 생성
|
||||
if (!sql_query(" DESC {$g5['autosave_table']} ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['autosave_table']}` (
|
||||
`as_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`mb_id` varchar(20) NOT NULL,
|
||||
`as_uid` bigint(20) unsigned NOT NULL,
|
||||
`as_subject` varchar(255) NOT NULL,
|
||||
`as_content` text NOT NULL,
|
||||
`as_datetime` datetime NOT NULL,
|
||||
PRIMARY KEY (`as_id`),
|
||||
UNIQUE KEY `as_uid` (`as_uid`),
|
||||
KEY `mb_id` (`mb_id`)
|
||||
) ",
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_admin_email'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_admin_email` VARCHAR(255) NOT NULL AFTER `cf_admin` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_admin_email_name'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_admin_email_name` VARCHAR(255) NOT NULL AFTER `cf_admin_email` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_cert_use'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_cert_use` TINYINT(4) NOT NULL DEFAULT '0' AFTER `cf_editor`,
|
||||
ADD `cf_cert_ipin` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_use`,
|
||||
ADD `cf_cert_hp` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_ipin`,
|
||||
ADD `cf_cert_kcb_cd` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_hp`,
|
||||
ADD `cf_cert_kcp_cd` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_kcb_cd`,
|
||||
ADD `cf_cert_limit` INT(11) NOT NULL DEFAULT '0' AFTER `cf_cert_kcp_cd` ",
|
||||
true
|
||||
);
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['member_table']}`
|
||||
CHANGE `mb_hp_certify` `mb_certify` VARCHAR(20) NOT NULL DEFAULT '' ",
|
||||
true
|
||||
);
|
||||
sql_query(" update {$g5['member_table']} set mb_certify = 'hp' where mb_certify = '1' ");
|
||||
sql_query(" update {$g5['member_table']} set mb_certify = '' where mb_certify = '0' ");
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['cert_history_table']}` (
|
||||
`cr_id` int(11) NOT NULL auto_increment,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`cr_company` varchar(255) NOT NULL DEFAULT '',
|
||||
`cr_method` varchar(255) NOT NULL DEFAULT '',
|
||||
`cr_ip` varchar(255) NOT NULL DEFAULT '',
|
||||
`cr_date` date NOT NULL DEFAULT '0000-00-00',
|
||||
`cr_time` time NOT NULL DEFAULT '00:00:00',
|
||||
PRIMARY KEY (`cr_id`),
|
||||
KEY `mb_id` (`mb_id`)
|
||||
)",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_analytics'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_analytics` TEXT NOT NULL AFTER `cf_intercept_ip` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_add_meta'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_add_meta` TEXT NOT NULL AFTER `cf_analytics` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_syndi_token'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_syndi_token` VARCHAR(255) NOT NULL AFTER `cf_add_meta` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_syndi_except'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_syndi_except` TEXT NOT NULL AFTER `cf_syndi_token` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_sms_use'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_sms_use` varchar(255) NOT NULL DEFAULT '' AFTER `cf_cert_limit`,
|
||||
ADD `cf_icode_id` varchar(255) NOT NULL DEFAULT '' AFTER `cf_sms_use`,
|
||||
ADD `cf_icode_pw` varchar(255) NOT NULL DEFAULT '' AFTER `cf_icode_id`,
|
||||
ADD `cf_icode_server_ip` varchar(255) NOT NULL DEFAULT '' AFTER `cf_icode_pw`,
|
||||
ADD `cf_icode_server_port` varchar(255) NOT NULL DEFAULT '' AFTER `cf_icode_server_ip` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_mobile_page_rows'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_mobile_page_rows` int(11) NOT NULL DEFAULT '0' AFTER `cf_page_rows` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_cert_req'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_cert_req` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_cert_limit` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_faq_skin'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_faq_skin` varchar(255) NOT NULL DEFAULT '' AFTER `cf_connect_skin`,
|
||||
ADD `cf_mobile_faq_skin` varchar(255) NOT NULL DEFAULT '' AFTER `cf_mobile_connect_skin` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// LG유플러스 본인확인 필드 추가
|
||||
if (!isset($config['cf_lg_mid'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_lg_mid` varchar(255) NOT NULL DEFAULT '' AFTER `cf_cert_kcp_cd`,
|
||||
ADD `cf_lg_mert_key` varchar(255) NOT NULL DEFAULT '' AFTER `cf_lg_mid` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['cf_optimize_date'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_optimize_date` date NOT NULL default '0000-00-00' AFTER `cf_popular_del` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 카카오톡링크 api 키
|
||||
if (!isset($config['cf_kakao_js_apikey'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_kakao_js_apikey` varchar(255) NOT NULL DEFAULT '' AFTER `cf_googl_shorturl_apikey` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// SMS 전송유형 필드 추가
|
||||
if (!isset($config['cf_sms_type'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_sms_type` varchar(10) NOT NULL DEFAULT '' AFTER `cf_sms_use` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 접속자 정보 필드 추가
|
||||
if (!sql_query(" select vi_browser from {$g5['visit_table']} limit 1 ")) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['visit_table']}`
|
||||
ADD `vi_browser` varchar(255) NOT NULL DEFAULT '' AFTER `vi_agent`,
|
||||
ADD `vi_os` varchar(255) NOT NULL DEFAULT '' AFTER `vi_browser`,
|
||||
ADD `vi_device` varchar(255) NOT NULL DEFAULT '' AFTER `vi_os` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
//소셜 로그인 관련 필드 및 구글 리챕챠 필드 추가
|
||||
if (!isset($config['cf_social_login_use'])) {
|
||||
sql_query(
|
||||
"ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_social_login_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_googl_shorturl_apikey`,
|
||||
ADD `cf_google_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_twitter_secret`,
|
||||
ADD `cf_google_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_clientid`,
|
||||
ADD `cf_naver_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_secret`,
|
||||
ADD `cf_naver_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_clientid`,
|
||||
ADD `cf_kakao_rest_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_secret`,
|
||||
ADD `cf_social_servicelist` varchar(255) NOT NULL DEFAULT '' AFTER `cf_social_login_use`,
|
||||
ADD `cf_payco_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_social_servicelist`,
|
||||
ADD `cf_payco_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_payco_clientid`,
|
||||
ADD `cf_captcha` varchar(100) NOT NULL DEFAULT '' AFTER `cf_kakao_js_apikey`,
|
||||
ADD `cf_recaptcha_site_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_captcha`,
|
||||
ADD `cf_recaptcha_secret_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_recaptcha_site_key`
|
||||
",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
//소셜 로그인 관련 필드 카카오 클라이언트 시크릿 추가
|
||||
if (!isset($config['cf_kakao_client_secret'])) {
|
||||
sql_query(
|
||||
"ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_kakao_client_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_kakao_rest_key`
|
||||
",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 회원 이미지 관련 필드 추가
|
||||
if (!isset($config['cf_member_img_size'])) {
|
||||
sql_query(
|
||||
"ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_member_img_size` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_icon_height`,
|
||||
ADD `cf_member_img_width` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_img_size`,
|
||||
ADD `cf_member_img_height` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_img_width`
|
||||
",
|
||||
true
|
||||
);
|
||||
|
||||
$sql = " update {$g5['config_table']} set cf_member_img_size = 50000, cf_member_img_width = 60, cf_member_img_height = 60 ";
|
||||
sql_query($sql, false);
|
||||
|
||||
$config['cf_member_img_size'] = 50000;
|
||||
$config['cf_member_img_width'] = 60;
|
||||
$config['cf_member_img_height'] = 60;
|
||||
}
|
||||
|
||||
// 소셜 로그인 관리 테이블 없을 경우 생성
|
||||
if (!sql_query(" DESC {$g5['social_profile_table']} ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['social_profile_table']}` (
|
||||
`mp_no` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`provider` varchar(50) NOT NULL DEFAULT '',
|
||||
`object_sha` varchar(45) NOT NULL DEFAULT '',
|
||||
`identifier` varchar(255) NOT NULL DEFAULT '',
|
||||
`profileurl` varchar(255) NOT NULL DEFAULT '',
|
||||
`photourl` varchar(255) NOT NULL DEFAULT '',
|
||||
`displayname` varchar(150) NOT NULL DEFAULT '',
|
||||
`description` varchar(255) NOT NULL DEFAULT '',
|
||||
`mp_register_day` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`mp_latest_day` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
UNIQUE KEY `mp_no` (`mp_no`),
|
||||
KEY `mb_id` (`mb_id`),
|
||||
KEY `provider` (`provider`)
|
||||
) ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 짧은 URL 주소를 사용 여부 필드 추가
|
||||
if (!isset($config['cf_bbs_rewrite'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_bbs_rewrite` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_link_target` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 읽지 않은 메모 수 칼럼 추가
|
||||
if (!isset($member['mb_memo_cnt'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_memo_cnt` int(11) NOT NULL DEFAULT '0' AFTER `mb_memo_call`",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 스크랩 읽은 수 추가
|
||||
if (!isset($member['mb_scrap_cnt'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_scrap_cnt` int(11) NOT NULL DEFAULT '0' AFTER `mb_memo_cnt`",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 아이코드 토큰키 추가
|
||||
if (!isset($config['cf_icode_token_key'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_icode_token_key` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_icode_server_port`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
// 아이디/비밀번호 찾기에 본인확인 사용 여부 필드 추가
|
||||
if (!isset($config['cf_cert_find'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_find` TINYINT(4) NOT NULL DEFAULT '0' AFTER `cf_cert_use`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
// 간편인증 필드 추가
|
||||
if (!isset($config['cf_cert_simple'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_simple` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_hp`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
if (!isset($config['cf_cert_kg_cd'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_kg_cd` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_simple`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
if (!isset($config['cf_cert_kg_mid'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_kg_mid` VARCHAR(255) NOT NULL DEFAULT '' AFTER `cf_cert_kg_cd`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
if (!isset($config['cf_cert_use_seed'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_use_seed` TINYINT(4) NOT NULL DEFAULT '1' AFTER `cf_cert_kg_mid`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
if (!isset($config['cf_cert_kcp_enckey'])) {
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_cert_kcp_enckey` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_cert_kcp_cd`; ";
|
||||
sql_query($sql, false);
|
||||
|
||||
$config['cf_cert_kcp_enckey'] = '';
|
||||
}
|
||||
|
||||
// 광고성 정보 수신 동의 사용 필드 추가
|
||||
if (!isset($config['cf_use_promotion'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_use_promotion` tinyint(1) NOT NULL DEFAULT '0' AFTER `cf_privacy` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 광고성 정보 수신 동의 여부 필드 추가 + 메일 / SMS 수신 일자 추가
|
||||
if (!isset($member['mb_marketing_agree'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_marketing_agree` tinyint(1) NOT NULL DEFAULT '0' AFTER `mb_scrap_cnt`,
|
||||
ADD `mb_marketing_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_marketing_agree`,
|
||||
ADD `mb_thirdparty_agree` tinyint(1) NOT NULL DEFAULT '0' AFTER `mb_marketing_date`,
|
||||
ADD `mb_thirdparty_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_thirdparty_agree`,
|
||||
ADD `mb_agree_log` TEXT NOT NULL AFTER `mb_thirdparty_date`,
|
||||
ADD `mb_mailling_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_mailling`,
|
||||
ADD `mb_sms_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_sms` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
if (!$config['cf_faq_skin']) {
|
||||
$config['cf_faq_skin'] = "basic";
|
||||
}
|
||||
@@ -1189,6 +758,13 @@ if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw']) {
|
||||
<input type="checkbox" name="cf_use_email_certify" value="1" id="cf_use_email_certify" <?php echo $config['cf_use_email_certify'] ? 'checked' : ''; ?>> 사용
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="cf_email_certify_minutes">메일인증 유효시간</label></th>
|
||||
<td>
|
||||
<?php echo help('인증메일 발송 후 링크를 사용할 수 있는 시간을 분 단위로 설정합니다. 0은 만료시간과 미인증 만료 정리를 적용하지 않습니다.<br>만료된 미인증 회원은 최고관리자 접속 시 하루 한 번 실행되는 정리에서 탈퇴 처리하며, 본인확인 정보와 소셜 연결을 해제합니다. 나머지 회원자료는 회원탈퇴후 삭제일 설정에 따라 정리합니다.') ?>
|
||||
<input type="number" name="cf_email_certify_minutes" value="<?php echo isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60; ?>" id="cf_email_certify_minutes" class="frm_input" min="0"> 분
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="cf_formmail_is_member">폼메일 사용 여부</label></th>
|
||||
<td>
|
||||
|
||||
@@ -56,6 +56,7 @@ if (isset($_POST['cf_intercept_ip']) && $_POST['cf_intercept_ip']) {
|
||||
|
||||
$check_keys = array(
|
||||
'cf_use_email_certify' => 'int',
|
||||
'cf_email_certify_minutes' => 'int',
|
||||
'cf_use_homepage' => 'int',
|
||||
'cf_req_homepage' => 'int',
|
||||
'cf_use_tel' => 'int',
|
||||
@@ -178,6 +179,8 @@ foreach ($check_keys as $k => $v) {
|
||||
}
|
||||
}
|
||||
|
||||
$_POST['cf_email_certify_minutes'] = max(0, $_POST['cf_email_certify_minutes']);
|
||||
|
||||
// 본인확인을 사용할 경우 아이핀, 휴대폰인증 중 하나는 선택되어야 함
|
||||
if ($_POST['cf_cert_use'] && !$_POST['cf_cert_ipin'] && !$_POST['cf_cert_hp'] && !$_POST['cf_cert_simple']) {
|
||||
alert('본인확인을 위해 아이핀, 휴대폰 본인확인, KG이니시스 간편인증 서비스 중 하나 이상 선택해 주십시오.');
|
||||
@@ -226,6 +229,7 @@ $sql = " update {$g5['config_table']}
|
||||
cf_point_term = '{$_POST['cf_point_term']}',
|
||||
cf_use_copy_log = '{$_POST['cf_use_copy_log']}',
|
||||
cf_use_email_certify = '{$_POST['cf_use_email_certify']}',
|
||||
cf_email_certify_minutes = '{$_POST['cf_email_certify_minutes']}',
|
||||
cf_login_point = '{$_POST['cf_login_point']}',
|
||||
cf_cut_name = '{$_POST['cf_cut_name']}',
|
||||
cf_nick_modify = '{$_POST['cf_nick_modify']}',
|
||||
|
||||
@@ -8,41 +8,12 @@ auth_check_menu($auth, $sub_menu, "w");
|
||||
$co_id = isset($_REQUEST['co_id']) ? preg_replace('/[^a-z0-9_]/i', '', $_REQUEST['co_id']) : '';
|
||||
|
||||
// 상단, 하단 파일경로 필드 추가
|
||||
if (!sql_query(" select co_include_head from {$g5['content_table']} limit 1 ", false)) {
|
||||
$sql = " ALTER TABLE `{$g5['content_table']}` ADD `co_include_head` VARCHAR( 255 ) NOT NULL ,
|
||||
ADD `co_include_tail` VARCHAR( 255 ) NOT NULL ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// html purifier 사용여부 필드
|
||||
if (!sql_query(" select co_tag_filter_use from {$g5['content_table']} limit 1 ", false)) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['content_table']}`
|
||||
ADD `co_tag_filter_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `co_content` ",
|
||||
true
|
||||
);
|
||||
sql_query(" update {$g5['content_table']} set co_tag_filter_use = '1' ");
|
||||
}
|
||||
|
||||
// 모바일 내용 추가
|
||||
if (!sql_query(" select co_mobile_content from {$g5['content_table']} limit 1", false)) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['content_table']}`
|
||||
ADD `co_mobile_content` longtext NOT NULL AFTER `co_content` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 스킨 설정 추가
|
||||
if (!sql_query(" select co_skin from {$g5['content_table']} limit 1 ", false)) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['content_table']}`
|
||||
ADD `co_skin` varchar(255) NOT NULL DEFAULT '' AFTER `co_mobile_content`,
|
||||
ADD `co_mobile_skin` varchar(255) NOT NULL DEFAULT '' AFTER `co_skin` ",
|
||||
true
|
||||
);
|
||||
sql_query(" update {$g5['content_table']} set co_skin = 'basic', co_mobile_skin = 'basic' ");
|
||||
}
|
||||
|
||||
$html_title = "내용";
|
||||
$g5['title'] = $html_title . ' 관리';
|
||||
|
||||
@@ -8,30 +8,6 @@ if (!isset($g5['content_table'])) {
|
||||
die('<meta charset="utf-8">/data/dbconfig.php 파일에 <strong>$g5[\'content_table\'] = G5_TABLE_PREFIX.\'content\';</strong> 를 추가해 주세요.');
|
||||
}
|
||||
//내용(컨텐츠)정보 테이블이 있는지 검사한다.
|
||||
if (!sql_query(" DESCRIBE {$g5['content_table']} ", false)) {
|
||||
if (sql_query(" DESCRIBE {$g5['g5_shop_content_table']} ", false)) {
|
||||
sql_query(" ALTER TABLE {$g5['g5_shop_content_table']} RENAME TO `{$g5['content_table']}` ;", false);
|
||||
} else {
|
||||
$query_cp = sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['content_table']}` (
|
||||
`co_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`co_html` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`co_subject` varchar(255) NOT NULL DEFAULT '',
|
||||
`co_content` longtext NOT NULL,
|
||||
`co_hit` int(11) NOT NULL DEFAULT '0',
|
||||
`co_include_head` varchar(255) NOT NULL,
|
||||
`co_include_tail` varchar(255) NOT NULL,
|
||||
PRIMARY KEY (`co_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ",
|
||||
true
|
||||
);
|
||||
|
||||
// 내용관리 생성
|
||||
sql_query(" insert into `{$g5['content_table']}` set co_id = 'company', co_html = '1', co_subject = '회사소개', co_content= '<p align=center><b>회사소개에 대한 내용을 입력하십시오.</b></p>' ", false);
|
||||
sql_query(" insert into `{$g5['content_table']}` set co_id = 'privacy', co_html = '1', co_subject = '개인정보 처리방침', co_content= '<p align=center><b>개인정보 처리방침에 대한 내용을 입력하십시오.</b></p>' ", false);
|
||||
sql_query(" insert into `{$g5['content_table']}` set co_id = 'provision', co_html = '1', co_subject = '서비스 이용약관', co_content= '<p align=center><b>서비스 이용약관에 대한 내용을 입력하십시오.</b></p>' ", false);
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = '내용관리';
|
||||
require_once G5_ADMIN_PATH . '/admin.head.php';
|
||||
|
||||
@@ -696,7 +696,6 @@ a.scf_pgreg {display:inline-block;margin:5px 0 0;padding:5px 10px;background:#22
|
||||
a.kcp_btn {display:inline-block;margin:5px 0 0;padding:5px 10px;background:#226C8B;color:#fff;font-weight:normal;text-decoration:none}
|
||||
a.lg_btn{display:inline-block;margin:5px 0 0;padding:5px 10px;background:#ED008C;color:#fff;font-weight:normal;text-decoration:none}
|
||||
a.kg_btn{display:inline-block;margin:5px 0 0;padding:5px 10px;background:#4A2C7C;color:#fff;font-weight:normal;text-decoration:none}
|
||||
a.kakao_btn{display:inline-block;margin:5px 0 0;padding:5px 10px;background:#FDDC2F;color:#3B1E1E;font-weight:normal;text-decoration:none}
|
||||
a.naver_btn {display:inline-block;margin:5px 0 0;padding:5px 10px;background:#00C73C;color:#fff;font-weight:normal;text-decoration:none}
|
||||
a.nicepay_btn{display:inline-block;margin:5px 0 0;padding:5px 10px;background:#0057bf;color:#fff;font-weight:normal;text-decoration:none}
|
||||
|
||||
@@ -740,7 +739,6 @@ ul.de_pg_tab li.tab-current a{background:#2CC185;color:#fff}
|
||||
.lg_info_fld th{background-color:#FFF4FA}
|
||||
.lg_info_fld_v2 th{background-color:#ffe8f5}
|
||||
.inicis_info_fld th{background-color:#F6F1FF}
|
||||
.kakao_info_fld th{background-color:#FFFCED}
|
||||
.naver_info_fld th{background-color:#F3FFF3}
|
||||
.nicepay_info_fld th{background-color:#d1e6ff}
|
||||
|
||||
|
||||
+199
-632
@@ -1,646 +1,213 @@
|
||||
<?php
|
||||
$sub_menu = '100410';
|
||||
include_once('./_common.php');
|
||||
include_once(G5_LIB_PATH . '/migration.lib.php');
|
||||
include_once(G5_LIB_PATH . '/shop_install.lib.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, 'r');
|
||||
|
||||
if ($is_admin != 'super') {
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
}
|
||||
|
||||
$is_execute = isset($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
$migration_result = array('success' => true, 'applied' => 0, 'skipped' => 0, 'errors' => array());
|
||||
$shop_install_result = null;
|
||||
$action = '';
|
||||
|
||||
if ($is_execute) {
|
||||
check_demo();
|
||||
check_admin_token();
|
||||
$action = isset($_POST['action']) ? trim($_POST['action']) : 'migrate';
|
||||
if ($action === 'install_shop') {
|
||||
$shop_install_result = g5_shop_install_run();
|
||||
} elseif ($action === 'record_existing') {
|
||||
$migration_result = g5_migration_run('', true);
|
||||
} else {
|
||||
$migration_id = isset($_POST['migration_id']) ? trim($_POST['migration_id']) : '';
|
||||
$migration_result = g5_migration_run($migration_id);
|
||||
$migration_result = run_replace('admin_dbupgrade_result', $migration_result);
|
||||
}
|
||||
}
|
||||
|
||||
$migration_statuses = g5_migration_status();
|
||||
$pending_count = 0;
|
||||
$unrecorded_count = 0;
|
||||
foreach ($migration_statuses as $migration_status) {
|
||||
if (isset($migration_status['status']) && $migration_status['status'] === 'unrecorded') {
|
||||
$unrecorded_count++;
|
||||
} elseif (isset($migration_status['error']) || $migration_status['status'] !== 'success' || $migration_status['checksum_changed']) {
|
||||
$pending_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($shop_install_result !== null && $shop_install_result['success']) {
|
||||
alert('쇼핑몰 설치가 완료되었습니다.', G5_ADMIN_URL . '/dbupgrade.php');
|
||||
} elseif ($shop_install_result !== null) {
|
||||
$db_upgrade_msg = '쇼핑몰 설치에 실패했습니다. 오류 내용을 확인해 주십시오.';
|
||||
} elseif (!$is_execute) {
|
||||
$db_upgrade_msg = $pending_count ? '확인·실행이 필요한 DB 마이그레이션이 있습니다. 아래 내용을 확인해 주십시오.' : ($unrecorded_count ? '현재 실행할 변경은 없지만 기존 상태 확인 이력이 등록되지 않았습니다.' : 'DB 마이그레이션이 모두 적용되어 있습니다.');
|
||||
} elseif (!$migration_result['success']) {
|
||||
$db_upgrade_msg = 'DB 마이그레이션에 실패했습니다. 오류 내용을 확인해 주십시오.';
|
||||
} elseif ($action === 'record_existing') {
|
||||
$db_upgrade_msg = '기존 상태 확인 이력 ' . (int) $migration_result['skipped'] . '건을 등록했습니다. 선행 항목에 실행이 필요하거나 실패 이력이 있으면 등록을 중단합니다.';
|
||||
} elseif ($migration_result['applied'] || $migration_result['skipped']) {
|
||||
$db_upgrade_msg = 'DB 마이그레이션이 완료되었습니다.';
|
||||
} else {
|
||||
$db_upgrade_msg = '적용할 DB 마이그레이션이 없습니다.';
|
||||
}
|
||||
|
||||
$g5['title'] = 'DB 업그레이드';
|
||||
include_once('./admin.head.php');
|
||||
|
||||
$is_check = false;
|
||||
|
||||
//소셜 로그인 관련 필드 및 구글 리챕챠 필드 추가
|
||||
if(!isset($config['cf_social_login_use'])) {
|
||||
sql_query("ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_social_login_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_googl_shorturl_apikey`,
|
||||
ADD `cf_google_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_twitter_secret`,
|
||||
ADD `cf_google_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_clientid`,
|
||||
ADD `cf_naver_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_google_secret`,
|
||||
ADD `cf_naver_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_clientid`,
|
||||
ADD `cf_kakao_rest_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_naver_secret`,
|
||||
ADD `cf_social_servicelist` varchar(255) NOT NULL DEFAULT '' AFTER `cf_social_login_use`,
|
||||
ADD `cf_payco_clientid` varchar(100) NOT NULL DEFAULT '' AFTER `cf_social_servicelist`,
|
||||
ADD `cf_payco_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_payco_clientid`,
|
||||
ADD `cf_captcha` varchar(100) NOT NULL DEFAULT '' AFTER `cf_kakao_js_apikey`,
|
||||
ADD `cf_recaptcha_site_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_captcha`,
|
||||
ADD `cf_recaptcha_secret_key` varchar(100) NOT NULL DEFAULT '' AFTER `cf_recaptcha_site_key`
|
||||
", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
//소셜 로그인 관련 필드 카카오 클라이언트 시크릿 추가
|
||||
if(!isset($config['cf_kakao_client_secret'])) {
|
||||
sql_query("ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_kakao_client_secret` varchar(100) NOT NULL DEFAULT '' AFTER `cf_kakao_rest_key`
|
||||
", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 회원 이미지 관련 필드 추가
|
||||
if(!isset($config['cf_member_img_size'])) {
|
||||
sql_query("ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_member_img_size` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_icon_height`,
|
||||
ADD `cf_member_img_width` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_img_size`,
|
||||
ADD `cf_member_img_height` int(11) NOT NULL DEFAULT '0' AFTER `cf_member_img_width`
|
||||
", true);
|
||||
|
||||
$sql = " update {$g5['config_table']} set cf_member_img_size = 50000, cf_member_img_width = 60, cf_member_img_height = 60 ";
|
||||
sql_query($sql, false);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 소셜 로그인 관리 테이블 없을 경우 생성
|
||||
if( isset($g5['social_profile_table']) && !sql_query(" DESC {$g5['social_profile_table']} ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['social_profile_table']}` (
|
||||
`mp_no` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`provider` varchar(50) NOT NULL DEFAULT '',
|
||||
`object_sha` varchar(45) NOT NULL DEFAULT '',
|
||||
`identifier` varchar(255) NOT NULL DEFAULT '',
|
||||
`profileurl` varchar(255) NOT NULL DEFAULT '',
|
||||
`photourl` varchar(255) NOT NULL DEFAULT '',
|
||||
`displayname` varchar(150) NOT NULL DEFAULT '',
|
||||
`description` varchar(255) NOT NULL DEFAULT '',
|
||||
`mp_register_day` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`mp_latest_day` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
UNIQUE KEY `mp_no` (`mp_no`),
|
||||
KEY `mb_id` (`mb_id`),
|
||||
KEY `provider` (`provider`)
|
||||
) ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 게시판 짧은 주소
|
||||
$sql = " select bo_table from {$g5['board_table']} ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$write_table = $g5['write_prefix'] . $row['bo_table']; // 게시판 테이블 전체이름
|
||||
|
||||
$sql = " SHOW COLUMNS FROM {$write_table} LIKE 'wr_seo_title' ";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
if( !$row ){
|
||||
sql_query("ALTER TABLE `{$write_table}`
|
||||
ADD `wr_seo_title` varchar(200) NOT NULL DEFAULT '' AFTER `wr_content`,
|
||||
ADD INDEX `wr_seo_title` (`wr_seo_title`);
|
||||
", false);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 내용 관리 짧은 주소
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['content_table']}` LIKE 'co_seo_title' ";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
if( !$row ){
|
||||
sql_query("ALTER TABLE `{$g5['content_table']}`
|
||||
ADD `co_seo_title` varchar(200) NOT NULL DEFAULT '' AFTER `co_content`,
|
||||
ADD INDEX `co_seo_title` (`co_seo_title`);
|
||||
", false);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
$sql = "select * from {$g5['content_table']} limit 100 ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
|
||||
if( ! $row['co_seo_title']){
|
||||
|
||||
$co_seo_title = exist_seo_title_recursive('content', generate_seo_title($row['co_subject']), $g5['content_table'], $row['co_id']);
|
||||
|
||||
$sql = " update {$g5['content_table']}
|
||||
set co_seo_title = '$co_seo_title'
|
||||
where co_id = '{$row['co_id']}' ";
|
||||
sql_query($sql);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 메모 테이블
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['memo_table']}` LIKE 'me_send_id' ";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
if( !$row ){
|
||||
sql_query("ALTER TABLE `{$g5['memo_table']}`
|
||||
ADD `me_send_id` INT(11) NOT NULL DEFAULT '0',
|
||||
ADD `me_type` ENUM('send','recv') NOT NULL DEFAULT 'recv',
|
||||
ADD `me_send_ip` VARCHAR(100) NOT NULL DEFAULT '',
|
||||
CHANGE COLUMN `me_id` `me_id` INT(11) NOT NULL AUTO_INCREMENT;
|
||||
", false);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 읽지 않은 메모 수 칼럼
|
||||
if(!isset($member['mb_memo_cnt'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_memo_cnt` int(11) NOT NULL DEFAULT '0' AFTER `mb_memo_call`", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 스크랩 읽은 수 추가
|
||||
if(!isset($member['mb_scrap_cnt'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_scrap_cnt` int(11) NOT NULL DEFAULT '0' AFTER `mb_memo_cnt`", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 짧은 URL 주소를 사용 여부 필드 추가
|
||||
if (!isset($config['cf_bbs_rewrite'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_bbs_rewrite` tinyint(4) NOT NULL DEFAULT '0' AFTER `cf_link_target` ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 파일테이블에 추가 칼럼
|
||||
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['board_file_table']}` LIKE 'bf_fileurl' ";
|
||||
$row = sql_fetch($sql);
|
||||
|
||||
if( !$row ) {
|
||||
sql_query(" ALTER TABLE `{$g5['board_file_table']}`
|
||||
ADD COLUMN `bf_fileurl` VARCHAR(255) NOT NULL DEFAULT '' AFTER `bf_content`,
|
||||
ADD COLUMN `bf_thumburl` VARCHAR(255) NOT NULL DEFAULT '' AFTER `bf_fileurl`,
|
||||
ADD COLUMN `bf_storage` VARCHAR(50) NOT NULL DEFAULT '' AFTER `bf_thumburl`", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
if (defined('G5_USE_SHOP') && G5_USE_SHOP) {
|
||||
// 임시저장 테이블이 없을 경우 생성
|
||||
if(!sql_query(" DESC {$g5['g5_shop_post_log_table']} ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_post_log_table']}` (
|
||||
`log_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`oid` bigint(20) unsigned NOT NULL,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`post_data` text NOT NULL,
|
||||
`ol_code` varchar(255) NOT NULL DEFAULT '',
|
||||
`ol_msg` text NOT NULL,
|
||||
`ol_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`ol_ip` varchar(25) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`log_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8; ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
$result = sql_query("describe `{$g5['g5_shop_post_log_table']}`");
|
||||
while ($row = sql_fetch_array($result)){
|
||||
if( isset($row['Field']) && $row['Field'] === 'ol_msg' && $row['Type'] === 'varchar(255)' ){
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_post_log_table']}` MODIFY ol_msg TEXT NOT NULL;", false);
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_post_log_table']}` DROP PRIMARY KEY;", false);
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_post_log_table']}` ADD `log_id` int(11) NOT NULL AUTO_INCREMENT, ADD PRIMARY KEY (`log_id`);", false);
|
||||
$is_check = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($default['de_id'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD COLUMN `de_id` INT(11) NOT NULL AUTO_INCREMENT FIRST,
|
||||
ADD PRIMARY KEY (`de_id`); ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
// auth.au_menu 컬럼 크기 조정
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['auth_table']}` LIKE 'au_menu' ";
|
||||
$row = sql_fetch($sql);
|
||||
if (
|
||||
stripos($row['Type'], 'varchar') !== false
|
||||
&& (int) preg_replace('/[^0-9]/', '', $row['Type']) < 50
|
||||
) {
|
||||
sql_query(" ALTER TABLE `{$g5['auth_table']}` CHANGE `au_menu` `au_menu` VARCHAR(50) NOT NULL; ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// qa config 테이블 auto id key 추가
|
||||
$row = sql_fetch("select * from `{$g5['qa_config_table']}` limit 1");
|
||||
if (!isset($row['qa_id'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['qa_config_table']}` ADD COLUMN `qa_id` INT(11) NOT NULL AUTO_INCREMENT FIRST,
|
||||
ADD PRIMARY KEY (`qa_id`); ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// config 기본 테이블 auto id key 추가
|
||||
if (!isset($config['cf_id'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_id` INT(11) NOT NULL AUTO_INCREMENT FIRST,
|
||||
ADD PRIMARY KEY (`cf_id`); ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// login 테이블 auto id key 추가
|
||||
$row = sql_fetch("select * from `{$g5['login_table']}` limit 1");
|
||||
if (!isset($row['lo_id'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['login_table']}`
|
||||
ADD COLUMN `lo_id` INT(11) NOT NULL AUTO_INCREMENT FIRST,
|
||||
DROP PRIMARY KEY,
|
||||
ADD PRIMARY KEY (`lo_id`),
|
||||
ADD UNIQUE KEY `lo_ip_unique` (`lo_ip`) ", true);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// visit 테이블 auto id key 로 변경
|
||||
$result = sql_query("describe `{$g5['visit_table']}`");
|
||||
while ($row = sql_fetch_array($result)){
|
||||
if (isset($row['Field']) && $row['Field'] === 'vi_id' && (isset($row['Default']) && $row['Default'] == 0)){
|
||||
sql_query("ALTER TABLE `{$g5['visit_table']}`
|
||||
CHANGE COLUMN `vi_id` `vi_id` INT(11) NOT NULL AUTO_INCREMENT;
|
||||
", false);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
// SMS5 테이블 G5_TABLE_PREFIX 적용
|
||||
if($g5['sms5_prefix'] != 'sms5_' && sql_num_rows(sql_query("show tables like 'sms5_config'")))
|
||||
{
|
||||
$tables = array('config','write','history','book','book_group','form','form_group');
|
||||
|
||||
foreach($tables as $name){
|
||||
$old_table = 'sms5_' . $name;
|
||||
$new_table = $g5['sms5_prefix'] . $name;
|
||||
|
||||
// 기존 테이블이 있고, G5_TABLE_PREFIX 적용 테이블이 없을 경우 → 테이블명 변경
|
||||
if(sql_num_rows(sql_query("SHOW TABLES LIKE '{$old_table}' "))){
|
||||
if(!sql_num_rows(sql_query("SHOW TABLES LIKE '{$new_table}' "))){
|
||||
sql_query("RENAME TABLE {$old_table} TO {$new_table}", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 광고성 정보 수신 동의 사용 필드 추가
|
||||
if (!isset($config['cf_use_promotion'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_use_promotion` tinyint(1) NOT NULL DEFAULT '0' AFTER `cf_privacy` ",
|
||||
true
|
||||
);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 광고성 정보 수신 동의 여부 필드 추가 + 메일 / SMS 수신 일자 추가
|
||||
if (!isset($member['mb_marketing_agree'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['member_table']}`
|
||||
ADD `mb_marketing_agree` tinyint(1) NOT NULL DEFAULT '0' AFTER `mb_scrap_cnt`,
|
||||
ADD `mb_marketing_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_marketing_agree`,
|
||||
ADD `mb_thirdparty_agree` tinyint(1) NOT NULL DEFAULT '0' AFTER `mb_marketing_date`,
|
||||
ADD `mb_thirdparty_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_thirdparty_agree`,
|
||||
ADD `mb_agree_log` TEXT NOT NULL AFTER `mb_thirdparty_date`,
|
||||
ADD `mb_mailling_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_mailling`,
|
||||
ADD `mb_sms_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `mb_sms` ",
|
||||
true
|
||||
);
|
||||
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// 쿠폰 로그 테이블에 UNIQUE 인덱스 추가 (쿠폰 이중사용 방지)
|
||||
if (defined('G5_USE_SHOP') && G5_USE_SHOP) {
|
||||
$result = sql_query("SHOW INDEX FROM `{$g5['g5_shop_coupon_log_table']}` WHERE Key_name = 'idx_coupon_use'", false);
|
||||
if (!$result || !sql_num_rows($result)) {
|
||||
// 기존에 동일 쿠폰이 중복 사용된 데이터가 있으면 UNIQUE 인덱스 생성 실패하므로 중복 데이터 정리
|
||||
$dup_sql = " SELECT cp_id, mb_id, MIN(cl_id) as keep_id
|
||||
FROM `{$g5['g5_shop_coupon_log_table']}`
|
||||
GROUP BY cp_id, mb_id
|
||||
HAVING COUNT(*) > 1 ";
|
||||
|
||||
$dup_result = sql_query($dup_sql, false);
|
||||
if ($dup_result && sql_num_rows($dup_result)) {
|
||||
while ($dup_row = sql_fetch_array($dup_result)) {
|
||||
|
||||
echo $dup_row['cp_id']." 의 동일 쿠폰이 중복 사용된 데이터가 있으므로 인덱스 생성이 불가합니다. <br>";
|
||||
|
||||
$sql = " DELETE FROM `{$g5['g5_shop_coupon_log_table']}`
|
||||
WHERE cp_id = '{$dup_row['cp_id']}'
|
||||
AND mb_id = '{$dup_row['mb_id']}'
|
||||
AND cl_id != '{$dup_row['keep_id']}' ";
|
||||
if ($is_admin === 'super') {
|
||||
echo "데이터베이스에서 검토후에 이 쿼리문을 실행해 주세요.<br>$sql<br>";
|
||||
}
|
||||
// sql_query($sql);
|
||||
}
|
||||
}
|
||||
|
||||
// MyISAM + utf8mb4 환경에서 키 길이 초과 방지: cp_id varchar(100), mb_id varchar(100)으로 조정
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_coupon_log_table']}` MODIFY `cp_id` varchar(100) NOT NULL DEFAULT '', MODIFY `mb_id` varchar(100) NOT NULL DEFAULT ''", false);
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_coupon_log_table']}` ADD UNIQUE KEY `idx_coupon_use` (`cp_id`, `mb_id`)", false);
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 자동 로그인 토큰 테이블 생성 (KVE-2026-0610: 추측 가능한 자동 로그인 쿠키 위조 방지)
|
||||
// 다중 디바이스 지원을 위해 회원당 여러 토큰을 별도 테이블로 관리
|
||||
if (!isset($g5['member_auto_login_table'])) {
|
||||
$g5['member_auto_login_table'] = G5_TABLE_PREFIX.'member_auto_login';
|
||||
}
|
||||
if (!sql_query(" DESC `{$g5['member_auto_login_table']}` ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['member_auto_login_table']}` (
|
||||
`al_id` int(11) NOT NULL auto_increment,
|
||||
`mb_id` varchar(20) NOT NULL default '',
|
||||
`al_token` varchar(64) NOT NULL default '',
|
||||
`al_user_agent` varchar(255) NOT NULL default '',
|
||||
`al_ip` varchar(45) NOT NULL default '',
|
||||
`al_created` datetime DEFAULT NULL,
|
||||
`al_last_used` datetime DEFAULT NULL,
|
||||
`al_expire` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`al_id`),
|
||||
UNIQUE KEY `al_token` (`al_token`),
|
||||
KEY `mb_id` (`mb_id`),
|
||||
KEY `al_expire` (`al_expire`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true);
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
// KG이니시스 결제 처리 현황 및 단계별 이력 테이블
|
||||
if (defined('G5_USE_SHOP') && G5_USE_SHOP) {
|
||||
$inicis_pro_config_columns = array(
|
||||
'de_inicis_pro_alert_use' => "ADD COLUMN `de_inicis_pro_alert_use` tinyint(4) NOT NULL DEFAULT '1'",
|
||||
'de_inicis_pro_reconcile_use' => "ADD COLUMN `de_inicis_pro_reconcile_use` tinyint(4) NOT NULL DEFAULT '0'",
|
||||
'de_inicis_pro_log_days' => "ADD COLUMN `de_inicis_pro_log_days` int(11) NOT NULL DEFAULT '365'",
|
||||
'de_inicis_pro_summary_days' => "ADD COLUMN `de_inicis_pro_summary_days` int(11) NOT NULL DEFAULT '1825'",
|
||||
'de_inicis_pro_monitor_at' => "ADD COLUMN `de_inicis_pro_monitor_at` datetime DEFAULT NULL",
|
||||
'de_inicis_pro_monitor_message' => "ADD COLUMN `de_inicis_pro_monitor_message` varchar(255) NOT NULL DEFAULT ''"
|
||||
);
|
||||
$inicis_pro_config_alter = array();
|
||||
foreach ($inicis_pro_config_columns as $column => $alter) {
|
||||
$column_result = sql_query(" SHOW COLUMNS FROM `{$g5['g5_shop_default_table']}` LIKE '$column' ", false);
|
||||
if (!$column_result || sql_num_rows($column_result) === 0)
|
||||
$inicis_pro_config_alter[] = $alter;
|
||||
}
|
||||
if (count($inicis_pro_config_alter)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}` ".implode(', ', $inicis_pro_config_alter), true);
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
if (!isset($g5['g5_shop_inicis_pay_table']))
|
||||
$g5['g5_shop_inicis_pay_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay';
|
||||
if (!isset($g5['g5_shop_inicis_pay_event_table']))
|
||||
$g5['g5_shop_inicis_pay_event_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay_event';
|
||||
|
||||
if (!sql_query(" DESC `{$g5['g5_shop_inicis_pay_table']}` ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_inicis_pay_table']}` (
|
||||
`ip_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`ip_oid` varchar(64) NOT NULL DEFAULT '',
|
||||
`ip_tid` varchar(80) NOT NULL DEFAULT '',
|
||||
`ip_auth_tid` varchar(80) NOT NULL DEFAULT '',
|
||||
`ip_mid` varchar(80) NOT NULL DEFAULT '',
|
||||
`ip_environment` varchar(10) NOT NULL DEFAULT '',
|
||||
`mb_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`ip_amount` int(11) NOT NULL DEFAULT '0',
|
||||
`ip_pay_type` varchar(20) NOT NULL DEFAULT '',
|
||||
`ip_easy_pay` varchar(20) NOT NULL DEFAULT '',
|
||||
`ip_device` varchar(10) NOT NULL DEFAULT '',
|
||||
`ip_order_type` varchar(10) NOT NULL DEFAULT '',
|
||||
`ip_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_result_code` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_result_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`ip_noti_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_noti_code` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_noti_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`ip_noti_failed_count` int(11) NOT NULL DEFAULT '0',
|
||||
`ip_noti_at` datetime DEFAULT NULL,
|
||||
`ip_cancel_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_cancel_code` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_cancel_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`ip_cancel_checked_at` datetime DEFAULT NULL,
|
||||
`ip_refund_required` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ip_vbank_due_at` datetime DEFAULT NULL,
|
||||
`ip_expired_at` datetime DEFAULT NULL,
|
||||
`ip_order_exists` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ip_approved_at` datetime DEFAULT NULL,
|
||||
`ip_ordered_at` datetime DEFAULT NULL,
|
||||
`ip_notified_at` datetime DEFAULT NULL,
|
||||
`ip_canceled_at` datetime DEFAULT NULL,
|
||||
`ip_created_at` datetime DEFAULT NULL,
|
||||
`ip_updated_at` datetime DEFAULT NULL,
|
||||
`ip_ip` varchar(45) NOT NULL DEFAULT '',
|
||||
`ip_event_count` int(11) NOT NULL DEFAULT '0',
|
||||
`ip_audit_error` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ip_alerted_at` datetime DEFAULT NULL,
|
||||
`ip_alert_key` varchar(64) NOT NULL DEFAULT '',
|
||||
`ip_pg_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_pg_amount` int(11) NOT NULL DEFAULT '0',
|
||||
`ip_pg_tid` varchar(80) NOT NULL DEFAULT '',
|
||||
`ip_pg_result_code` varchar(30) NOT NULL DEFAULT '',
|
||||
`ip_pg_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`ip_pg_checked_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`ip_id`),
|
||||
UNIQUE KEY `ip_oid` (`ip_oid`),
|
||||
KEY `ip_tid` (`ip_tid`),
|
||||
KEY `ip_auth_tid` (`ip_auth_tid`),
|
||||
KEY `ip_status` (`ip_status`),
|
||||
KEY `ip_noti_status` (`ip_noti_status`),
|
||||
KEY `ip_cancel_status` (`ip_cancel_status`),
|
||||
KEY `ip_refund_required` (`ip_refund_required`),
|
||||
KEY `ip_updated_at` (`ip_updated_at`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true);
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
if (sql_query(" DESC `{$g5['g5_shop_inicis_pay_table']}` ", false)) {
|
||||
$inicis_pay_columns = array(
|
||||
'ip_environment' => "ADD COLUMN `ip_environment` varchar(10) NOT NULL DEFAULT '' AFTER `ip_mid`",
|
||||
'ip_easy_pay' => "ADD COLUMN `ip_easy_pay` varchar(20) NOT NULL DEFAULT '' AFTER `ip_pay_type`",
|
||||
'ip_noti_status' => "ADD COLUMN `ip_noti_status` varchar(30) NOT NULL DEFAULT '' AFTER `ip_result_message`",
|
||||
'ip_noti_code' => "ADD COLUMN `ip_noti_code` varchar(30) NOT NULL DEFAULT '' AFTER `ip_noti_status`",
|
||||
'ip_noti_message' => "ADD COLUMN `ip_noti_message` varchar(255) NOT NULL DEFAULT '' AFTER `ip_noti_code`",
|
||||
'ip_noti_failed_count' => "ADD COLUMN `ip_noti_failed_count` int(11) NOT NULL DEFAULT '0' AFTER `ip_noti_message`",
|
||||
'ip_noti_at' => "ADD COLUMN `ip_noti_at` datetime DEFAULT NULL AFTER `ip_noti_failed_count`",
|
||||
'ip_cancel_status' => "ADD COLUMN `ip_cancel_status` varchar(30) NOT NULL DEFAULT '' AFTER `ip_noti_at`",
|
||||
'ip_cancel_code' => "ADD COLUMN `ip_cancel_code` varchar(30) NOT NULL DEFAULT '' AFTER `ip_cancel_status`",
|
||||
'ip_cancel_message' => "ADD COLUMN `ip_cancel_message` varchar(255) NOT NULL DEFAULT '' AFTER `ip_cancel_code`",
|
||||
'ip_cancel_checked_at' => "ADD COLUMN `ip_cancel_checked_at` datetime DEFAULT NULL AFTER `ip_cancel_message`",
|
||||
'ip_refund_required' => "ADD COLUMN `ip_refund_required` tinyint(4) NOT NULL DEFAULT '0' AFTER `ip_cancel_checked_at`",
|
||||
'ip_vbank_due_at' => "ADD COLUMN `ip_vbank_due_at` datetime DEFAULT NULL AFTER `ip_refund_required`",
|
||||
'ip_expired_at' => "ADD COLUMN `ip_expired_at` datetime DEFAULT NULL AFTER `ip_vbank_due_at`",
|
||||
'ip_audit_error' => "ADD COLUMN `ip_audit_error` tinyint(4) NOT NULL DEFAULT '0'",
|
||||
'ip_alerted_at' => "ADD COLUMN `ip_alerted_at` datetime DEFAULT NULL",
|
||||
'ip_alert_key' => "ADD COLUMN `ip_alert_key` varchar(64) NOT NULL DEFAULT ''",
|
||||
'ip_pg_status' => "ADD COLUMN `ip_pg_status` varchar(30) NOT NULL DEFAULT ''",
|
||||
'ip_pg_amount' => "ADD COLUMN `ip_pg_amount` int(11) NOT NULL DEFAULT '0'",
|
||||
'ip_pg_tid' => "ADD COLUMN `ip_pg_tid` varchar(80) NOT NULL DEFAULT ''",
|
||||
'ip_pg_result_code' => "ADD COLUMN `ip_pg_result_code` varchar(30) NOT NULL DEFAULT ''",
|
||||
'ip_pg_message' => "ADD COLUMN `ip_pg_message` varchar(255) NOT NULL DEFAULT ''",
|
||||
'ip_pg_checked_at' => "ADD COLUMN `ip_pg_checked_at` datetime DEFAULT NULL"
|
||||
);
|
||||
$inicis_pay_alter = array();
|
||||
foreach ($inicis_pay_columns as $column => $alter) {
|
||||
$column_result = sql_query(" SHOW COLUMNS FROM `{$g5['g5_shop_inicis_pay_table']}` LIKE '$column' ", false);
|
||||
if (!$column_result || sql_num_rows($column_result) === 0)
|
||||
$inicis_pay_alter[] = $alter;
|
||||
}
|
||||
if (count($inicis_pay_alter)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_inicis_pay_table']}` ".implode(', ', $inicis_pay_alter), true);
|
||||
$is_check = true;
|
||||
}
|
||||
|
||||
if (count($inicis_pay_alter)) {
|
||||
$environment = !empty($default['de_card_test']) ? 'test' : 'live';
|
||||
sql_query(" insert ignore into `{$g5['g5_shop_inicis_pay_table']}`
|
||||
(ip_oid, ip_environment, ip_status, ip_noti_status, ip_noti_code, ip_noti_message,
|
||||
ip_noti_failed_count, ip_noti_at, ip_created_at, ip_updated_at, ip_event_count)
|
||||
select e.ip_oid, '$environment', e.pe_status, e.pe_status, e.pe_code, e.pe_message,
|
||||
sum(if(e.pe_status = 'notification_failed', 1, 0)), max(e.pe_created_at),
|
||||
min(e.pe_created_at), max(e.pe_created_at), count(*)
|
||||
from `{$g5['g5_shop_inicis_pay_event_table']}` e
|
||||
left join `{$g5['g5_shop_inicis_pay_table']}` p on p.ip_oid = e.ip_oid
|
||||
where p.ip_id is null
|
||||
and e.pe_stage = 'notification'
|
||||
group by e.ip_oid ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}`
|
||||
set ip_environment = case
|
||||
when lower(ip_mid) in ('inipaytest','iniescrow0') then 'test'
|
||||
when ip_mid <> '' then 'live'
|
||||
else '$environment'
|
||||
end
|
||||
where ip_environment = '' ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
inner join (
|
||||
select ip_oid, max(pe_id) as pe_id,
|
||||
sum(if(pe_status = 'notification_failed', 1, 0)) as fail_count
|
||||
from `{$g5['g5_shop_inicis_pay_event_table']}`
|
||||
where pe_stage = 'notification'
|
||||
group by ip_oid
|
||||
) x on x.ip_oid = p.ip_oid
|
||||
inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.pe_id = x.pe_id
|
||||
set p.ip_noti_status = e.pe_status,
|
||||
p.ip_noti_code = e.pe_code,
|
||||
p.ip_noti_message = e.pe_message,
|
||||
p.ip_noti_failed_count = x.fail_count,
|
||||
p.ip_noti_at = e.pe_created_at ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
inner join (
|
||||
select ip_oid, max(pe_id) as pe_id
|
||||
from `{$g5['g5_shop_inicis_pay_event_table']}`
|
||||
where pe_stage = 'cancel'
|
||||
group by ip_oid
|
||||
) x on x.ip_oid = p.ip_oid
|
||||
inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.pe_id = x.pe_id
|
||||
set p.ip_cancel_status = e.pe_status,
|
||||
p.ip_cancel_code = e.pe_code,
|
||||
p.ip_cancel_message = e.pe_message,
|
||||
p.ip_cancel_checked_at = e.pe_created_at ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
inner join `{$g5['g5_shop_inicis_pay_event_table']}` e on e.ip_oid = p.ip_oid and e.pe_stage = 'request'
|
||||
set p.ip_easy_pay = case
|
||||
when e.pe_message like '삼성페이%' then 'SAMSUNGPAY'
|
||||
when e.pe_message like 'lpay%' then 'LPAY'
|
||||
when e.pe_message like 'inicis_kakaopay%' then 'KAKAOPAY'
|
||||
when e.pe_message like '간편결제%' then 'EASYPAY'
|
||||
else p.ip_easy_pay
|
||||
end
|
||||
where p.ip_easy_pay = '' ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}`
|
||||
set ip_noti_status = case
|
||||
when ip_status = 'paid' then 'paid'
|
||||
when ip_status = 'vbank_issued' then 'vbank_issued'
|
||||
when ip_status = 'notification_received' then 'notification_received'
|
||||
else ip_noti_status
|
||||
end,
|
||||
ip_cancel_status = case
|
||||
when ip_status in ('canceled','cancel_failed','partial_canceled','partial_cancel_failed') then ip_status
|
||||
else ip_cancel_status
|
||||
end ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
inner join {$g5['g5_shop_order_table']} o on o.od_id = p.ip_oid
|
||||
set p.ip_status = 'paid_after_cancel',
|
||||
p.ip_refund_required = '1',
|
||||
p.ip_noti_status = 'paid_after_cancel',
|
||||
p.ip_noti_message = '취소 주문에 가상계좌 입금 확인'
|
||||
where p.ip_pay_type = 'VBANK'
|
||||
and p.ip_status = 'paid'
|
||||
and o.od_status = '취소'
|
||||
and o.od_receipt_price > 0 ", false);
|
||||
$inicis_pro_migrated_at = G5_TIME_YMDHIS;
|
||||
$inicis_pro_migrated_message = '기존 결제·주문 이력 대조에서 취소 후 입금 확인';
|
||||
sql_query(" insert into `{$g5['g5_shop_inicis_pay_event_table']}`
|
||||
(ip_oid, ip_tid, pe_stage, pe_status, pe_code, pe_message, pe_source, pe_ip, pe_created_at)
|
||||
select p.ip_oid, p.ip_tid, 'reconcile', 'paid_after_cancel', '',
|
||||
'".sql_escape_string($inicis_pro_migrated_message)."', 'system', '', '$inicis_pro_migrated_at'
|
||||
from `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
left join `{$g5['g5_shop_inicis_pay_event_table']}` e
|
||||
on e.ip_oid = p.ip_oid and e.pe_status = 'paid_after_cancel'
|
||||
where p.ip_status = 'paid_after_cancel'
|
||||
and p.ip_refund_required = '1'
|
||||
and e.pe_id is null ", false);
|
||||
sql_query(" update `{$g5['g5_shop_inicis_pay_table']}` p
|
||||
inner join `{$g5['g5_shop_inicis_pay_event_table']}` e
|
||||
on e.ip_oid = p.ip_oid
|
||||
and e.pe_status = 'paid_after_cancel'
|
||||
and e.pe_message = '".sql_escape_string($inicis_pro_migrated_message)."'
|
||||
and e.pe_created_at = '$inicis_pro_migrated_at'
|
||||
set p.ip_event_count = p.ip_event_count + 1 ", false);
|
||||
}
|
||||
|
||||
$inicis_pay_indexes = array(
|
||||
'ip_noti_status' => "ADD KEY `ip_noti_status` (`ip_noti_status`)",
|
||||
'ip_cancel_status' => "ADD KEY `ip_cancel_status` (`ip_cancel_status`)",
|
||||
'ip_refund_required' => "ADD KEY `ip_refund_required` (`ip_refund_required`)"
|
||||
);
|
||||
foreach ($inicis_pay_indexes as $index_name => $index_sql) {
|
||||
$index_result = sql_query(" SHOW INDEX FROM `{$g5['g5_shop_inicis_pay_table']}` WHERE Key_name = '$index_name' ", false);
|
||||
if (!$index_result || sql_num_rows($index_result) === 0) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_inicis_pay_table']}` $index_sql ", true);
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sql_query(" DESC `{$g5['g5_shop_inicis_pay_event_table']}` ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_inicis_pay_event_table']}` (
|
||||
`pe_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`ip_oid` varchar(64) NOT NULL DEFAULT '',
|
||||
`ip_tid` varchar(80) NOT NULL DEFAULT '',
|
||||
`pe_stage` varchar(30) NOT NULL DEFAULT '',
|
||||
`pe_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`pe_code` varchar(30) NOT NULL DEFAULT '',
|
||||
`pe_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`pe_source` varchar(10) NOT NULL DEFAULT '',
|
||||
`pe_ip` varchar(45) NOT NULL DEFAULT '',
|
||||
`pe_created_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`pe_id`),
|
||||
KEY `ip_oid` (`ip_oid`),
|
||||
KEY `ip_tid` (`ip_tid`),
|
||||
KEY `pe_status` (`pe_status`),
|
||||
KEY `pe_created_at` (`pe_created_at`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true);
|
||||
$is_check = true;
|
||||
}
|
||||
}
|
||||
|
||||
$is_check = run_replace('admin_dbupgrade', $is_check);
|
||||
|
||||
$db_upgrade_msg = $is_check ? 'DB 업그레이드가 완료되었습니다.' : '더 이상 업그레이드 할 내용이 없습니다.<br>현재 DB 업그레이드가 완료된 상태입니다.';
|
||||
$dbupgrade_token = get_admin_token();
|
||||
?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>
|
||||
<?php echo $db_upgrade_msg; ?>
|
||||
</p>
|
||||
<p><?php echo $db_upgrade_msg; ?></p>
|
||||
<?php if ($unrecorded_count) { ?>
|
||||
<p>실행 불필요 <?php echo (int) $unrecorded_count; ?>건: 현재 실행 조건상 건너뛰는 항목입니다. 과거 실행 성공을 의미하지 않으며, 선행 작업 후에는 결과가 달라질 수 있습니다. 기존 상태 등록은 처음부터 연속해서 실행 불필요한 항목만 기록합니다.</p>
|
||||
<?php } ?>
|
||||
<p>대기 항목에는 데이터 보정 등 조회만으로 완료를 판단할 수 없는 작업도 포함됩니다. 조회 시 DB 변경이나 이력 등록은 수행하지 않습니다.</p>
|
||||
</div>
|
||||
|
||||
<?php if ($shop_install_result !== null && !$shop_install_result['success']) { ?>
|
||||
<div class="local_desc01 local_desc" style="color:#d00">
|
||||
<p><?php echo htmlspecialchars($shop_install_result['error'], ENT_QUOTES, 'UTF-8'); ?></p>
|
||||
</div>
|
||||
<?php } elseif ($migration_result['errors']) { ?>
|
||||
<div class="local_desc01 local_desc" style="color:#d00">
|
||||
<?php foreach ($migration_result['errors'] as $migration_error) { ?>
|
||||
<p><?php echo htmlspecialchars($migration_error, ENT_QUOTES, 'UTF-8'); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<div class="dbupgrade_all_actions">
|
||||
<?php if ($unrecorded_count) { ?>
|
||||
<form class="dbupgrade_existing_action" method="post" action="<?php echo G5_ADMIN_URL; ?>/dbupgrade.php">
|
||||
<input type="hidden" name="token" value="<?php echo $dbupgrade_token; ?>">
|
||||
<input type="hidden" name="action" value="record_existing">
|
||||
<button type="submit" class="btn_submit">기존 상태 확인 이력 등록</button>
|
||||
</form>
|
||||
<?php } ?>
|
||||
<?php if (!defined('G5_USE_SHOP') || !G5_USE_SHOP) { ?>
|
||||
<form method="post" action="<?php echo G5_ADMIN_URL; ?>/dbupgrade.php" onsubmit="return confirm('쇼핑몰을 설치하시겠습니까? 설치 전 데이터베이스와 data/dbconfig.php 백업을 권장합니다.');">
|
||||
<input type="hidden" name="token" value="<?php echo $dbupgrade_token; ?>">
|
||||
<input type="hidden" name="action" value="install_shop">
|
||||
<button type="submit" class="btn_submit">쇼핑몰 설치</button>
|
||||
</form>
|
||||
<?php } ?>
|
||||
<form method="post" action="<?php echo G5_ADMIN_URL; ?>/dbupgrade.php" onsubmit="return confirm('선택한 DB 마이그레이션을 실행하시겠습니까? 실행 전 데이터베이스 백업을 권장합니다.');">
|
||||
<input type="hidden" name="token" value="<?php echo $dbupgrade_token; ?>">
|
||||
<input type="hidden" name="action" value="migrate">
|
||||
<button type="submit" name="migration_id" value="" class="btn_submit">전체 마이그레이션 실행</button>
|
||||
</form>
|
||||
</div>
|
||||
<form method="post" action="<?php echo G5_ADMIN_URL; ?>/dbupgrade.php" onsubmit="return confirm('선택한 DB 마이그레이션을 실행하시겠습니까? 실행 전 데이터베이스 백업을 권장합니다.');">
|
||||
<input type="hidden" name="token" value="<?php echo $dbupgrade_token; ?>">
|
||||
<input type="hidden" name="action" value="migrate">
|
||||
<div class="tbl_head01 tbl_wrap">
|
||||
<table id="dbupgrade_migration_table">
|
||||
<caption>버전형 DB 마이그레이션 목록</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" aria-sort="none"><button type="button" class="dbupgrade_sort" data-column="0">마이그레이션 <span aria-hidden="true"></span></button></th>
|
||||
<th scope="col" class="dbupgrade_description" aria-sort="none"><button type="button" class="dbupgrade_sort" data-column="1">설명 <span aria-hidden="true"></span></button></th>
|
||||
<th scope="col" aria-sort="none"><button type="button" class="dbupgrade_sort" data-column="2">상태 <span aria-hidden="true"></span></button></th>
|
||||
<th scope="col" aria-sort="none"><button type="button" class="dbupgrade_sort" data-column="3">적용 시각 <span aria-hidden="true"></span></button></th>
|
||||
<th scope="col" aria-sort="none"><button type="button" class="dbupgrade_sort" data-column="4">실행 <span aria-hidden="true"></span></button></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php $previous_migrations_ready = true; ?>
|
||||
<?php foreach ($migration_statuses as $migration_status) { ?>
|
||||
<?php
|
||||
$current_migration_status = isset($migration_status['status']) ? $migration_status['status'] : 'error';
|
||||
$migration_succeeded = $current_migration_status === 'success' && !$migration_status['checksum_changed'];
|
||||
$migration_can_run = in_array($current_migration_status, array('pending', 'failed'), true) && $previous_migrations_ready;
|
||||
$migration_button_title = $current_migration_status === 'unrecorded' ? '기존 상태 확인 이력을 등록해 주십시오.' : ($current_migration_status === 'success' ? '이미 성공한 마이그레이션입니다.' : ($previous_migrations_ready ? '' : '선행 마이그레이션을 먼저 실행해야 합니다.'));
|
||||
?>
|
||||
<tr>
|
||||
<?php if (isset($migration_status['error'])) { ?>
|
||||
<td colspan="5"><?php echo htmlspecialchars($migration_status['error'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<?php } else { ?>
|
||||
<td><?php echo htmlspecialchars($migration_status['id'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<td class="dbupgrade_description"><?php echo htmlspecialchars($migration_status['description'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<td><?php echo g5_migration_status_label($migration_status['status'], $migration_status['checksum_changed']); ?></td>
|
||||
<td><?php echo htmlspecialchars($migration_status['applied_at'], ENT_QUOTES, 'UTF-8'); ?></td>
|
||||
<td>
|
||||
<button type="submit" name="migration_id" value="<?php echo htmlspecialchars($migration_status['id'], ENT_QUOTES, 'UTF-8'); ?>" class="btn_frmline"<?php echo $migration_can_run ? '' : ' disabled'; ?><?php echo $migration_button_title !== '' ? ' title="' . htmlspecialchars($migration_button_title, ENT_QUOTES, 'UTF-8') . '"' : ''; ?>>실행</button>
|
||||
</td>
|
||||
<?php } ?>
|
||||
</tr>
|
||||
<?php $previous_migrations_ready = $previous_migrations_ready && $migration_succeeded; ?>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.dbupgrade_all_actions {display:flex;width:100%;margin:0 0 20px;justify-content:flex-end;gap:5px}
|
||||
.dbupgrade_all_actions form {margin:0}
|
||||
.dbupgrade_all_actions .dbupgrade_existing_action {margin-right:auto}
|
||||
.dbupgrade_all_actions .btn_submit {display:inline-block;float:none;position:static;width:auto;height:30px;margin:0;padding:0 15px;border:0}
|
||||
.dbupgrade_sort {width:100%; border:0; background:transparent; color:inherit; font:inherit; cursor:pointer}
|
||||
#dbupgrade_migration_table td.dbupgrade_description {text-align:left}
|
||||
#dbupgrade_migration_table th.dbupgrade_description, #dbupgrade_migration_table th.dbupgrade_description .dbupgrade_sort {text-align:center}
|
||||
.btn_frmline:disabled {background:#b7b7b7;color:#fff;cursor:not-allowed}
|
||||
</style>
|
||||
<script>
|
||||
(function () {
|
||||
var table = document.getElementById('dbupgrade_migration_table');
|
||||
if (!table || !table.tBodies.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var buttons = table.querySelectorAll('.dbupgrade_sort');
|
||||
var tbody = table.tBodies[0];
|
||||
|
||||
function getCellText(row, column) {
|
||||
return row.cells[column] ? row.cells[column].textContent.replace(/^\s+|\s+$/g, '') : '';
|
||||
}
|
||||
|
||||
function sortRows(button, initialAscending) {
|
||||
var column = parseInt(button.getAttribute('data-column'), 10);
|
||||
var header = button.parentNode;
|
||||
var ascending = typeof initialAscending === 'boolean' ? initialAscending : header.getAttribute('aria-sort') !== 'ascending';
|
||||
var rows = Array.prototype.slice.call(tbody.rows);
|
||||
|
||||
rows.sort(function (left, right) {
|
||||
var leftText = getCellText(left, column);
|
||||
var rightText = getCellText(right, column);
|
||||
var compared = leftText.localeCompare(rightText, undefined, {numeric: true, sensitivity: 'base'});
|
||||
|
||||
return ascending ? compared : -compared;
|
||||
});
|
||||
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].parentNode.setAttribute('aria-sort', 'none');
|
||||
buttons[i].getElementsByTagName('span')[0].textContent = '';
|
||||
}
|
||||
header.setAttribute('aria-sort', ascending ? 'ascending' : 'descending');
|
||||
button.getElementsByTagName('span')[0].textContent = ascending ? '▲' : '▼';
|
||||
|
||||
for (var j = 0; j < rows.length; j++) {
|
||||
tbody.appendChild(rows[j]);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < buttons.length; i++) {
|
||||
buttons[i].onclick = function () {
|
||||
sortRows(this);
|
||||
};
|
||||
}
|
||||
|
||||
if (buttons.length) {
|
||||
sortRows(buttons[0], false);
|
||||
}
|
||||
}());
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once ('./admin.tail.php');
|
||||
include_once('./admin.tail.php');
|
||||
|
||||
@@ -26,14 +26,6 @@ if ($w == "u") {
|
||||
$g5['title'] = $html_title . ' 관리';
|
||||
|
||||
// 모바일 상하단 내용 필드추가
|
||||
if (!sql_query(" select fm_mobile_head_html from {$g5['faq_master_table']} limit 1 ", false)) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['faq_master_table']}`
|
||||
ADD `fm_mobile_head_html` text NOT NULL AFTER `fm_tail_html`,
|
||||
ADD `fm_mobile_tail_html` text NOT NULL AFTER `fm_mobile_head_html` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
require_once G5_ADMIN_PATH . '/admin.head.php';
|
||||
?>
|
||||
|
||||
@@ -42,9 +42,6 @@ $sql_common = " set fm_subject = '$fm_subject',
|
||||
fm_order = '$fm_order' ";
|
||||
|
||||
if ($w == "") {
|
||||
$sql = " alter table {$g5['faq_master_table']} auto_increment=1 ";
|
||||
sql_query($sql);
|
||||
|
||||
$sql = " insert {$g5['faq_master_table']} $sql_common ";
|
||||
sql_query($sql);
|
||||
|
||||
|
||||
@@ -10,45 +10,8 @@ if (!isset($g5['faq_table']) || !isset($g5['faq_master_table'])) {
|
||||
}
|
||||
|
||||
//자주하시는 질문 마스터 테이블이 있는지 검사한다.
|
||||
if (!sql_query(" DESCRIBE {$g5['faq_master_table']} ", false)) {
|
||||
if (sql_query(" DESCRIBE {$g5['g5_shop_faq_master_table']} ", false)) {
|
||||
sql_query(" ALTER TABLE {$g5['g5_shop_faq_master_table']} RENAME TO `{$g5['faq_master_table']}` ;", false);
|
||||
} else {
|
||||
$query_cp = sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['faq_master_table']}` (
|
||||
`fm_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`fm_subject` varchar(255) NOT NULL DEFAULT '',
|
||||
`fm_head_html` text NOT NULL,
|
||||
`fm_tail_html` text NOT NULL,
|
||||
`fm_order` int(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`fm_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ",
|
||||
true
|
||||
);
|
||||
}
|
||||
// FAQ Master
|
||||
sql_query(" insert into `{$g5['faq_master_table']}` set fm_id = '1', fm_subject = '자주하시는 질문' ", false);
|
||||
}
|
||||
|
||||
//자주하시는 질문 테이블이 있는지 검사한다.
|
||||
if (!sql_query(" DESCRIBE {$g5['faq_table']} ", false)) {
|
||||
if (sql_query(" DESCRIBE {$g5['g5_shop_faq_table']} ", false)) {
|
||||
sql_query(" ALTER TABLE {$g5['g5_shop_faq_table']} RENAME TO `{$g5['faq_table']}` ;", false);
|
||||
} else {
|
||||
$query_cp = sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['faq_table']}` (
|
||||
`fa_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`fm_id` int(11) NOT NULL DEFAULT '0',
|
||||
`fa_subject` text NOT NULL,
|
||||
`fa_content` text NOT NULL,
|
||||
`fa_order` int(11) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`fa_id`),
|
||||
KEY `fm_id` (`fm_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ",
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = 'FAQ관리';
|
||||
require_once G5_ADMIN_PATH . '/admin.head.php';
|
||||
|
||||
@@ -8,6 +8,8 @@ $html_title = '회원메일 발송';
|
||||
|
||||
check_demo();
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
check_admin_token();
|
||||
|
||||
require_once './admin.head.php';
|
||||
@@ -59,7 +61,7 @@ for ($i = 0; $i < count($member_list); $i++) {
|
||||
$content = preg_replace("/{회원아이디}/", $mb_id, (string)$content);
|
||||
$content = preg_replace("/{이메일}/", $to_email, (string)$content);
|
||||
|
||||
$content = $content . "<hr size=0><p><span style='font-size:9pt; font-family:굴림'>▶ 더 이상 정보 수신을 원치 않으시면 [<a href='" . G5_BBS_URL . "/email_stop.php?mb_id={$mb_id}&mb_md5={$mb_md5}' target='_blank'>수신거부</a>] 해 주십시오.</span></p>";
|
||||
$content = $content . "<hr size=0><p><span style='font-size:9pt; font-family:굴림'>▶ 더 이상 정보 수신을 원치 않으시면 [<a href='" . ($security_mail_url . '/' . G5_BBS_DIR) . "/email_stop.php?mb_id={$mb_id}&mb_md5={$mb_md5}' target='_blank'>수신거부</a>] 해 주십시오.</span></p>";
|
||||
|
||||
mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $to_email, $subject, $content, 1);
|
||||
|
||||
|
||||
+3
-1
@@ -12,6 +12,8 @@ auth_check_menu($auth, $sub_menu, 'w');
|
||||
|
||||
check_demo();
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
$g5['title'] = '회원메일 테스트';
|
||||
|
||||
$name = get_text($member['mb_name']);
|
||||
@@ -33,7 +35,7 @@ $content = preg_replace("/{이메일}/", $email, (string)$content);
|
||||
|
||||
$mb_md5 = md5($member['mb_id'] . $member['mb_email'] . $member['mb_datetime']);
|
||||
|
||||
$content = $content . '<p>더 이상 정보 수신을 원치 않으시면 [<a href="' . G5_BBS_URL . '/email_stop.php?mb_id=' . $mb_id . '&mb_md5=' . $mb_md5 . '" target="_blank">수신거부</a>] 해 주십시오.</p>';
|
||||
$content = $content . '<p>더 이상 정보 수신을 원치 않으시면 [<a href="' . ($security_mail_url . '/' . G5_BBS_DIR) . '/email_stop.php?mb_id=' . $mb_id . '&mb_md5=' . $mb_md5 . '" target="_blank">수신거부</a>] 해 주십시오.</p>';
|
||||
|
||||
mailer($config['cf_title'], $member['mb_email'], $member['mb_email'], $subject, $content, 1);
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
$sub_menu = '200100';
|
||||
require_once './_common.php';
|
||||
|
||||
check_demo();
|
||||
auth_check_menu($auth, $sub_menu, 'w');
|
||||
auth_check_menu($auth, $sub_menu, 'd');
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
alert('올바른 방법으로 이용해 주십시오.');
|
||||
}
|
||||
|
||||
check_admin_token();
|
||||
admin_referer_check();
|
||||
|
||||
$mb_id = isset($_POST['mb_id']) && is_string($_POST['mb_id']) ? trim($_POST['mb_id']) : '';
|
||||
$mb = get_member($mb_id);
|
||||
if (empty($mb['mb_id'])) {
|
||||
alert('존재하지 않는 회원자료입니다.');
|
||||
}
|
||||
|
||||
if ($is_admin !== 'super' && ($mb['mb_level'] >= $member['mb_level'] || is_admin($mb['mb_id']) === 'super')) {
|
||||
alert('자신보다 권한이 높거나 같은 회원의 본인확인 정보는 삭제할 수 없습니다.');
|
||||
}
|
||||
|
||||
if (!admin_clear_member_certification($mb['mb_id'])) {
|
||||
alert('본인확인 정보를 모두 삭제하지 못했습니다. DB 상태를 확인한 뒤 다시 시도해 주십시오.');
|
||||
}
|
||||
|
||||
alert('본인확인 정보와 인증 내역을 삭제했습니다.', './member_form.php?w=u&mb_id='.urlencode($mb['mb_id']));
|
||||
+21
-41
@@ -150,62 +150,21 @@ $mb_marketing_agree_no = !$mb['mb_marketing_agree'] ? 'checked="checked"' :
|
||||
$mb_thirdparty_agree_yes = $mb['mb_thirdparty_agree'] ? 'checked="checked"' : '';
|
||||
$mb_thirdparty_agree_no = !$mb['mb_thirdparty_agree'] ? 'checked="checked"' : '';
|
||||
|
||||
if (isset($mb['mb_certify'])) {
|
||||
// 날짜시간형이라면 drop 시킴
|
||||
if (preg_match("/-/", $mb['mb_certify'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}` DROP `mb_certify` ", false);
|
||||
}
|
||||
} else {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}` ADD `mb_certify` TINYINT(4) NOT NULL DEFAULT '0' AFTER `mb_hp` ", false);
|
||||
}
|
||||
|
||||
if (isset($mb['mb_adult'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}` CHANGE `mb_adult` `mb_adult` TINYINT(4) NOT NULL DEFAULT '0' ", false);
|
||||
} else {
|
||||
sql_query(" ALTER TABLE `{$g5['member_table']}` ADD `mb_adult` TINYINT NOT NULL DEFAULT '0' AFTER `mb_certify` ", false);
|
||||
}
|
||||
|
||||
// 지번주소 필드추가
|
||||
if (!isset($mb['mb_addr_jibeon'])) {
|
||||
sql_query(" ALTER TABLE {$g5['member_table']} ADD `mb_addr_jibeon` varchar(255) NOT NULL DEFAULT '' AFTER `mb_addr2` ", false);
|
||||
}
|
||||
|
||||
// 건물명필드추가
|
||||
if (!isset($mb['mb_addr3'])) {
|
||||
sql_query(" ALTER TABLE {$g5['member_table']} ADD `mb_addr3` varchar(255) NOT NULL DEFAULT '' AFTER `mb_addr2` ", false);
|
||||
}
|
||||
|
||||
// 중복가입 확인필드 추가
|
||||
if (!isset($mb['mb_dupinfo'])) {
|
||||
sql_query(" ALTER TABLE {$g5['member_table']} ADD `mb_dupinfo` varchar(255) NOT NULL DEFAULT '' AFTER `mb_adult` ", false);
|
||||
}
|
||||
|
||||
// 이메일인증 체크 필드추가
|
||||
if (!isset($mb['mb_email_certify2'])) {
|
||||
sql_query(" ALTER TABLE {$g5['member_table']} ADD `mb_email_certify2` varchar(255) NOT NULL DEFAULT '' AFTER `mb_email_certify` ", false);
|
||||
}
|
||||
|
||||
// 본인인증 내역 테이블 정보가 dbconfig에 없으면 소셜 테이블 정의
|
||||
if (!isset($g5['member_cert_history'])) {
|
||||
$g5['member_cert_history_table'] = G5_TABLE_PREFIX . 'member_cert_history';
|
||||
}
|
||||
// 멤버 본인인증 정보 변경 내역 테이블 없을 경우 생성
|
||||
if (isset($g5['member_cert_history_table']) && !sql_query(" DESC {$g5['member_cert_history_table']} ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['member_cert_history_table']}` (
|
||||
`ch_id` int(11) NOT NULL auto_increment,
|
||||
`mb_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`ch_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_hp` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_birth` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_type` varchar(20) NOT NULL DEFAULT '',
|
||||
`ch_datetime` datetime NOT NULL default '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`ch_id`),
|
||||
KEY `mb_id` (`mb_id`)
|
||||
) ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$mb_cert_history = '';
|
||||
if (isset($mb_id) && $mb_id) {
|
||||
@@ -479,6 +438,20 @@ add_javascript(G5_POSTCODE_JS, 0); //다음 주소 js
|
||||
<?php if ($cnt == 0) { ?>
|
||||
본인인증 내역이 없습니다.
|
||||
<?php } ?>
|
||||
<?php
|
||||
$has_certification_data = $cnt > 0 || !empty($mb['mb_dupinfo']) || !empty($mb['mb_certify'])
|
||||
|| !empty($mb['mb_adult']) || !empty($mb['mb_birth']) || !empty($mb['mb_sex']);
|
||||
if ($w === 'u' && !$has_certification_data) {
|
||||
$cert_request = sql_fetch("select count(*) as cnt from {$g5['cert_history_table']} where mb_id = '".sql_real_escape_string($mb['mb_id'])."'");
|
||||
$has_certification_data = !empty($cert_request['cnt']);
|
||||
}
|
||||
$can_cleanup_certification = $w === 'u' && $has_certification_data
|
||||
&& ($is_admin === 'super' || (isset($auth[$sub_menu]) && strpos($auth[$sub_menu], 'd') !== false));
|
||||
?>
|
||||
<?php if ($can_cleanup_certification) { ?>
|
||||
<p>본인확인 ‘아니오’는 인증 상태만 변경합니다. 정보 삭제는 CI/DI 관련 식별값, 본인확인·성인인증 상태, 생년월일·성별 및 본인인증 내역을 삭제합니다. 회원 이름과 연락처는 유지합니다.</p>
|
||||
<button type="submit" form="fmembercertcleanup" class="btn btn_02">본인확인 정보 삭제</button>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -639,6 +612,13 @@ add_javascript(G5_POSTCODE_JS, 0); //다음 주소 js
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($can_cleanup_certification) { ?>
|
||||
<form id="fmembercertcleanup" method="post" action="./member_cert_cleanup.php" onsubmit="return confirm('본인확인 정보와 인증 내역을 삭제하시겠습니까? 삭제 후 복구할 수 없으며, 회원정보의 다른 수정사항은 저장하지 않습니다.');">
|
||||
<input type="hidden" name="mb_id" value="<?php echo get_text($mb['mb_id']); ?>">
|
||||
<input type="hidden" name="token" value="">
|
||||
</form>
|
||||
<?php } ?>
|
||||
|
||||
<script>
|
||||
function fmember_submit(f) {
|
||||
if (!f.mb_icon.value.match(/\.(gif|jpe?g|png)$/i) && f.mb_icon.value) {
|
||||
|
||||
@@ -275,6 +275,8 @@ $colspan = 16;
|
||||
<?php
|
||||
if ($leave_msg || $intercept_msg) {
|
||||
echo $leave_msg . ' ' . $intercept_msg;
|
||||
} else if ($config['cf_use_email_certify'] && !preg_match('/[1-9]/', $row['mb_email_certify'])) {
|
||||
echo '<span class="txt_false">메일 미인증</span>';
|
||||
} else {
|
||||
echo "정상";
|
||||
}
|
||||
|
||||
@@ -11,22 +11,6 @@ if (!isset($g5['menu_table'])) {
|
||||
die('<meta charset="utf-8">dbconfig.php 파일에 <strong>$g5[\'menu_table\'] = G5_TABLE_PREFIX.\'menu\';</strong> 를 추가해 주세요.');
|
||||
}
|
||||
|
||||
if (!sql_query(" DESCRIBE {$g5['menu_table']} ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['menu_table']}` (
|
||||
`me_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`me_code` varchar(255) NOT NULL DEFAULT '',
|
||||
`me_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`me_link` varchar(255) NOT NULL DEFAULT '',
|
||||
`me_target` varchar(255) NOT NULL DEFAULT '0',
|
||||
`me_order` int(11) NOT NULL DEFAULT '0',
|
||||
`me_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`me_mobile_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`me_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$sql = " select * from {$g5['menu_table']} order by me_id ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
@@ -17,8 +17,6 @@ $nw = array(
|
||||
$html_title = "팝업레이어";
|
||||
|
||||
// 팝업레이어 테이블에 쇼핑몰, 커뮤니티 인지 구분하는 여부 필드 추가
|
||||
$sql = " ALTER TABLE `{$g5['new_win_table']}` ADD `nw_division` VARCHAR(10) NOT NULL DEFAULT 'both' ";
|
||||
sql_query($sql, false);
|
||||
|
||||
if ($w == "u") {
|
||||
$html_title .= " 수정";
|
||||
|
||||
@@ -8,31 +8,6 @@ if (!isset($g5['new_win_table'])) {
|
||||
die('<meta charset="utf-8">/data/dbconfig.php 파일에 <strong>$g5[\'new_win_table\'] = G5_TABLE_PREFIX.\'new_win\';</strong> 를 추가해 주세요.');
|
||||
}
|
||||
//내용(컨텐츠)정보 테이블이 있는지 검사한다.
|
||||
if (!sql_query(" DESCRIBE {$g5['new_win_table']} ", false)) {
|
||||
if (sql_query(" DESCRIBE {$g5['g5_shop_new_win_table']} ", false)) {
|
||||
sql_query(" ALTER TABLE {$g5['g5_shop_new_win_table']} RENAME TO `{$g5['new_win_table']}` ;", false);
|
||||
} else {
|
||||
$query_cp = sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['new_win_table']}` (
|
||||
`nw_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`nw_division` varchar(10) NOT NULL DEFAULT 'both',
|
||||
`nw_device` varchar(10) NOT NULL DEFAULT 'both',
|
||||
`nw_begin_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`nw_end_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`nw_disable_hours` int(11) NOT NULL DEFAULT '0',
|
||||
`nw_left` int(11) NOT NULL DEFAULT '0',
|
||||
`nw_top` int(11) NOT NULL DEFAULT '0',
|
||||
`nw_height` int(11) NOT NULL DEFAULT '0',
|
||||
`nw_width` int(11) NOT NULL DEFAULT '0',
|
||||
`nw_subject` text NOT NULL,
|
||||
`nw_content` text NOT NULL,
|
||||
`nw_content_html` tinyint(4) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`nw_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ",
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = '팝업레이어 관리';
|
||||
require_once G5_ADMIN_PATH . '/admin.head.php';
|
||||
|
||||
@@ -23,10 +23,6 @@ if ($w == '') {
|
||||
alert('w 값이 제대로 넘어오지 않았습니다.');
|
||||
}
|
||||
|
||||
if (!isset($po['po_use'])) {
|
||||
sql_query(" alter table `{$g5['poll_table']}` add `po_use` tinyint not null default '0' after `mb_ids` ", false);
|
||||
}
|
||||
|
||||
$g5['title'] = $html_title;
|
||||
require_once './admin.head.php';
|
||||
?>
|
||||
|
||||
@@ -8,92 +8,6 @@ auth_check_menu($auth, $sub_menu, 'r');
|
||||
$g5['title'] = '1:1문의 설정';
|
||||
require_once './admin.head.php';
|
||||
|
||||
// DB 테이블 생성
|
||||
if (!sql_query(" DESCRIBE `{$g5['qa_config_table']}` ", false)) {
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['qa_config_table']}` (
|
||||
`qa_id` int(11) NOT NULL auto_increment,
|
||||
`qa_title` varchar(255) NOT NULL DEFAULT'',
|
||||
`qa_category` varchar(255) NOT NULL DEFAULT'',
|
||||
`qa_skin` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_mobile_skin` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_use_email` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_req_email` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_use_hp` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_req_hp` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_use_sms` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_send_number` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_admin_hp` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_use_editor` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_subject_len` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_mobile_subject_len` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_page_rows` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_mobile_page_rows` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_image_width` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_upload_size` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_insert_content` text NOT NULL,
|
||||
`qa_include_head` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_include_tail` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_content_head` text NOT NULL,
|
||||
`qa_content_tail` text NOT NULL,
|
||||
`qa_mobile_content_head` text NOT NULL,
|
||||
`qa_mobile_content_tail` text NOT NULL,
|
||||
`qa_1_subj` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_2_subj` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_3_subj` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_4_subj` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_5_subj` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_1` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_2` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_3` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_4` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_5` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`qa_id`)
|
||||
)",
|
||||
true
|
||||
);
|
||||
sql_query(
|
||||
" CREATE TABLE IF NOT EXISTS `{$g5['qa_content_table']}` (
|
||||
`qa_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`qa_num` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_parent` int(11) NOT NULL DEFAULT '0',
|
||||
`qa_related` int(11) NOT NULL DEFAULT '0',
|
||||
`mb_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`qa_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_email` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_hp` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_type` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_category` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_email_recv` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_sms_recv` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_html` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_subject` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_content` text NOT NULL,
|
||||
`qa_status` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`qa_file1` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_source1` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_file2` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_source2` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_ip` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`qa_1` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_2` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_3` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_4` varchar(255) NOT NULL DEFAULT '',
|
||||
`qa_5` varchar(255) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`qa_id`),
|
||||
KEY `qa_num_parent` (`qa_num`,`qa_parent`)
|
||||
)",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$sql = " SHOW COLUMNS FROM `{$g5['qa_content_table']}` LIKE 'qa_content' ";
|
||||
$row = sql_fetch($sql);
|
||||
if (strpos($row['Type'], 'text') === false) {
|
||||
sql_query(" ALTER TABLE `{$g5['qa_content_table']}` CHANGE `qa_content` `qa_content` text NOT NULL ", true);
|
||||
}
|
||||
|
||||
$qaconfig = get_qa_config();
|
||||
|
||||
if (empty($qaconfig)) {
|
||||
@@ -106,28 +20,6 @@ if (empty($qaconfig)) {
|
||||
$qaconfig = get_qa_config();
|
||||
}
|
||||
|
||||
// 관리자 이메일필드 추가
|
||||
if (!isset($qaconfig['qa_admin_email'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['qa_config_table']}`
|
||||
ADD `qa_admin_email` varchar(255) NOT NULL DEFAULT '' AFTER `qa_admin_hp` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
// 상단 하단 설정 필드 추가
|
||||
if (!isset($qaconfig['qa_include_head'])) {
|
||||
sql_query(
|
||||
" ALTER TABLE `{$g5['qa_config_table']}`
|
||||
ADD `qa_include_head` varchar(255) NOT NULL DEFAULT '' AFTER `qa_insert_content`,
|
||||
ADD `qa_include_tail` varchar(255) NOT NULL DEFAULT '' AFTER `qa_include_head`,
|
||||
ADD `qa_content_head` text NOT NULL AFTER `qa_include_tail`,
|
||||
ADD `qa_content_tail` text NOT NULL AFTER `qa_content_head`,
|
||||
ADD `qa_mobile_content_head` text NOT NULL AFTER `qa_content_tail`,
|
||||
ADD `qa_mobile_content_tail` text NOT NULL AFTER `qa_mobile_content_head` ",
|
||||
true
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<form name="fqaconfigform" id="fqaconfigform" method="post" onsubmit="return fqaconfigform_submit(this);" autocomplete="off">
|
||||
|
||||
@@ -88,7 +88,8 @@ function change_status($od_id, $current_status, $change_status)
|
||||
$sql = " update {$g5['g5_shop_order_table']} set od_status = '{$change_status}' where od_id = '{$od_id}' and od_status = '{$current_status}' ";
|
||||
sql_query($sql, true);
|
||||
|
||||
$sql = " update {$g5['g5_shop_cart_table']} set ct_status = '{$change_status}' where od_id = '{$od_id}' and ct_status = '{$current_status}' ";
|
||||
$complete_time_sql = get_cart_complete_time_sql($change_status);
|
||||
$sql = " update {$g5['g5_shop_cart_table']} set ct_complete_time = $complete_time_sql, ct_status = '{$change_status}' where od_id = '{$od_id}' and ct_status = '{$current_status}' ";
|
||||
sql_query($sql, true);
|
||||
}
|
||||
|
||||
@@ -170,7 +171,7 @@ function pg_setting_check($is_print=false){
|
||||
if( $default['de_pg_service'] === 'kcp' && $default['de_kcp_mid'] && $default['de_kcp_site_key'] ){
|
||||
$pg_msg = 'NHN KCP';
|
||||
} else if ( $default['de_pg_service'] === 'lg' && $config['cf_lg_mid'] && $config['cf_lg_mert_key'] ){
|
||||
$pg_msg = 'LG유플러스';
|
||||
$msg .= '<div class="admin_pg_notice od_test_caution">(주의!) 토스페이먼츠(구버전) 결제의 결제 설정이 현재 테스트결제로 되어 있습니다.<br>반드시 <a href="#lg_info_anchor">MERT KEY</a>를 <u>[테스트]키</u>로 설정한 후 테스트결제를 진행해야합니다.<br>쇼핑몰 운영 시에는 실결제로 전환하여 <u>[라이브]키</u>로 설정해 주시기 바랍니다.<br>아래 링크를 클릭하여 실결제로 설정하여 운영해 주세요.<br><a href="'.$pg_test_conf_link.'" class="pg_test_conf_link">'.$pg_test_conf_link.'</a></div>';
|
||||
} else if ( $default['de_pg_service'] === 'toss' && $config['cf_lg_mid'] && $config['cf_toss_client_key'] && $config['cf_toss_secret_key'] ){
|
||||
$msg .= '<div class="admin_pg_notice od_test_caution">(주의!) 토스페이먼츠 결제의 결제 설정이 현재 테스트결제로 되어 있습니다.<br>반드시 <a href="#lg_info_anchor">상점 API키</a>를 <u>[테스트]키</u>로 설정한 후 테스트결제를 진행해야합니다.<br>쇼핑몰 운영 시에는 실결제로 전환하여 <u>[라이브]키</u>로 설정해 주시기 바랍니다.<br>아래 링크를 클릭하여 실결제로 설정하여 운영해 주세요.<br><a href="'.$pg_test_conf_link.'" class="pg_test_conf_link">'.$pg_test_conf_link.'</a></div>';
|
||||
} else if ( $default['de_pg_service'] === 'inicis' && $default['de_inicis_mid'] && $default['de_inicis_sign_key'] ){
|
||||
|
||||
@@ -33,11 +33,6 @@ else
|
||||
}
|
||||
|
||||
// 접속기기 필드 추가
|
||||
if(!sql_query(" select bn_device from {$g5['g5_shop_banner_table']} limit 0, 1 ")) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_banner_table']}`
|
||||
ADD `bn_device` varchar(10) not null default '' AFTER `bn_url` ", true);
|
||||
sql_query(" update {$g5['g5_shop_banner_table']} set bn_device = 'pc' ", true);
|
||||
}
|
||||
|
||||
include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
@@ -171,4 +166,4 @@ include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
</form>
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -49,8 +49,6 @@ if ($w=="")
|
||||
{
|
||||
if (!$bn_bimg_name) alert('배너 이미지를 업로드 하세요.');
|
||||
|
||||
sql_query(" alter table {$g5['g5_shop_banner_table']} auto_increment=1 ");
|
||||
|
||||
$sql = " insert into {$g5['g5_shop_banner_table']}
|
||||
set bn_alt = '$bn_alt',
|
||||
bn_url = '$bn_url',
|
||||
|
||||
@@ -109,30 +109,12 @@ if ($w == 'u') $pg_anchor .= '<li><a href="#frm_etc">기타설정</a></li>';
|
||||
$pg_anchor .= '</ul>';
|
||||
|
||||
// 쿠폰 적용 불가 설정 필드 추가
|
||||
if(!sql_query(" select ca_nocoupon from {$g5['g5_shop_category_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_category_table']}`
|
||||
ADD `ca_nocoupon` tinyint(4) NOT NULL DEFAULT '0' AFTER `ca_adult_use` ", true);
|
||||
}
|
||||
|
||||
// 스킨 디렉토리 필드 추가
|
||||
if(!sql_query(" select ca_skin_dir from {$g5['g5_shop_category_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_category_table']}`
|
||||
ADD `ca_skin_dir` varchar(255) NOT NULL DEFAULT '' AFTER `ca_name`,
|
||||
ADD `ca_mobile_skin_dir` varchar(255) NOT NULL DEFAULT '' AFTER `ca_skin_dir` ", true);
|
||||
}
|
||||
|
||||
// 분류 출력순서 필드 추가
|
||||
if(!sql_query(" select ca_order from {$g5['g5_shop_category_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_category_table']}`
|
||||
ADD `ca_order` int(11) NOT NULL DEFAULT '0' AFTER `ca_name` ", true);
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_category_table']}` ADD INDEX(`ca_order`) ", true);
|
||||
}
|
||||
|
||||
// 모바일 상품 출력줄수 필드 추가
|
||||
if(!sql_query(" select ca_mobile_list_row from {$g5['g5_shop_category_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_category_table']}`
|
||||
ADD `ca_mobile_list_row` int(11) NOT NULL DEFAULT '0' AFTER `ca_mobile_list_mod` ", true);
|
||||
}
|
||||
|
||||
// 스킨 Path
|
||||
if(!$ca['ca_skin_dir'])
|
||||
@@ -636,4 +618,4 @@ jQuery(function($){
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
+30
-339
@@ -27,249 +27,19 @@ $pg_anchor = '<ul class="anchor">
|
||||
<li><a href="#anc_scf_sms">SMS설정</a></li>
|
||||
</ul>';
|
||||
|
||||
// 무이자 할부 사용설정 필드 추가
|
||||
if(!isset($default['de_card_noint_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_card_noint_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_card_use` ", true);
|
||||
}
|
||||
|
||||
// 모바일 관련상품 설정 필드추가
|
||||
if(!isset($default['de_mobile_rel_list_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_mobile_rel_list_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_rel_img_height`,
|
||||
ADD `de_mobile_rel_list_skin` varchar(255) NOT NULL DEFAULT '' AFTER `de_mobile_rel_list_use`,
|
||||
ADD `de_mobile_rel_img_width` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_rel_list_skin`,
|
||||
ADD `de_mobile_rel_img_height` int(11) NOT NULL DEFAULT ' 0' AFTER `de_mobile_rel_img_width`", true);
|
||||
}
|
||||
|
||||
// 신규회원 쿠폰 설정 필드 추가
|
||||
if(!isset($default['de_member_reg_coupon_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_member_reg_coupon_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_tax_flag_use`,
|
||||
ADD `de_member_reg_coupon_term` int(11) NOT NULL DEFAULT '0' AFTER `de_member_reg_coupon_use`,
|
||||
ADD `de_member_reg_coupon_price` int(11) NOT NULL DEFAULT '0' AFTER `de_member_reg_coupon_term` ", true);
|
||||
}
|
||||
|
||||
// 신규회원 쿠폰 주문 최소금액 필드추가
|
||||
if(!isset($default['de_member_reg_coupon_minimum'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_member_reg_coupon_minimum` int(11) NOT NULL DEFAULT '0' AFTER `de_member_reg_coupon_price` ", true);
|
||||
}
|
||||
|
||||
// lg 결제관련 필드 추가
|
||||
if(!isset($default['de_pg_service'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_pg_service` varchar(255) NOT NULL DEFAULT '' AFTER `de_sms_hp` ", true);
|
||||
}
|
||||
|
||||
|
||||
// inicis 필드 추가
|
||||
if(!isset($default['de_inicis_mid'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_inicis_mid` varchar(255) NOT NULL DEFAULT '' AFTER `de_kcp_site_key` ", true);
|
||||
}
|
||||
|
||||
// 모바일 초기화면 이미지 줄 수 필드 추가
|
||||
if(!isset($default['de_mobile_type1_list_row'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_mobile_type1_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_type1_list_mod`,
|
||||
ADD `de_mobile_type2_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_type2_list_mod`,
|
||||
ADD `de_mobile_type3_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_type3_list_mod`,
|
||||
ADD `de_mobile_type4_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_type4_list_mod`,
|
||||
ADD `de_mobile_type5_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_type5_list_mod` ", true);
|
||||
}
|
||||
|
||||
// 모바일 관련상품 이미지 줄 수 필드 추가
|
||||
if(!isset($default['de_mobile_rel_list_mod'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_mobile_rel_list_mod` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_rel_list_skin` ", true);
|
||||
}
|
||||
|
||||
// 모바일 검색상품 이미지 줄 수 필드 추가
|
||||
if(!isset($default['de_mobile_search_list_row'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_mobile_search_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_search_list_mod` ", true);
|
||||
}
|
||||
|
||||
// PG 간펼결제 사용여부 필드 추가
|
||||
if(!isset($default['de_easy_pay_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_easy_pay_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_iche_use` ", true);
|
||||
}
|
||||
|
||||
// 이니시스 삼성페이 사용여부 필드 추가
|
||||
if(!isset($default['de_samsung_pay_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_samsung_pay_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_easy_pay_use` ", true);
|
||||
}
|
||||
|
||||
// 이니시스
|
||||
if(!isset($default['de_inicis_cartpoint_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_inicis_cartpoint_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_samsung_pay_use` ", true);
|
||||
}
|
||||
|
||||
// 이니시스 lpay 사용여부 필드 추가
|
||||
if(!isset($default['de_inicis_lpay_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_inicis_lpay_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_samsung_pay_use` ", true);
|
||||
}
|
||||
|
||||
// 이니시스 kakaopay 사용여부 필드 추가
|
||||
if(!isset($default['de_inicis_kakaopay_use'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_inicis_kakaopay_use` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_inicis_lpay_use` ", true);
|
||||
}
|
||||
|
||||
// 카카오페이 필드 추가
|
||||
if(!isset($default['de_kakaopay_mid'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_kakaopay_mid` varchar(255) NOT NULL DEFAULT '' AFTER `de_tax_flag_use`,
|
||||
ADD `de_kakaopay_key` varchar(255) NOT NULL DEFAULT '' AFTER `de_kakaopay_mid`,
|
||||
ADD `de_kakaopay_enckey` varchar(255) NOT NULL DEFAULT '' AFTER `de_kakaopay_key`,
|
||||
ADD `de_kakaopay_hashkey` varchar(255) NOT NULL DEFAULT '' AFTER `de_kakaopay_enckey`,
|
||||
ADD `de_kakaopay_cancelpwd` varchar(255) NOT NULL DEFAULT '' AFTER `de_kakaopay_hashkey` ", true);
|
||||
}
|
||||
|
||||
// 이니시스 웹결제 사인키 필드 추가
|
||||
if(!isset($default['de_inicis_sign_key'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_inicis_sign_key` varchar(255) NOT NULL DEFAULT '' ", true);
|
||||
}
|
||||
|
||||
// 네이버페이 필드추가
|
||||
if(!isset($default['de_naverpay_mid'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_naverpay_mid` varchar(255) NOT NULL DEFAULT '' AFTER `de_kakaopay_cancelpwd`,
|
||||
ADD `de_naverpay_cert_key` varchar(255) NOT NULL DEFAULT '' AFTER `de_naverpay_mid`,
|
||||
ADD `de_naverpay_button_key` varchar(255) NOT NULL DEFAULT '' AFTER `de_naverpay_cert_key`,
|
||||
ADD `de_naverpay_test` tinyint(4) NOT NULL DEFAULT '0' AFTER `de_naverpay_button_key`,
|
||||
ADD `de_naverpay_mb_id` varchar(255) NOT NULL DEFAULT '' AFTER `de_naverpay_test`,
|
||||
ADD `de_naverpay_sendcost` varchar(255) NOT NULL DEFAULT '' AFTER `de_naverpay_mb_id`", true);
|
||||
}
|
||||
|
||||
// 유형별상품리스트 설정필드 추가
|
||||
if(!isset($default['de_listtype_list_skin'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_listtype_list_skin` varchar(255) NOT NULL DEFAULT '' AFTER `de_mobile_search_img_height`,
|
||||
ADD `de_listtype_list_mod` int(11) NOT NULL DEFAULT '0' AFTER `de_listtype_list_skin`,
|
||||
ADD `de_listtype_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_listtype_list_mod`,
|
||||
ADD `de_listtype_img_width` int(11) NOT NULL DEFAULT '0' AFTER `de_listtype_list_row`,
|
||||
ADD `de_listtype_img_height` int(11) NOT NULL DEFAULT '0' AFTER `de_listtype_img_width`,
|
||||
ADD `de_mobile_listtype_list_skin` varchar(255) NOT NULL DEFAULT '' AFTER `de_listtype_img_height`,
|
||||
ADD `de_mobile_listtype_list_mod` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_listtype_list_skin`,
|
||||
ADD `de_mobile_listtype_list_row` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_listtype_list_mod`,
|
||||
ADD `de_mobile_listtype_img_width` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_listtype_list_row`,
|
||||
ADD `de_mobile_listtype_img_height` int(11) NOT NULL DEFAULT '0' AFTER `de_mobile_listtype_img_width` ", true);
|
||||
}
|
||||
|
||||
// 임시저장 테이블이 없을 경우 생성
|
||||
if(!sql_query(" DESC {$g5['g5_shop_post_log_table']} ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_post_log_table']}` (
|
||||
`log_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`oid` bigint(20) unsigned NOT NULL,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`post_data` text NOT NULL,
|
||||
`ol_code` varchar(255) NOT NULL DEFAULT '',
|
||||
`ol_msg` text NOT NULL,
|
||||
`ol_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`ol_ip` varchar(25) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`log_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8; ", false);
|
||||
}
|
||||
|
||||
|
||||
// 현금영수증 발급 조건 추가
|
||||
if(!isset($default['de_taxsave_types'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD `de_taxsave_types` set('account','vbank','transfer') NOT NULL DEFAULT 'account' AFTER `de_taxsave_use` ", true);
|
||||
}
|
||||
|
||||
// 아이코드 토큰키 추가
|
||||
if( ! isset($config['cf_icode_token_key']) ){
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_icode_token_key` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_icode_server_port`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// PG 간편결제 추가 ( NHN_KCP 네이버페이, 카카오페이 )
|
||||
if( ! isset($default['de_easy_pay_services']) ){
|
||||
$sql = "ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD COLUMN `de_easy_pay_services` VARCHAR(255) NOT NULL DEFAULT '' AFTER `de_easy_pay_use`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// KG 이니시스 iniapi_key 추가
|
||||
if( ! isset($default['de_inicis_iniapi_key']) ){
|
||||
$sql = "ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD COLUMN `de_inicis_iniapi_key` VARCHAR(30) NOT NULL DEFAULT '' AFTER `de_inicis_sign_key`,
|
||||
ADD COLUMN `de_inicis_iniapi_iv` VARCHAR(30) NOT NULL DEFAULT '' AFTER `de_inicis_iniapi_key`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// KG이니시스 INIpay PRO 설정 추가
|
||||
if (!isset($default['de_inicis_pro_use'])) {
|
||||
$sql = "ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD COLUMN `de_inicis_pro_use` TINYINT(4) NOT NULL DEFAULT '0' AFTER `de_inicis_sign_key`,
|
||||
ADD COLUMN `de_inicis_hash_key` VARCHAR(255) NOT NULL DEFAULT '' AFTER `de_inicis_pro_use`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
if (!isset($default['de_inicis_pro_alert_use'])) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_alert_use` TINYINT(4) NOT NULL DEFAULT '1'", false);
|
||||
$default['de_inicis_pro_alert_use'] = 1;
|
||||
}
|
||||
if (!isset($default['de_inicis_pro_reconcile_use'])) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_reconcile_use` TINYINT(4) NOT NULL DEFAULT '0'", false);
|
||||
$default['de_inicis_pro_reconcile_use'] = 0;
|
||||
}
|
||||
if (!isset($default['de_inicis_pro_log_days'])) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_log_days` INT(11) NOT NULL DEFAULT '365'", false);
|
||||
$default['de_inicis_pro_log_days'] = 365;
|
||||
}
|
||||
if (!isset($default['de_inicis_pro_summary_days'])) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_summary_days` INT(11) NOT NULL DEFAULT '1825'", false);
|
||||
$default['de_inicis_pro_summary_days'] = 1825;
|
||||
}
|
||||
if (!array_key_exists('de_inicis_pro_monitor_at', $default)) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_monitor_at` DATETIME DEFAULT NULL", false);
|
||||
$default['de_inicis_pro_monitor_at'] = '';
|
||||
}
|
||||
if (!isset($default['de_inicis_pro_monitor_message'])) {
|
||||
sql_query("ALTER TABLE `{$g5['g5_shop_default_table']}` ADD COLUMN `de_inicis_pro_monitor_message` VARCHAR(255) NOT NULL DEFAULT ''", false);
|
||||
$default['de_inicis_pro_monitor_message'] = '';
|
||||
}
|
||||
|
||||
// KG이니시스 INIpay PRO 결제 감사 테이블이 없으면 생성한다.
|
||||
include_once(G5_SHOP_PATH.'/inicis/pro/inicis_pro.lib.php');
|
||||
if (function_exists('inicis_pro_audit_ensure_tables'))
|
||||
inicis_pro_audit_ensure_tables();
|
||||
|
||||
// NICEPAY mid, key 추가
|
||||
if (! isset($default['de_nicepay_mid'])) {
|
||||
$sql = "ALTER TABLE `{$g5['g5_shop_default_table']}`
|
||||
ADD COLUMN `de_nicepay_mid` VARCHAR(20) NOT NULL DEFAULT '' AFTER `de_inicis_cartpoint_use`,
|
||||
ADD COLUMN `de_nicepay_key` VARCHAR(150) NOT NULL DEFAULT '' AFTER `de_nicepay_mid`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
// 토스페이먼츠 client, secret key 추가
|
||||
if( ! isset($config['cf_toss_client_key']) ){
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_toss_client_key` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_lg_mert_key`,
|
||||
ADD COLUMN `cf_toss_secret_key` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_toss_client_key`; ";
|
||||
sql_query($sql, false);
|
||||
}
|
||||
|
||||
if( function_exists('pg_setting_check') ){
|
||||
pg_setting_check(true);
|
||||
}
|
||||
|
||||
if(!$default['de_kakaopay_cancelpwd']){
|
||||
$default['de_kakaopay_cancelpwd'] = '1111';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php if (!empty($default['de_kakaopay_mid']) || !empty($default['de_kakaopay_enckey'])) { ?>
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>5.6.37부터 SIRK 전용 카카오페이 연동을 지원하지 않습니다. 기존 거래는 이니시스 상점관리자에서 승인·취소 내역을 확인해 주십시오.</p>
|
||||
<p>기존 이용 고객 지원: 김민섭 <a href="mailto:minsup@sir.kr">minsup@sir.kr</a></p>
|
||||
</div>
|
||||
<?php } ?>
|
||||
|
||||
<form name="fconfig" action="./configformupdate.php" onsubmit="return fconfig_check(this)" method="post" enctype="MULTIPART/FORM-DATA">
|
||||
<input type="hidden" name="token" value="">
|
||||
<section id="anc_scf_info">
|
||||
@@ -732,7 +502,7 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<tr>
|
||||
<th scope="row"><label for="de_easy_pay_use">PG사 간편결제 버튼 사용</label></th>
|
||||
<td>
|
||||
<?php echo help("주문서 작성 페이지에 PG사 간편결제(PAYCO, 토스, KPAY...) 버튼의 별도 사용 여부를 설정합니다.", 50); ?>
|
||||
<?php echo help("주문서에 선택한 PG사의 사용 설정된 간편결제 수단을 개별 표시합니다. 아래 PG별 항목에서 계약된 수단을 선택하세요.", 50); ?>
|
||||
<select id="de_easy_pay_use" name="de_easy_pay_use">
|
||||
<option value="0" <?php echo get_selected($default['de_easy_pay_use'], 0); ?>>노출안함</option>
|
||||
<option value="1" <?php echo get_selected($default['de_easy_pay_use'], 1); ?>>노출함</option>
|
||||
@@ -817,8 +587,8 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<tr>
|
||||
<th scope="row"><label for="de_point_days">주문완료 포인트</label></th>
|
||||
<td>
|
||||
<?php echo help("주문자가 회원일 경우에만 주문완료시 포인트를 지급합니다. 주문취소, 반품 등을 고려하여 포인트를 지급할 적당한 기간을 입력하십시오. (기본값은 7일)\n0일로 설정하는 경우에는 주문완료와 동시에 포인트를 지급합니다."); ?>
|
||||
주문 완료 <input type="text" name="de_point_days" value="<?php echo get_sanitize_input($default['de_point_days']); ?>" id="de_point_days" class="frm_input" size="2"> 일 이후에 포인트를 지급
|
||||
<?php echo help("주문자가 회원일 경우에만 상품별 배송완료 시각부터 설정한 기간이 지난 후 포인트를 지급합니다. 주문취소, 반품 등을 고려하여 포인트를 지급할 적당한 기간을 입력하십시오. (기본값은 7일)\n0일로 설정하면 배송완료 처리 시 지급합니다. 그 외에는 기간 경과 후 관리자 주문 상세 조회 또는 완료 처리 시 지급합니다."); ?>
|
||||
배송 완료 <input type="text" name="de_point_days" value="<?php echo get_sanitize_input($default['de_point_days']); ?>" id="de_point_days" class="frm_input" size="2"> 일 이후에 포인트를 지급
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -856,16 +626,16 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<th scope="row"><label for="de_kcp_easy_pays">NHN KCP 간편결제</label></th>
|
||||
<td>
|
||||
<?php echo help("체크시 NHN KCP 간편결제들을 활성화 합니다.\nNHN_KCP > 네이버페이, 카카오페이는 테스트결제가 되지 않습니다.\n애플페이는 IOS 기기에 모바일결제만 가능합니다."); ?>
|
||||
<input type="checkbox" id="de_easy_nhnkcp_payco" name="de_easy_pays[]" value="nhnkcp_payco" <?php if(stripos($default['de_easy_pay_services'], 'nhnkcp_payco') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nhnkcp_payco" disabled>PAYCO (페이코)</label><br>
|
||||
<input type="checkbox" id="de_easy_nhnkcp_naverpay" name="de_easy_pays[]" value="nhnkcp_naverpay" <?php if(stripos($default['de_easy_pay_services'], 'nhnkcp_naverpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nhnkcp_naverpay">NAVERPAY (네이버페이)</label><br>
|
||||
<input type="checkbox" id="de_easy_nhnkcp_kakaopay" name="de_easy_pays[]" value="nhnkcp_kakaopay" <?php if(stripos($default['de_easy_pay_services'], 'nhnkcp_kakaopay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nhnkcp_kakaopay">KAKAOPAY (카카오페이)</label><br>
|
||||
<input type="checkbox" id="de_easy_nhnkcp_applepay" name="de_easy_pays[]" value="nhnkcp_applepay" <?php if(stripos($default['de_easy_pay_services'], 'nhnkcp_applepay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nhnkcp_applepay">APPLEPAY (애플페이)</label>
|
||||
<?php foreach (shop_easypay_catalog('kcp') as $easy_key => $easy_provider) { ?>
|
||||
<input type="checkbox" name="de_easy_pays[]" id="de_easy_<?php echo $easy_key; ?>" value="<?php echo $easy_key; ?>"<?php echo in_array($easy_key, explode(',', $default['de_easy_pay_services']), true) ? ' checked' : ''; ?>>
|
||||
<label for="de_easy_<?php echo $easy_key; ?>"><?php echo $easy_provider[0]; ?></label><br>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pg_info_fld kcp_info_fld">
|
||||
<th scope="row"><label for="de_global_nhnkcp_naverpay">NHN KCP 네이버페이 사용</label></th>
|
||||
<td>
|
||||
<?php echo help("체크시 타 PG (토스페이먼츠, KG 이니시스) 사용중일때도 NHN_KCP 를 통한 네이버페이 간편결제를 사용할수 있습니다.\n실결제시 반드시 결제대행사 NHN_KCP 항목에 KCP SITE CODE와 NHN KCP SITE KEY를 입력해야 합니다."); ?>
|
||||
<?php echo help("선택한 PG에서 네이버페이를 지원하지 않거나 사용 설정하지 않은 경우에만 NHN KCP 네이버페이를 보조 결제로 제공합니다. 기본 PG의 네이버페이가 활성화되어 있으면 병용 버튼과 요청 경로를 사용하지 않습니다. 기존 KCP 주문의 조회·취소는 유지됩니다.\n실결제시 반드시 결제대행사 NHN_KCP 항목에 KCP SITE CODE와 NHN KCP SITE KEY를 입력해야 합니다."); ?>
|
||||
<input type="checkbox" id="de_global_nhnkcp_naverpay" name="de_easy_pays[]" value="global_nhnkcp_naverpay" <?php if(stripos($default['de_easy_pay_services'], 'global_nhnkcp_naverpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_global_nhnkcp_naverpay">NAVERPAY (네이버페이)</label><br>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -889,7 +659,7 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<tr class="pg_info_fld lg_info_fld">
|
||||
<th scope="row"><label for="cf_lg_mert_key">토스페이먼츠(구버전) MERT KEY</label></th>
|
||||
<td>
|
||||
<?php echo help("토스페이먼츠(구버전) 상점 MertKey는 상점관리자 -> 개발자센터 -> API키 -> 머트 키에서 확인하실 수 있습니다.\n예) 95160cce09854ef44d2edb2bfb05f9f3"); ?>
|
||||
<?php echo help("토스페이먼츠(구버전) 상점 MertKey는 상점관리자 -> 개발자센터 -> API키 -> 머트 키에서 확인하실 수 있습니다. 예) 95160cce09854ef44d2edb2bfb05f9f3\n실결제용 [라이브] 키와 테스트용 [테스트] 키는 서로 다르므로, <b>테스트로 결제시에는 [테스트] 키</b>로 변경하여 사용해주시기 바랍니다."); ?>
|
||||
<input type="text" name="cf_lg_mert_key" value="<?php echo get_sanitize_input($config['cf_lg_mert_key']); ?>" id="cf_lg_mert_key" class="frm_input " size="36" maxlength="50">
|
||||
</td>
|
||||
</tr>
|
||||
@@ -977,34 +747,18 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<input type="text" name="de_inicis_pro_summary_days" value="<?php echo isset($default['de_inicis_pro_summary_days']) ? (int) $default['de_inicis_pro_summary_days'] : 1825; ?>" id="de_inicis_pro_summary_days" class="frm_input" size="6" maxlength="4"> 일
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pg_info_fld inicis_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_samsung_pay_use">KG이니시스 삼성페이 사용</label>
|
||||
<a href="http://sir.kr/main/service/samsungpay.php" target="_blank" class="kg_btn">삼성페이 서비스신청하기</a>
|
||||
</th>
|
||||
<?php foreach (array('inicis' => 'KG이니시스', 'toss' => '토스페이먼츠 API') as $easy_pg => $easy_title) { ?>
|
||||
<tr class="pg_info_fld <?php echo $easy_pg; ?>_info_fld">
|
||||
<th scope="row"><?php echo $easy_title; ?> 간편결제</th>
|
||||
<td>
|
||||
<?php echo help("KG이니시스와 별도로 <strong>삼성페이 사용 계약을 하신 경우</strong>에만 체크해주세요. INIpay PRO 사용 시 PC와 모바일 주문서에 삼성페이가 노출되며 삼성페이 결제창을 직접 호출합니다. 구버전 결제에서는 모바일 주문서에만 노출됩니다.<br>실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?>
|
||||
<input type="checkbox" name="de_samsung_pay_use" value="1" id="de_samsung_pay_use"<?php echo $default['de_samsung_pay_use']?' checked':''; ?>> <label for="de_samsung_pay_use">사용</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pg_info_fld inicis_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_inicis_lpay_use">KG이니시스 L.pay 사용</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo help("체크 시 KG이니시스 L.pay를 사용합니다. INIpay PRO에서는 주문서에서 L.pay 선택 시 L.pay 결제창을 직접 호출합니다.<br>실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?>
|
||||
<input type="checkbox" name="de_inicis_lpay_use" value="1" id="de_inicis_lpay_use"<?php echo $default['de_inicis_lpay_use']?' checked':''; ?>> <label for="de_inicis_lpay_use">사용</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="pg_info_fld inicis_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_inicis_kakaopay_use">KG이니시스 카카오페이 사용</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo help("체크 시 KG이니시스 결제의 카카오페이를 사용합니다. INIpay PRO에서는 주문서에서 카카오페이 선택 시 카카오페이 결제창을 직접 호출합니다.<br>실결제 시 상점 아이디와 사용 중인 결제모듈의 인증키(PRO: HashKey, 구버전: 웹결제 사인키)를 입력해 주세요.", 50); ?>
|
||||
<input type="checkbox" name="de_inicis_kakaopay_use" value="1" id="de_inicis_kakaopay_use"<?php echo $default['de_inicis_kakaopay_use']?' checked':''; ?>> <label for="de_inicis_kakaopay_use">사용</label>
|
||||
<?php echo help("PG사 간편결제 버튼 사용을 '노출함'으로 설정하고, 해당 PG와 계약하여 사용할 수 있는 수단만 선택하세요. 계약 상태는 PG사에 확인해야 하며 이 화면에서 자동 조회하지 않습니다. 자체창 호출에 별도 계약이 필요할 수 있고 일부 수단은 테스트 결제를 지원하지 않습니다. 실제 MID에서 승인·취소를 확인한 후 제공하세요.\n애플페이는 iOS 모바일에서만 표시합니다. 삼성페이는 PC에서 휴대폰으로 연결하며 구 INIpay에서는 모바일에서만 표시합니다.\nKG이니시스는 삼성페이·L.pay·카카오페이를 지원하며 INIpay PRO는 HashKey, 구버전은 웹결제 사인키가 필요합니다."); ?>
|
||||
<?php foreach (shop_easypay_catalog($easy_pg) as $easy_key => $easy_provider) { ?>
|
||||
<input type="checkbox" name="de_easy_pays[]" id="de_easy_<?php echo $easy_key; ?>" value="<?php echo $easy_key; ?>"<?php echo in_array($easy_key, explode(',', $default['de_easy_pay_services']), true) ? ' checked' : ''; ?>>
|
||||
<label for="de_easy_<?php echo $easy_key; ?>"><?php echo $easy_provider[0]; ?></label><br>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr class="pg_info_fld inicis_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_inicis_cartpoint_use">KG이니시스 신용카드 포인트 결제</label>
|
||||
@@ -1014,46 +768,6 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<input type="checkbox" name="de_inicis_cartpoint_use" value="1" id="de_inicis_cartpoint_use"<?php echo $default['de_inicis_cartpoint_use']?' checked':''; ?>> <label for="de_inicis_cartpoint_use">사용</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="kakao_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_kakaopay_mid">카카오페이 상점아이디<br>( KG이니시스 )</label>
|
||||
<a href="http://sir.kr/main/service/kakaopay.php?kk=yc5" target="_blank" class="kakao_btn">카카오페이 서비스신청하기</a>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo help("KG이니시스로 부터 카카오페이 간편결제만 사용용도로 발급 받으신 상점아이디(MID) 10자리 중 SIRK 을 제외한 나머지 6자리를 입력 합니다."); ?>
|
||||
<span class="sitecode">SIRK</span> <input type="text" name="de_kakaopay_mid" value="<?php echo get_sanitize_input($default['de_kakaopay_mid']); ?>" id="de_kakaopay_mid" class="frm_input code_input" size="10" maxlength="7">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="kakao_info_fld">
|
||||
<th scope="row"><label for="de_kakaopay_key">카카오페이 상점키<br>( KG이니시스 )</label></th>
|
||||
<td>
|
||||
<?php echo help("SIRK****** 아이디로 KG이니시스에서 발급받은 웹결제 사인키를 입력합니다.\nKG이니시스 상점관리자 > 상점정보 > 계약정보 > 부가정보의 웹결제 signkey생성 조회 버튼 클릭, 팝업창에서 생성 버튼 클릭 후 해당 값을 입력합니다."); ?>
|
||||
<input type="text" name="de_kakaopay_key" value="<?php echo get_sanitize_input($default['de_kakaopay_key']); ?>" id="de_kakaopay_key" class="frm_input" size="100">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="kakao_info_fld">
|
||||
<th scope="row"><label for="de_kakaopay_cancelpwd">카카오페이 키패스워드<br>( KG이니시스 )</label></th>
|
||||
<td>
|
||||
<?php echo help("SIRK****** 아이디로 KG이니시스에서 발급받은 4자리 상점 키패스워드를 입력합니다.\nKG이니시스 상점관리자 패스워드와 관련이 없습니다.\n키패스워드 값을 확인하시려면 상점측에 발급된 키파일 안의 readme.txt 파일을 참조해 주십시오"); ?>
|
||||
<input type="text" name="de_kakaopay_cancelpwd" value="<?php echo get_sanitize_input($default['de_kakaopay_cancelpwd']); ?>" id="de_kakaopay_cancelpwd" class="frm_input" size="20">
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="kakao_info_fld">
|
||||
<th scope="row">
|
||||
<label for="de_kakaopay_enckey">KG이니시스<br>카카오페이 사용</label>
|
||||
</th>
|
||||
<td>
|
||||
<?php echo help("체크시 카카오페이 (KG 이니시스)를 사용합니다. <br >KG 이니시스의 SIRK****** 아이디를 받은 상점만 해당됩니다.", 50); ?>
|
||||
<input type="checkbox" name="de_kakaopay_enckey" value="1" id="de_kakaopay_enckey"<?php echo $default['de_kakaopay_enckey']?' checked':''; ?>> <label for="de_kakaopay_enckey">사용</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="kakao_info_fld" style="display:none">
|
||||
<th scope="row"><label for="de_kakaopay_hashkey">카카오페이 상점 HashKey</label></th>
|
||||
<td>
|
||||
<?php echo help("카카오페이로 부터 발급 받으신 상점 인증 전용 HashKey를 입력합니다."); ?>
|
||||
<input type="text" name="de_kakaopay_hashkey" value="<?php echo get_sanitize_input($default['de_kakaopay_hashkey']); ?>" id="de_kakaopay_hashkey" class="frm_input" size="20">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="pg_info_fld nicepay_info_fld" id="nicepay_info_anchor">
|
||||
<th scope="row"><label for="de_nicepay_mid">NICEPAY MID</label><br><a href="http://sir.kr/main/service/nicepayments_pg.php" target="_blank" id="scf_nicepay_reg" class="nicepay_btn">NICEPAY 신청하기</a></th>
|
||||
@@ -1075,14 +789,10 @@ if(!$default['de_kakaopay_cancelpwd']){
|
||||
<th scope="row"><label for="de_nicepay_easy_pays">NICEPAY 간편결제</label></th>
|
||||
<td>
|
||||
<?php echo help("체크시 NICEPAY 간편결제들을 활성화 합니다.\nNICEPAY > 간편결제는 테스트결제가 되지 않습니다. 실결제에만 정상작동 합니다.\n애플페이는 IOS 기기에 모바일결제만 가능합니다."); ?>
|
||||
<input type="checkbox" id="de_easy_nicepay_samsungpay" name="de_easy_pays[]" value="nicepay_samsungpay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_samsungpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_samsungpay" disabled>삼성페이</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_naverpay" name="de_easy_pays[]" value="nicepay_naverpay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_naverpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_naverpay">NAVERPAY (네이버페이)</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_kakaopay" name="de_easy_pays[]" value="nicepay_kakaopay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_kakaopay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_kakaopay">KAKAOPAY (카카오페이)</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_applepay" name="de_easy_pays[]" value="nicepay_applepay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_applepay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_applepay">APPLEPAY (애플페이)</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_paycopay" name="de_easy_pays[]" value="nicepay_paycopay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_paycopay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_paycopay">페이코</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_skpay" name="de_easy_pays[]" value="nicepay_skpay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_skpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_skpay">SK페이</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_ssgpay" name="de_easy_pays[]" value="nicepay_ssgpay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_ssgpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_ssgpay">SSG페이</label><br>
|
||||
<input type="checkbox" id="de_easy_nicepay_lpay" name="de_easy_pays[]" value="nicepay_lpay" <?php if(stripos($default['de_easy_pay_services'], 'nicepay_lpay') !== false){ echo 'checked="checked"'; } ?> > <label for="de_easy_nicepay_lpay">LPAY</label>
|
||||
<?php foreach (shop_easypay_catalog('nicepay') as $easy_key => $easy_provider) { ?>
|
||||
<input type="checkbox" name="de_easy_pays[]" id="de_easy_<?php echo $easy_key; ?>" value="<?php echo $easy_key; ?>"<?php echo in_array($easy_key, explode(',', $default['de_easy_pay_services']), true) ? ' checked' : ''; ?>>
|
||||
<label for="de_easy_<?php echo $easy_key; ?>"><?php echo $easy_provider[0]; ?></label><br>
|
||||
<?php } ?>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -1924,7 +1634,7 @@ function fconfig_check(f)
|
||||
}
|
||||
} else if ( f.de_pg_service.value == "lg" ) {
|
||||
if( f.cf_lg_mid.value && f.cf_lg_mert_key.value && parseInt(f.de_card_test.value) > 0 ){
|
||||
pg_msg = "토스페이먼츠(구버전)";
|
||||
msg += "(주의!) 토스페이먼츠(구버전) 결제의 결제 설정이 현재 테스트결제로 되어 있습니다.\nMERT KEY를 [테스트]키로 설정한 후 테스트결제를 진행해주세요.\n쇼핑몰 운영중이면 반드시 실결제 전환 및 [라이브]키로 설정하여 운영하셔야 합니다.\n실결제로 변경하려면 결제설정 탭 -> 결제 테스트에서 실결제를 선택해 주세요.\n정말로 테스트결제로 설정하시겠습니까?";
|
||||
}
|
||||
} else if ( f.de_pg_service.value == "toss" ) {
|
||||
if( f.cf_lg_mid.value && f.cf_toss_client_key.value && f.cf_toss_secret_key.value && parseInt(f.de_card_test.value) > 0 ){
|
||||
@@ -2239,26 +1949,7 @@ if($default['de_iche_use'] || $default['de_vbank_use'] || $default['de_hp_use']
|
||||
}
|
||||
}
|
||||
|
||||
// 카카오페이의 경우 log 디렉토리 체크
|
||||
if($default['de_kakaopay_mid'] && $default['de_kakaopay_key'] && $default['de_kakaopay_enckey'] && $default['de_kakaopay_hashkey'] && $default['de_kakaopay_cancelpwd']) {
|
||||
$log_path = G5_SHOP_PATH.'/kakaopay/log';
|
||||
|
||||
if(!is_dir($log_path)) {
|
||||
echo '<script>'.PHP_EOL;
|
||||
echo 'alert("'.str_replace(G5_PATH.'/', '', G5_SHOP_PATH).'/kakaopay 폴더 안에 log 폴더를 생성하신 후 쓰기권한을 부여해 주십시오.\n> mkdir log\n> chmod 707 log");'.PHP_EOL;
|
||||
echo '</script>'.PHP_EOL;
|
||||
} else {
|
||||
if(!is_writable($log_path)) {
|
||||
echo '<script>'.PHP_EOL;
|
||||
echo 'alert("'.str_replace(G5_PATH.'/', '',$log_path).' 폴더에 쓰기권한을 부여해 주십시오.\n> chmod 707 log");'.PHP_EOL;
|
||||
echo '</script>'.PHP_EOL;
|
||||
} else {
|
||||
if( function_exists('check_log_folder') && is_writable($log_path) ){
|
||||
check_log_folder($log_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ $logo_img_fields = array('logo_img', 'logo_img2', 'mobile_logo_img', 'mobile_log
|
||||
foreach ($logo_img_fields as $logo_img_field) {
|
||||
if (isset($_FILES[$logo_img_field]['name']) && $_FILES[$logo_img_field]['name']) {
|
||||
$filename = get_safe_filename($_FILES[$logo_img_field]['name']);
|
||||
if (is_disallowed_svg_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)');
|
||||
if (is_disallowed_active_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -88,15 +88,16 @@ if(isset($_POST['de_taxsave_types_transfer']) && $_POST['de_taxsave_types_transf
|
||||
$de_taxsave_types .= ',transfer';
|
||||
}
|
||||
|
||||
// NHN_KCP 간편결제 체크
|
||||
$de_easy_pay_services = '';
|
||||
if(isset($_POST['de_easy_pays'])){
|
||||
$tmps = array();
|
||||
foreach( (array) $_POST['de_easy_pays'] as $v ){
|
||||
$tmps[] = preg_replace('/[^0-9a-z_\-]/i', '', $v);
|
||||
}
|
||||
$de_easy_pay_services = implode(",", $tmps);
|
||||
// 알려진 PG별 간편결제 설정만 저장한다. 선택 해제도 명시적으로 보존한다.
|
||||
$easy_allowed = array('global_nhnkcp_naverpay', 'used_nhnkcp_naverpay_point');
|
||||
foreach (array('kcp', 'inicis', 'toss', 'nicepay') as $easy_pg) {
|
||||
$easy_allowed = array_merge($easy_allowed, array_keys(shop_easypay_catalog($easy_pg)));
|
||||
}
|
||||
$easy_selected = array();
|
||||
foreach (isset($_POST['de_easy_pays']) ? (array) $_POST['de_easy_pays'] : array() as $easy_key) {
|
||||
if (is_string($easy_key) && in_array($easy_key, $easy_allowed, true)) $easy_selected[] = $easy_key;
|
||||
}
|
||||
$de_easy_pay_services = implode(',', array_unique(array_merge($easy_selected, array('inicis_configured', 'toss_configured'))));
|
||||
|
||||
//KVE-2019-0689, KVE-2019-0691, KVE-2019-0694
|
||||
$check_sanitize_keys = array(
|
||||
@@ -189,11 +190,6 @@ $check_sanitize_keys = array(
|
||||
'de_inicis_cartpoint_use', //KG이니시스 신용카드 포인트 결제
|
||||
'de_nicepay_mid', //NICEPAY 상점아이디
|
||||
'de_nicepay_key', //NICEPAY 상점키
|
||||
'de_kakaopay_mid', //카카오페이 상점MID
|
||||
'de_kakaopay_key', //카카오페이 상점키
|
||||
'de_kakaopay_enckey', //카카오페이 상점 EncKey
|
||||
'de_kakaopay_hashkey', //카카오페이 상점 HashKey
|
||||
'de_kakaopay_cancelpwd', //카카오페이 결제취소 비밀번호
|
||||
'de_naverpay_mid', //네이버페이 가맹점 아이디
|
||||
'de_naverpay_cert_key', //네이버페이 가맹점 인증키
|
||||
'de_naverpay_button_key', //네이버페이 버튼 인증키
|
||||
@@ -269,6 +265,11 @@ foreach( $check_sanitize_keys as $key ){
|
||||
}
|
||||
}
|
||||
|
||||
// 구버전 결제 코드 및 기존 스킨과의 호환을 위해 개별 컬럼도 동기화한다.
|
||||
foreach (shop_easypay_legacy_keys() as $easy_key => $legacy_key) {
|
||||
$$legacy_key = (int) in_array($easy_key, $easy_selected, true);
|
||||
}
|
||||
|
||||
$de_inicis_pro_use = !empty($de_inicis_pro_use) ? 1 : 0;
|
||||
$de_inicis_hash_key = preg_replace('/[^A-Za-z0-9+\/=_-]/', '', $de_inicis_hash_key);
|
||||
|
||||
@@ -300,11 +301,6 @@ if($de_pg_service == 'kcp' && ! $de_card_test && ($de_iche_use || $de_vbank_use
|
||||
alert('NHN KCP SITE KEY를 입력해 주십시오.');
|
||||
}
|
||||
|
||||
if( $de_kakaopay_enckey && ($de_pg_service === 'inicis' || $de_inicis_lpay_use || $de_inicis_kakaopay_use) ){
|
||||
|
||||
$warning_msg = 'KG 이니시스 결제 또는 L.pay 또는 KG이니시스 카카오페이를 사용시 결제모듈 중복문제로 카카오페이를 활성화 할수 없습니다. \\n\\n카카오페이 사용을 비활성화 합니다.';
|
||||
$de_kakaopay_enckey = '';
|
||||
}
|
||||
|
||||
//
|
||||
// 영카트 default
|
||||
@@ -481,11 +477,6 @@ $sql = " update {$g5['g5_shop_default_table']}
|
||||
de_hp_use = '{$de_hp_use}',
|
||||
de_escrow_use = '{$de_escrow_use}',
|
||||
de_tax_flag_use = '{$de_tax_flag_use}',
|
||||
de_kakaopay_mid = '{$de_kakaopay_mid}',
|
||||
de_kakaopay_key = '{$de_kakaopay_key}',
|
||||
de_kakaopay_enckey = '{$de_kakaopay_enckey}',
|
||||
de_kakaopay_hashkey = '{$de_kakaopay_hashkey}',
|
||||
de_kakaopay_cancelpwd = '{$de_kakaopay_cancelpwd}',
|
||||
de_member_reg_coupon_use = '{$de_member_reg_coupon_use}',
|
||||
de_member_reg_coupon_term = '{$de_member_reg_coupon_term}',
|
||||
de_member_reg_coupon_price = '{$de_member_reg_coupon_price}',
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// 날짜 없는 최초 조회와 잘못된 날짜 조회를 구분한다. 오류는 false로 반환한다.
|
||||
function shop_admin_date_range($params, $default_to = '')
|
||||
{
|
||||
if (!array_key_exists('fr_date', $params) && !array_key_exists('to_date', $params)) {
|
||||
return array('', $default_to);
|
||||
}
|
||||
|
||||
foreach (array('fr_date', 'to_date') as $key) {
|
||||
if (!isset($params[$key]) || !is_string($params[$key]) || !preg_match('/\A[0-9]{8}\z/', $params[$key])) {
|
||||
return false;
|
||||
}
|
||||
$date = $params[$key];
|
||||
if (!checkdate((int) substr($date, 4, 2), (int) substr($date, 6, 2), (int) substr($date, 0, 4))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($params['fr_date'] > $params['to_date']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array($params['fr_date'], $params['to_date']);
|
||||
}
|
||||
@@ -30,6 +30,9 @@ if($w == 'd') {
|
||||
|
||||
// 주문정보
|
||||
$data = unserialize(base64_decode($od['dt_data']));
|
||||
if (isset($data['od_settle_case']) && $data['od_settle_case'] === 'KAKAOPAY') {
|
||||
alert('SIRK 전용 카카오페이 미완료 주문은 결제 상태를 이니시스에서 확인한 뒤 사후지원 담당자에게 문의해 주십시오.');
|
||||
}
|
||||
$data_od_cp_id = isset($data['od_cp_id']) ? safe_replace_regex($data['od_cp_id'], 'cp_id') : '';
|
||||
$data_sc_cp_id = isset($data['sc_cp_id']) ? safe_replace_regex($data['sc_cp_id'], 'cp_id') : '';
|
||||
|
||||
@@ -210,8 +213,6 @@ if($default['de_tax_flag_use']) {
|
||||
}
|
||||
|
||||
$od_pg = $default['de_pg_service'];
|
||||
if($data['od_settle_case'] == 'KAKAOPAY')
|
||||
$od_pg = 'KAKAOPAY';
|
||||
|
||||
$od_email = get_email_address($data['od_email']);
|
||||
$od_name = addslashes(clean_xss_tags($data['od_name']));
|
||||
|
||||
@@ -73,10 +73,6 @@ for ($i=0; $row=sql_fetch_array($result); $i++)
|
||||
}
|
||||
|
||||
// 모바일 1줄당 이미지수 필드 추가
|
||||
if(!sql_query(" select ev_mobile_list_row from {$g5['g5_shop_event_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_event_table']}`
|
||||
ADD `ev_mobile_list_row` int(11) NOT NULL DEFAULT '0' AFTER `ev_mobile_list_mod` ", true);
|
||||
}
|
||||
|
||||
include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
?>
|
||||
@@ -455,4 +451,4 @@ function feventform_check(f)
|
||||
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -158,29 +158,13 @@ for ($i=0; $row=sql_fetch_array($result); $i++)
|
||||
}
|
||||
|
||||
// 재입고알림 설정 필드 추가
|
||||
if(!sql_query(" select it_stock_sms from {$g5['g5_shop_item_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `it_stock_sms` tinyint(4) NOT NULL DEFAULT '0' AFTER `it_stock_qty` ", true);
|
||||
}
|
||||
|
||||
// 추가옵션 포인트 설정 필드 추가
|
||||
if(!sql_query(" select it_supply_point from {$g5['g5_shop_item_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `it_supply_point` int(11) NOT NULL DEFAULT '0' AFTER `it_point_type` ", true);
|
||||
}
|
||||
|
||||
// 상품메모 필드 추가
|
||||
if(!sql_query(" select it_shop_memo from {$g5['g5_shop_item_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `it_shop_memo` text NOT NULL AFTER `it_use_avg` ", true);
|
||||
}
|
||||
|
||||
// 지식쇼핑 PID 필드추가
|
||||
// 상품메모 필드 추가
|
||||
if(!sql_query(" select ec_mall_pid from {$g5['g5_shop_item_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `ec_mall_pid` varchar(255) NOT NULL AFTER `it_shop_memo` ", true);
|
||||
}
|
||||
|
||||
$pg_anchor ='<ul class="anchor">
|
||||
<li><a href="#anc_sitfrm_cate">상품분류</a></li>
|
||||
@@ -199,17 +183,8 @@ $pg_anchor ='<ul class="anchor">
|
||||
|
||||
|
||||
// 쿠폰적용안함 설정 필드 추가
|
||||
if(!sql_query(" select it_nocoupon from {$g5['g5_shop_item_table']} limit 1", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `it_nocoupon` tinyint(4) NOT NULL DEFAULT '0' AFTER `it_use` ", true);
|
||||
}
|
||||
|
||||
// 스킨필드 추가
|
||||
if(!sql_query(" select it_skin from {$g5['g5_shop_item_table']} limit 1", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_table']}`
|
||||
ADD `it_skin` varchar(255) NOT NULL DEFAULT '' AFTER `ca_id3`,
|
||||
ADD `it_mobile_skin` varchar(255) NOT NULL DEFAULT '' AFTER `it_skin` ", true);
|
||||
}
|
||||
?>
|
||||
|
||||
<form name="fitemform" action="./itemformupdate.php" method="post" enctype="MULTIPART/FORM-DATA" autocomplete="off" onsubmit="return fitemformcheck(this)">
|
||||
|
||||
@@ -4,13 +4,18 @@ include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
include_once('./date_filter.lib.php');
|
||||
$date_range = shop_admin_date_range($_GET, date('Ymd'));
|
||||
if ($date_range === false) {
|
||||
http_response_code(400);
|
||||
alert('조회 기간은 올바른 시작일과 종료일을 YYYYMMDD 형식으로 입력해 주십시오. 시작일은 종료일보다 늦을 수 없습니다.');
|
||||
}
|
||||
list($fr_date, $to_date) = $date_range;
|
||||
|
||||
$g5['title'] = '상품판매순위';
|
||||
include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
include_once(G5_PLUGIN_PATH.'/jquery-ui/datepicker.php');
|
||||
|
||||
$fr_date = (isset($_GET['fr_date']) && preg_match("/[0-9]/", $_GET['fr_date'])) ? $_GET['fr_date'] : '';
|
||||
$to_date = (isset($_GET['to_date']) && preg_match("/[0-9]/", $_GET['to_date'])) ? $_GET['to_date'] : date("Ymd", time());
|
||||
|
||||
$doc = isset($_GET['doc']) ? clean_xss_tags($_GET['doc'], 1, 1) : '';
|
||||
$sort1 = (isset($_GET['sort1']) && in_array($_GET['sort1'], array('ct_status_1', 'ct_status_2', 'ct_status_3', 'ct_status_4', 'ct_status_5', 'ct_status_6', 'ct_status_7', 'ct_status_8', 'ct_status_9', 'ct_status_sum'))) ? $_GET['sort1'] : 'ct_status_sum';
|
||||
$sort2 = (isset($_GET['sort2']) && in_array($_GET['sort2'], array('desc', 'asc'))) ? $_GET['sort2'] : 'desc';
|
||||
@@ -57,7 +62,14 @@ $sql = $sql . " limit $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
//$qstr = 'page='.$page.'&sort1='.$sort1.'&sort2='.$sort2;
|
||||
$qstr1 = $qstr.'&fr_date='.$fr_date.'&to_date='.$to_date.'&sel_ca_id='.$sel_ca_id;
|
||||
// 검증된 날짜만 URL에 전달한다. 최초 조회의 빈 날짜는 링크에서 생략한다.
|
||||
$filter_query = array('sel_ca_id' => $sel_ca_id);
|
||||
if ($fr_date !== '' && $to_date !== '') {
|
||||
$filter_query['fr_date'] = $fr_date;
|
||||
$filter_query['to_date'] = $to_date;
|
||||
}
|
||||
$qstr1 = htmlspecialchars(http_build_query($filter_query, '', '&'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$paging_query = htmlspecialchars(http_build_query(array_merge($filter_query, array('sort1' => $sort1, 'sort2' => $sort2)), '', '&'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
$listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목록</a>';
|
||||
?>
|
||||
@@ -92,9 +104,9 @@ $listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목
|
||||
|
||||
기간설정
|
||||
<label for="fr_date" class="sound_only">시작일</label>
|
||||
<input type="text" name="fr_date" value="<?php echo $fr_date; ?>" id="fr_date" required class="required frm_input" size="8" maxlength="8"> 에서
|
||||
<input type="text" name="fr_date" value="<?php echo htmlspecialchars($fr_date, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" id="fr_date" required class="required frm_input" size="8" maxlength="8"> 에서
|
||||
<label for="to_date" class="sound_only">종료일</label>
|
||||
<input type="text" name="to_date" value="<?php echo $to_date; ?>" id="to_date" required class="required frm_input" size="8" maxlength="8"> 까지
|
||||
<input type="text" name="to_date" value="<?php echo htmlspecialchars($to_date, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" id="to_date" required class="required frm_input" size="8" maxlength="8"> 까지
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
|
||||
</form>
|
||||
@@ -162,7 +174,7 @@ $listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, "{$_SERVER['SCRIPT_NAME']}?$qstr1&page="); ?>
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, "{$_SERVER['SCRIPT_NAME']}?$paging_query&page="); ?>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
|
||||
@@ -11,18 +11,6 @@ include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
if(!isset($g5['g5_shop_item_stocksms_table']))
|
||||
die('<meta charset="utf-8">dbconfig.php 파일에 <strong>$g5[\'g5_shop_item_stocksms_table\'] = G5_SHOP_TABLE_PREFIX.\'item_stocksms\';</strong> 를 추가해 주세요.');
|
||||
|
||||
if(!sql_query(" select ss_id from {$g5['g5_shop_item_stocksms_table']} limit 1", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['g5_shop_item_stocksms_table']}` (
|
||||
`ss_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`it_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`ss_hp` varchar(255) NOT NULL DEFAULT '',
|
||||
`ss_send` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ss_send_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`ss_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`ss_ip` varchar(25) NOT NULL DEFAULT '',
|
||||
PRIMARY KEY (`ss_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ", true);
|
||||
}
|
||||
|
||||
$doc = isset($_GET['doc']) ? clean_xss_tags($_GET['doc'], 1, 1) : '';
|
||||
$sort1 = (isset($_GET['sort1']) && in_array($_GET['sort1'], array('it_id', 'ss_hp', 'ss_send', 'ss_send_time', 'ss_datetime'))) ? $_GET['sort1'] : 'ss_send';
|
||||
@@ -184,4 +172,4 @@ function fitemstocksms_submit(f)
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -18,13 +18,6 @@ if (!$is['is_id'])
|
||||
alert('등록된 자료가 없습니다.');
|
||||
|
||||
// 사용후기 의 답변 필드 추가
|
||||
if (!isset($is['is_reply_subject'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_item_use_table']}`
|
||||
ADD COLUMN `is_reply_subject` VARCHAR(255) NOT NULL DEFAULT '' AFTER `is_confirm`,
|
||||
ADD COLUMN `is_reply_content` TEXT NOT NULL AFTER `is_reply_subject`,
|
||||
ADD COLUMN `is_reply_name` VARCHAR(25) NOT NULL DEFAULT '' AFTER `is_reply_content`
|
||||
", true);
|
||||
}
|
||||
|
||||
$name = get_sideview($is['mb_id'], get_text($is['is_name']), $is['mb_email'], $is['mb_homepage']);
|
||||
|
||||
@@ -117,4 +110,4 @@ function fitemuseform_submit(f)
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -76,27 +76,10 @@ $sql = " select it_id,
|
||||
$result = sql_query($sql);
|
||||
|
||||
// 주소 참고항목 필드추가
|
||||
if(!isset($od['od_addr3'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_order_table']}`
|
||||
ADD `od_addr3` varchar(255) NOT NULL DEFAULT '' AFTER `od_addr2`,
|
||||
ADD `od_b_addr3` varchar(255) NOT NULL DEFAULT '' AFTER `od_b_addr2` ", true);
|
||||
}
|
||||
|
||||
// 배송목록에 참고항목 필드추가
|
||||
if(!sql_query(" select ad_addr3 from {$g5['g5_shop_order_address_table']} limit 1", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_order_address_table']}`
|
||||
ADD `ad_addr3` varchar(255) NOT NULL DEFAULT '' AFTER `ad_addr2` ", true);
|
||||
}
|
||||
|
||||
// 결제 PG 필드 추가
|
||||
if(!sql_query(" select od_pg from {$g5['g5_shop_order_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_order_table']}`
|
||||
ADD `od_pg` varchar(255) NOT NULL DEFAULT '' AFTER `od_mobile`,
|
||||
ADD `od_casseqno` varchar(255) NOT NULL DEFAULT '' AFTER `od_escrow` ", true);
|
||||
|
||||
// 주문 결제 PG kcp로 설정
|
||||
sql_query(" update {$g5['g5_shop_order_table']} set od_pg = 'kcp' ");
|
||||
}
|
||||
|
||||
// LG 현금영수증 JS
|
||||
if($od['od_pg'] == 'lg') {
|
||||
@@ -256,6 +239,13 @@ if ($is_nicepay_vbank_pg_cancel) {
|
||||
<input type="hidden" name="search" value="<?php echo $search; ?>">
|
||||
<input type="hidden" name="page" value="<?php echo $page;?>">
|
||||
<input type="hidden" name="pg_cancel" value="0">
|
||||
<?php if ($od['od_pg'] === 'KAKAOPAY') { ?>
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>SIRK 전용 카카오페이의 자동 취소 지원이 종료되었습니다. <a href="https://iniweb.inicis.com/" target="_blank" rel="noopener noreferrer">이니시스 상점관리자</a>에서 전체·부분취소를 처리한 뒤 주문 상태와 환불금액을 정리해 주십시오. 이 화면의 상태 변경은 PG 취소를 수행하지 않습니다.</p>
|
||||
<p>기존 이용 고객 지원: 김민섭 <a href="mailto:minsup@sir.kr">minsup@sir.kr</a></p>
|
||||
<label><input type="checkbox" name="sirk_cancel_confirmed" value="1"> 이니시스에서 실제 취소 여부와 금액을 확인했습니다. 선택한 상품의 주문 상태만 변경합니다.</label>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<input type="hidden" name="submit_ct_status" value="">
|
||||
<?php if ($is_nicepay_vbank_pg_cancel) { ?>
|
||||
<input type="hidden" name="RefundAcctNo" value="">
|
||||
@@ -1035,7 +1025,7 @@ if ($is_nicepay_vbank_pg_cancel) {
|
||||
<?php if($od['od_status'] == '주문' && $od['od_misu'] > 0) { ?>
|
||||
<a href="./personalpayform.php?popup=yes&od_id=<?php echo $od_id; ?>" id="personalpay_add" class="btn btn_02">개인결제추가</a>
|
||||
<?php } ?>
|
||||
<?php if($od['od_misu'] < 0 && ($od['od_receipt_price'] - $od['od_refund_price']) > 0 && ($od['od_settle_case'] == '신용카드' || $od['od_settle_case'] == '계좌이체' || $od['od_settle_case'] == 'KAKAOPAY' || ($od['od_pg'] == 'nicepay' && $od['od_settle_case'] == '간편결제'))) { ?>
|
||||
<?php if($od['od_pg'] !== 'KAKAOPAY' && $od['od_misu'] < 0 && ($od['od_receipt_price'] - $od['od_refund_price']) > 0 && ($od['od_settle_case'] == '신용카드' || $od['od_settle_case'] == '계좌이체' || $od['od_settle_case'] == 'KAKAOPAY' || ($od['od_pg'] == 'nicepay' && $od['od_settle_case'] == '간편결제'))) { ?>
|
||||
<a href="./orderpartcancel.php?od_id=<?php echo $od_id; ?>" id="orderpartcancel" class="btn btn_02"><?php echo get_text($od['od_settle_case']); ?> 부분취소</a>
|
||||
<?php } ?>
|
||||
<a href="./orderlist.php?<?php echo $qstr; ?>" class="btn btn_02">목록</a>
|
||||
@@ -1472,16 +1462,13 @@ function form_submit(f)
|
||||
|
||||
var msg = "";
|
||||
|
||||
<?php if (is_cancel_shop_pg_order($od)) { ?>
|
||||
<?php if ($od['od_pg'] !== 'KAKAOPAY' && is_cancel_shop_pg_order($od)) { ?>
|
||||
if(status == "취소" || status == "반품" || status == "품절") {
|
||||
var $ct_chk = $("input[name^=ct_chk]");
|
||||
var chk_cnt = $ct_chk.length;
|
||||
var chked_cnt = $ct_chk.filter(":checked").length;
|
||||
<?php if($od['od_pg'] == 'KAKAOPAY') { ?>
|
||||
var cancel_pg = "카카오페이";
|
||||
<?php } else { ?>
|
||||
var cancel_pg = "PG사의 <?php echo get_text($od['od_settle_case']); ?>";
|
||||
<?php } ?>
|
||||
|
||||
|
||||
// 체크하지 않은 나머지 품목이 모두 취소류 상태이면 이번 처리로 주문 전체가 취소된다.
|
||||
var remain_active_cnt = $ct_chk.not(":checked").filter(function() {
|
||||
|
||||
@@ -29,6 +29,16 @@ if (in_array($ct_status, $status_normal) || in_array($ct_status, $status_cancel)
|
||||
alert('변경할 상태가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
// SIRK 주문은 PG 취소를 호출하지 않고, 실제 취소를 확인한 운영자만 로컬 상태를 정리한다.
|
||||
if (in_array($ct_status, $status_cancel, true)) {
|
||||
$sirk_order = sql_fetch(" select od_pg from {$g5['g5_shop_order_table']} where od_id = '" . sql_escape_string($od_id) . "' ");
|
||||
if (isset($sirk_order['od_pg']) && $sirk_order['od_pg'] === 'KAKAOPAY') {
|
||||
if ($pg_cancel || empty($_POST['sirk_cancel_confirmed'])) {
|
||||
alert('SIRK 전용 카카오페이는 이니시스 상점관리자에서 실제 취소 여부를 확인한 뒤 주문 상태만 변경해 주십시오.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// INIpay PRO 전체취소는 로컬 상품상태를 바꾸기 전에 PG 취소를 먼저 확정한다.
|
||||
// PG 취소 실패 후에도 주문만 취소되어 가상계좌 입금이 남는 상태를 방지한다.
|
||||
$inicis_pro_cancel_preprocessed = false;
|
||||
@@ -312,8 +322,10 @@ for ($i=0; $i<$cnt; $i++)
|
||||
$now = G5_TIME_YMDHIS;
|
||||
$ct_history="\n$ct_status|{$member['mb_id']}|$now|$REMOTE_ADDR";
|
||||
|
||||
$complete_time_sql = get_cart_complete_time_sql($ct_status);
|
||||
$sql = " update {$g5['g5_shop_cart_table']}
|
||||
set ct_point_use = '$point_use',
|
||||
set ct_complete_time = $complete_time_sql,
|
||||
ct_point_use = '$point_use',
|
||||
ct_stock_use = '$stock_use',
|
||||
ct_status = '$ct_status',
|
||||
ct_history = CONCAT(ct_history,'$ct_history')
|
||||
@@ -326,6 +338,11 @@ for ($i=0; $i<$cnt; $i++)
|
||||
$arr_it_id[] = $ct['it_id'];
|
||||
}
|
||||
|
||||
// 완료 처리 직후 실행하여 0일 설정도 즉시 적용한다.
|
||||
if ($ct_status == '완료') {
|
||||
save_order_point();
|
||||
}
|
||||
|
||||
// 상품 판매수량 반영
|
||||
if(is_array($arr_it_id) && !empty($arr_it_id)) {
|
||||
$unq_it_id = array_unique($arr_it_id);
|
||||
@@ -455,12 +472,8 @@ if (in_array($ct_status, $status_cancel)) {
|
||||
|
||||
break;
|
||||
case 'KAKAOPAY':
|
||||
include_once(G5_SHOP_PATH.'/settle_kakaopay.inc.php');
|
||||
$_REQUEST['TID'] = $od['od_tno'];
|
||||
$_REQUEST['Amt'] = $od['od_receipt_price'];
|
||||
$_REQUEST['CancelMsg'] = '쇼핑몰 운영자 승인 취소';
|
||||
$_REQUEST['PartialCancelCode'] = 0;
|
||||
include G5_SHOP_PATH.'/kakaopay/kakaopay_cancel.php';
|
||||
$pg_res_cd = 'SIRK_RETIRED';
|
||||
$pg_res_msg = 'SIRK 전용 카카오페이는 이니시스 상점관리자에서 취소해 주십시오.';
|
||||
break;
|
||||
default:
|
||||
include_once(G5_SHOP_PATH.'/settle_kcp.inc.php');
|
||||
|
||||
@@ -140,12 +140,6 @@ $qstr = "$qstr1&sort1=$sort1&sort2=$sort2&page=$page";
|
||||
$listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목록</a>';
|
||||
|
||||
// 주문삭제 히스토리 테이블 필드 추가
|
||||
if(!sql_query(" select mb_id from {$g5['g5_shop_order_delete_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_order_delete_table']}`
|
||||
ADD `mb_id` varchar(20) NOT NULL DEFAULT '' AFTER `de_data`,
|
||||
ADD `de_ip` varchar(255) NOT NULL DEFAULT '' AFTER `mb_id`,
|
||||
ADD `de_datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' AFTER `de_ip` ", true);
|
||||
}
|
||||
|
||||
if( function_exists('pg_setting_check') ){
|
||||
pg_setting_check(true);
|
||||
@@ -681,4 +675,4 @@ function forderlist_submit(f)
|
||||
</script>
|
||||
|
||||
<?php
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -161,6 +161,11 @@ for ($i=0; $i<$count_post_chk; $i++)
|
||||
|
||||
}
|
||||
|
||||
// 일괄 완료 처리도 0일 설정이면 즉시 적립한다.
|
||||
if ($od_status == '완료') {
|
||||
save_order_point();
|
||||
}
|
||||
|
||||
// SMS
|
||||
$sms_count = count($sms_messages);
|
||||
if($sms_count > 0) {
|
||||
|
||||
@@ -12,6 +12,10 @@ $od = sql_fetch($sql);
|
||||
if(! (isset($od['od_id']) && $od['od_id']))
|
||||
alert_close('주문정보가 존재하지 않습니다.');
|
||||
|
||||
if ($od['od_pg'] === 'KAKAOPAY') {
|
||||
alert_close('SIRK 전용 카카오페이의 자동 취소 지원이 종료되었습니다. 이니시스 상점관리자에서 실제 취소 후 주문 상태와 환불금액을 확인해 주십시오.');
|
||||
}
|
||||
|
||||
if($od['od_pg'] == 'inicis' && $od['od_settle_case'] == '계좌이체')
|
||||
alert_close('KG이니시스는 신용카드만 부분취소가 가능합니다.');
|
||||
|
||||
|
||||
@@ -27,6 +27,10 @@ if(! (isset($od['od_id']) && $od['od_id']))
|
||||
if($od['od_settle_case'] == '계좌이체' && substr($od['od_receipt_time'], 0, 10) >= G5_TIME_YMD)
|
||||
alert_close('실시간 계좌이체건의 부분취소 요청은 결제일 익일에 가능합니다.');
|
||||
|
||||
if ($od['od_pg'] === 'KAKAOPAY') {
|
||||
alert_close('SIRK 전용 카카오페이의 자동 취소 지원이 종료되었습니다. 이니시스 상점관리자에서 실제 취소 후 주문 상태와 환불금액을 확인해 주십시오.');
|
||||
}
|
||||
|
||||
// 금액비교
|
||||
$od_misu = abs($od['od_misu']);
|
||||
|
||||
|
||||
@@ -60,24 +60,8 @@ $pg_anchor = '<ul class="anchor">
|
||||
</ul>';
|
||||
|
||||
// pg 설정 필드 추가
|
||||
if(!sql_query(" select pp_pg from {$g5['g5_shop_personalpay_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_personalpay_table']}`
|
||||
ADD `pp_pg` varchar(255) NOT NULL DEFAULT '' AFTER `pp_price` ", true);
|
||||
|
||||
// 개인결제 PG kcp로 설정
|
||||
sql_query(" update {$g5['g5_shop_personalpay_table']} set pp_pg = 'kcp' ");
|
||||
}
|
||||
|
||||
// 현금영수증 필드 추가
|
||||
if(!sql_query(" select pp_cash from {$g5['g5_shop_personalpay_table']} limit 1 ", false)) {
|
||||
sql_query(" ALTER TABLE `{$g5['g5_shop_personalpay_table']}`
|
||||
ADD `pp_cash` tinyint(4) NOT NULL DEFAULT '0' AFTER `pp_shop_memo`,
|
||||
ADD `pp_cash_no` varchar(255) NOT NULL DEFAULT '' AFTER `pp_cash`,
|
||||
ADD `pp_cash_info` text NOT NULL AFTER `pp_cash_no`,
|
||||
ADD `pp_email` varchar(255) NOT NULL DEFAULT '' AFTER `pp_name`,
|
||||
ADD `pp_hp` varchar(255) NOT NULL DEFAULT '' AFTER `pp_email`,
|
||||
ADD `pp_casseqno` varchar(255) NOT NULL DEFAULT '' AFTER `pp_app_no` ", true);
|
||||
}
|
||||
?>
|
||||
|
||||
<form name="fpersonalpayform" action="./personalpayformupdate.php" method="post" onsubmit="return form_check(this);">
|
||||
@@ -288,4 +272,4 @@ if($popup == 'yes') {
|
||||
include_once(G5_PATH.'/tail.sub.php');
|
||||
} else {
|
||||
include_once (G5_ADMIN_PATH.'/admin.tail.php');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,13 +4,18 @@ include_once('./_common.php');
|
||||
|
||||
auth_check_menu($auth, $sub_menu, "r");
|
||||
|
||||
include_once('./date_filter.lib.php');
|
||||
$date_range = shop_admin_date_range($_GET);
|
||||
if ($date_range === false) {
|
||||
http_response_code(400);
|
||||
alert('조회 기간은 올바른 시작일과 종료일을 YYYYMMDD 형식으로 입력해 주십시오. 시작일은 종료일보다 늦을 수 없습니다.');
|
||||
}
|
||||
list($fr_date, $to_date) = $date_range;
|
||||
|
||||
$g5['title'] = '보관함현황';
|
||||
include_once (G5_ADMIN_PATH.'/admin.head.php');
|
||||
include_once(G5_PLUGIN_PATH.'/jquery-ui/datepicker.php');
|
||||
|
||||
$fr_date = (isset($_GET['fr_date']) && preg_match("/[0-9]/", $_GET['fr_date'])) ? $_GET['fr_date'] : '';
|
||||
$to_date = (isset($_GET['to_date']) && preg_match("/[0-9]/", $_GET['to_date'])) ? $_GET['to_date'] : '';
|
||||
|
||||
$doc = isset($_GET['doc']) ? clean_xss_tags($_GET['doc'], 1, 1) : '';
|
||||
$sort1 = (isset($_GET['sort1']) && in_array($_GET['sort1'], array('mb_id', 'it_id', 'wi_time', 'wi_ip'))) ? $_GET['sort1'] : 'it_id_cnt';
|
||||
$sort2 = (isset($_GET['sort2']) && in_array($_GET['sort2'], array('desc', 'asc'))) ? $_GET['sort2'] : 'desc';
|
||||
@@ -47,7 +52,14 @@ $rank = ($page - 1) * $rows;
|
||||
$sql = $sql . " limit $from_record, $rows ";
|
||||
$result = sql_query($sql);
|
||||
|
||||
$qstr1 = $qstr.'&fr_date='.$fr_date.'&to_date='.$to_date.'&sel_ca_id='.$sel_ca_id;
|
||||
// 검증된 날짜만 URL에 전달한다. 최초 조회의 빈 날짜는 링크에서 생략한다.
|
||||
$filter_query = array('sel_ca_id' => $sel_ca_id);
|
||||
if ($fr_date !== '' && $to_date !== '') {
|
||||
$filter_query['fr_date'] = $fr_date;
|
||||
$filter_query['to_date'] = $to_date;
|
||||
}
|
||||
$qstr1 = htmlspecialchars(http_build_query($filter_query, '', '&'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$paging_query = htmlspecialchars(http_build_query(array_merge($filter_query, array('sort1' => $sort1, 'sort2' => $sort2)), '', '&'), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
$listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목록</a>';
|
||||
?>
|
||||
@@ -79,10 +91,10 @@ $listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목
|
||||
</select>
|
||||
|
||||
<label for="fr_date" class="sound_only">시작일</label>
|
||||
<input type="text" name="fr_date" value="<?php echo $fr_date; ?>" id="fr_date" required class="required frm_input" size="8" maxlength="8">
|
||||
<input type="text" name="fr_date" value="<?php echo htmlspecialchars($fr_date, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" id="fr_date" required class="required frm_input" size="8" maxlength="8">
|
||||
~
|
||||
<label for="to_date" class="sound_only">종료일</label>
|
||||
<input type="text" name="to_date" value="<?php echo $to_date; ?>" id="to_date" required class="required frm_input" size="8" maxlength="8">
|
||||
<input type="text" name="to_date" value="<?php echo htmlspecialchars($to_date, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); ?>" id="to_date" required class="required frm_input" size="8" maxlength="8">
|
||||
<input type="submit" value="검색" class="btn_submit">
|
||||
|
||||
</form>
|
||||
@@ -127,7 +139,7 @@ $listall = '<a href="'.$_SERVER['SCRIPT_NAME'].'" class="ov_listall">전체목
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, "{$_SERVER['SCRIPT_NAME']}?$qstr1&page="); ?>
|
||||
<?php echo get_paging(G5_IS_MOBILE ? $config['cf_mobile_pages'] : $config['cf_write_pages'], $page, $total_page, "{$_SERVER['SCRIPT_NAME']}?$paging_query&page="); ?>
|
||||
|
||||
<div class="local_desc01 local_desc">
|
||||
<p>고객님들이 보관함에 가장 많이 넣은 순으로 순위를 출력합니다.</p>
|
||||
|
||||
@@ -7,19 +7,13 @@ auth_check_menu($auth, $sub_menu, "r");
|
||||
$g5['title'] = "SMS 기본설정";
|
||||
|
||||
if (!$config['cf_icode_server_ip']) $config['cf_icode_server_ip'] = '211.172.232.124';
|
||||
if (!$config['cf_icode_server_port']) $config['cf_icode_server_port'] = '7295';
|
||||
|
||||
// 아이코드 토큰키 추가
|
||||
if( ! isset($config['cf_icode_token_key']) ){
|
||||
$sql = "ALTER TABLE `{$g5['config_table']}`
|
||||
ADD COLUMN `cf_icode_token_key` VARCHAR(100) NOT NULL DEFAULT '' AFTER `cf_icode_server_port`; ";
|
||||
sql_query($sql, false);
|
||||
$config['cf_icode_token_key'] = '';
|
||||
}
|
||||
|
||||
// 배열코드 초기화
|
||||
$userinfo = array('payment'=>'', 'coin'=>'');
|
||||
|
||||
if (!$config['cf_icode_server_port']) $config['cf_icode_server_port'] = '7295';
|
||||
|
||||
// 아이코드 토큰키 추가
|
||||
|
||||
// 배열코드 초기화
|
||||
$userinfo = array('payment'=>'', 'coin'=>'');
|
||||
|
||||
if ($config['cf_sms_use'] && $config['cf_icode_id'] && $config['cf_icode_pw'])
|
||||
{
|
||||
$userinfo = get_icode_userinfo($config['cf_icode_id'], $config['cf_icode_pw']);
|
||||
@@ -147,4 +141,4 @@ if ($config['cf_sms_use'] == 'icode') { // 아이코드 사용
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
@@ -5,17 +5,13 @@ include_once("./_common.php");
|
||||
auth_check_menu($auth, $sub_menu, "w");
|
||||
|
||||
check_admin_token();
|
||||
|
||||
$result = sql_query("describe `{$g5['sms5_write_table']}`");
|
||||
while ($row = sql_fetch_array($result)){
|
||||
if( $row['Field'] === 'wr_message' && $row['Type'] === 'varchar(255)' ){
|
||||
sql_query("ALTER TABLE `{$g5['sms5_write_table']}` MODIFY wr_message TEXT NOT NULL;", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$g5['title'] = "문자전송중";
|
||||
|
||||
|
||||
$result = sql_query("describe `{$g5['sms5_write_table']}`");
|
||||
while ($row = sql_fetch_array($result)){
|
||||
}
|
||||
|
||||
$g5['title'] = "문자전송중";
|
||||
|
||||
if ($config['cf_sms_use'] != 'icode') {
|
||||
alert('기본환경설정에서 icode sms 사용이 비활성화 되어 있습니다.');
|
||||
}
|
||||
@@ -366,4 +362,4 @@ function win_close_alert($msg) {
|
||||
location.href = 'history_view.php?wr_no=<?php echo $wr_no?>';
|
||||
</script>
|
||||
<?php
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
include_once(G5_ADMIN_PATH.'/admin.tail.php');
|
||||
|
||||
+1
-5
@@ -6,10 +6,6 @@ if ($is_admin != 'super')
|
||||
alert('최고관리자만 접근 가능합니다.');
|
||||
|
||||
// 테마 필드 추가
|
||||
if(!isset($config['cf_theme'])) {
|
||||
sql_query(" ALTER TABLE `{$g5['config_table']}`
|
||||
ADD `cf_theme` varchar(255) NOT NULL DEFAULT '' AFTER `cf_title` ", true);
|
||||
}
|
||||
|
||||
$theme = get_theme_dir();
|
||||
if($config['cf_theme'] && in_array($config['cf_theme'], $theme))
|
||||
@@ -75,4 +71,4 @@ include_once('./admin.head.php');
|
||||
<?php } ?>
|
||||
|
||||
<?php
|
||||
include_once ('./admin.tail.php');
|
||||
include_once ('./admin.tail.php');
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
include_once('./_common.php');
|
||||
|
||||
$token_case = isset($_POST['token_case']) ? preg_replace('/[^0-9a-z_]/i', '', $_POST['token_case']) : '';
|
||||
|
||||
if( $token_case ){
|
||||
$token = _token();
|
||||
set_session('ss_'.$token_case.'_token', $token);
|
||||
die(json_encode(array('error'=>'', 'token'=>$token, 'url'=>'')));
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$is_member ||
|
||||
!isset($_POST['token_case']) || $_POST['token_case'] !== 'qa_write') {
|
||||
die(json_encode(array('error'=>'올바른 방법으로 이용해 주십시오.', 'token'=>'', 'url'=>'')));
|
||||
}
|
||||
|
||||
check_request_origin();
|
||||
$token = _token();
|
||||
set_session('ss_qa_write_token', $token);
|
||||
die(json_encode(array('error'=>'', 'token'=>$token, 'url'=>'')));
|
||||
|
||||
@@ -49,6 +49,23 @@ if($config['cf_memo_del'] > 0) {
|
||||
}
|
||||
}
|
||||
|
||||
// 메일 인증 기한이 지난 회원도 본인확인 정보를 해제하고 탈퇴 처리한다.
|
||||
// 재발송된 토큰의 발급 시각을 사용하며 실제 변경 직전에 회원 상태를 다시 확인한다.
|
||||
$email_certify_minutes = isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60;
|
||||
if ($config['cf_use_email_certify'] && $email_certify_minutes > 0) {
|
||||
$sql = " select mb_id, mb_email_certify, mb_email_certify2, mb_datetime, mb_leave_date, mb_memo
|
||||
from {$g5['member_table']}
|
||||
where mb_leave_date = ''
|
||||
and mb_email_certify not regexp '[1-9]'
|
||||
and mb_memo not regexp '^[0-9]{8}.*삭제함' ";
|
||||
$result = sql_query($sql);
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
if (is_expired_email_certify_member($row)) {
|
||||
g5_leave_member($row['mb_id'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 탈퇴회원 자동 삭제
|
||||
if($config['cf_leave_day'] > 0) {
|
||||
$sql = " select mb_id from {$g5['member_table']}
|
||||
@@ -79,4 +96,4 @@ if(isset($config['cf_optimize_date'])) {
|
||||
run_event('cf_optimize_date_update', $config);
|
||||
}
|
||||
|
||||
run_event('db_table_optimize_end', $config);
|
||||
run_event('db_table_optimize_end', $config);
|
||||
|
||||
+19
-7
@@ -6,8 +6,9 @@ if(function_exists('check_mail_bot')){ check_mail_bot($_SERVER['REMOTE_ADDR']);
|
||||
|
||||
$mb_id = isset($_GET['mb_id']) ? trim($_GET['mb_id']) : '';
|
||||
$mb_md5 = isset($_GET['mb_md5']) ? trim($_GET['mb_md5']) : '';
|
||||
$esc_mb_id = sql_real_escape_string($mb_id);
|
||||
|
||||
$sql = " select mb_id, mb_email_certify2, mb_leave_date, mb_intercept_date from {$g5['member_table']} where mb_id = '{$mb_id}' ";
|
||||
$sql = " select mb_id, mb_datetime, mb_email_certify2, mb_leave_date, mb_intercept_date from {$g5['member_table']} where mb_id = '{$esc_mb_id}' ";
|
||||
$row = sql_fetch($sql);
|
||||
if (!$row['mb_id'])
|
||||
alert('존재하는 회원이 아닙니다.', G5_URL);
|
||||
@@ -16,21 +17,32 @@ if ( $row['mb_leave_date'] || $row['mb_intercept_date'] ){
|
||||
alert('탈퇴 또는 차단된 회원입니다.', G5_URL);
|
||||
}
|
||||
|
||||
// 인증 링크는 한번만 처리가 되게 한다.
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '' where mb_id = '$mb_id' ");
|
||||
|
||||
if ($mb_md5)
|
||||
{
|
||||
if ($mb_md5 == $row['mb_email_certify2'])
|
||||
$valid_minutes = isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60;
|
||||
|
||||
if (is_valid_email_certify_token($mb_md5, $row['mb_email_certify2'], $row['mb_datetime'], $valid_minutes))
|
||||
{
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify = '".G5_TIME_YMDHIS."' where mb_id = '{$mb_id}' ");
|
||||
// 인증 링크는 한번만 처리되도록 인증과 동시에 토큰을 폐기한다.
|
||||
$esc_mb_md5 = sql_real_escape_string($mb_md5);
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify = '".G5_TIME_YMDHIS."', mb_email_certify2 = '' where mb_id = '{$esc_mb_id}' and mb_email_certify2 = '{$esc_mb_md5}' ");
|
||||
|
||||
if (get_sql_affected_rows() <= 0) {
|
||||
alert('이미 처리되었거나 올바르지 않은 메일인증 요청입니다.', G5_URL);
|
||||
}
|
||||
|
||||
alert("메일인증 처리를 완료 하였습니다.\\n\\n지금부터 {$mb_id} 아이디로 로그인 가능합니다.", G5_URL);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($mb_md5 === $row['mb_email_certify2']) {
|
||||
$esc_mb_md5 = sql_real_escape_string($mb_md5);
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '' where mb_id = '{$esc_mb_id}' and mb_email_certify2 = '{$esc_mb_md5}' ");
|
||||
alert('메일인증 유효시간이 만료되었습니다. 인증메일을 다시 요청해 주십시오.', G5_URL);
|
||||
}
|
||||
|
||||
alert('메일인증 요청 정보가 올바르지 않습니다.', G5_URL);
|
||||
}
|
||||
}
|
||||
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.', G5_URL);
|
||||
alert('제대로 된 값이 넘어오지 않았습니다.', G5_URL);
|
||||
|
||||
@@ -34,8 +34,8 @@ for ($i=1; $i<=$attach; $i++) {
|
||||
$file_key = 'file'.$i;
|
||||
if (isset($_FILES[$file_key]['name']) && $_FILES[$file_key]['name']) {
|
||||
$filename = get_safe_filename($_FILES[$file_key]['name']);
|
||||
if (is_disallowed_svg_filename($filename)) {
|
||||
alert_close('허용되지 않는 파일 확장자입니다. (svg, svgz)');
|
||||
if (is_disallowed_active_filename($filename)) {
|
||||
alert_close('허용되지 않는 파일 확장자입니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-12
@@ -12,12 +12,8 @@ $post_mb_password = isset($_POST['mb_password']) ? trim($_POST['mb_password']) :
|
||||
if (!($post_mb_password && check_password($post_mb_password, $member['mb_password'])))
|
||||
alert('비밀번호가 틀립니다.');
|
||||
|
||||
// 회원탈퇴일을 저장
|
||||
$date = date("Ymd");
|
||||
$sql = " update {$g5['member_table']} set mb_leave_date = '{$date}', mb_memo = '".date('Ymd', G5_SERVER_TIME)." 탈퇴함\n".sql_real_escape_string($member['mb_memo'])."', mb_certify = '', mb_adult = 0, mb_dupinfo = '' where mb_id = '{$member['mb_id']}' ";
|
||||
sql_query($sql);
|
||||
|
||||
run_event('member_leave', $member);
|
||||
if (!g5_leave_member($member['mb_id']))
|
||||
alert('회원탈퇴를 처리하지 못했습니다. 회원 상태를 확인해 주십시오.');
|
||||
|
||||
// 3.09 수정 (로그아웃)
|
||||
unset($_SESSION['ss_mb_id']);
|
||||
@@ -25,9 +21,4 @@ unset($_SESSION['ss_mb_id']);
|
||||
if (!$url)
|
||||
$url = G5_URL;
|
||||
|
||||
//소셜로그인 해제
|
||||
if(function_exists('social_member_link_delete')){
|
||||
social_member_link_delete($member['mb_id']);
|
||||
}
|
||||
|
||||
alert(''.$member['mb_nick'].'님께서는 '. date("Y년 m월 d일") .'에 회원에서 탈퇴 하셨습니다.', $url);
|
||||
alert(''.$member['mb_nick'].'님께서는 '. date("Y년 m월 d일") .'에 회원에서 탈퇴 하셨습니다.', $url);
|
||||
|
||||
@@ -20,6 +20,12 @@ if (!$email)
|
||||
// (이메일 열거 공격 방지)
|
||||
$generic_message = $email.' 메일로 회원아이디와 비밀번호를 인증할 수 있는 메일이 발송 되었습니다.\\n\\n메일을 확인하여 주십시오.';
|
||||
|
||||
$security_mail_url = g5_security_mail_base_url();
|
||||
if ($security_mail_url === false) {
|
||||
error_log('[g5 security mail] Valid G5_DOMAIN is required.');
|
||||
alert_close($generic_message);
|
||||
}
|
||||
|
||||
$sql = " select count(*) as cnt from {$g5['member_table']} where mb_email = '$email' ";
|
||||
$row = sql_fetch($sql);
|
||||
if ($row['cnt'] > 1) {
|
||||
@@ -48,7 +54,7 @@ $sql = " update {$g5['member_table']} set mb_lost_certify = '$mb_nonce $mb_lost_
|
||||
sql_query($sql);
|
||||
|
||||
// 인증 링크 생성
|
||||
$href = G5_BBS_URL.'/password_lost_certify.php?mb_no='.$mb['mb_no'].'&mb_nonce='.$mb_nonce;
|
||||
$href = $security_mail_url.'/'.G5_BBS_DIR.'/password_lost_certify.php?mb_no='.$mb['mb_no'].'&mb_nonce='.$mb_nonce;
|
||||
|
||||
$subject = "[".$config['cf_title']."] 요청하신 회원정보 찾기 안내 메일입니다.";
|
||||
|
||||
@@ -60,7 +66,7 @@ $content .= '<h1 style="padding:30px 30px 0;background:#f7f7f7;color:#555;font-s
|
||||
$content .= '회원정보 찾기 안내';
|
||||
$content .= '</h1>';
|
||||
$content .= '<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">';
|
||||
$content .= '<a href="'.G5_URL.'" target="_blank">'.$config['cf_title'].'</a>';
|
||||
$content .= '<a href="'.htmlspecialchars($security_mail_url, ENT_QUOTES, 'UTF-8').'" target="_blank">'.$config['cf_title'].'</a>';
|
||||
$content .= '</span>';
|
||||
$content .= '<p style="margin:20px 0 0;padding:30px 30px 30px;border-bottom:1px solid #eee;line-height:1.7em">';
|
||||
$content .= addslashes($mb['mb_name'])." (".addslashes($mb['mb_nick']).")"." 회원님은 ".G5_TIME_YMDHIS." 에 회원정보 찾기 요청을 하셨습니다.<br>";
|
||||
|
||||
@@ -136,8 +136,8 @@ if($w == 'u' || $w == 'a' || $w == 'r') {
|
||||
if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) {
|
||||
foreach ($_FILES['bf_file']['name'] as $filename) {
|
||||
$filename = get_safe_filename($filename);
|
||||
if (is_disallowed_svg_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)');
|
||||
if (is_disallowed_active_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,17 +40,19 @@ if ($row['cnt']) {
|
||||
alert("{$mb_email} 메일은 이미 존재하는 메일주소 입니다.\\n\\n다른 메일주소를 입력해 주십시오.");
|
||||
}
|
||||
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
|
||||
// 인증메일 발송
|
||||
$subject = '['.$config['cf_title'].'] 인증확인 메일입니다.';
|
||||
|
||||
$mb_name = $mb['mb_name'];
|
||||
|
||||
// 어떠한 회원정보도 포함되지 않은 일회용 난수를 생성하여 인증에 사용 (CSPRNG 사용)
|
||||
$mb_md5 = get_random_token_string(16);
|
||||
$mb_md5 = get_email_certify_token();
|
||||
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '{$esc_mb_id}' ");
|
||||
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
|
||||
ob_start();
|
||||
include_once ('./register_form_update_mail3.php');
|
||||
@@ -62,4 +64,4 @@ mailer($config['cf_admin_email_name'], $config['cf_admin_email'], $mb_email, $su
|
||||
$sql = " update {$g5['member_table']} set mb_email = '$mb_email' where mb_id = '{$esc_mb_id}' ";
|
||||
sql_query($sql);
|
||||
|
||||
alert("인증메일을 {$mb_email} 메일로 다시 보내 드렸습니다.\\n\\n잠시후 {$mb_email} 메일을 확인하여 주십시오.", G5_URL);
|
||||
alert("인증메일을 {$mb_email} 메일로 다시 보내 드렸습니다.\\n\\n잠시후 {$mb_email} 메일을 확인하여 주십시오.", G5_URL);
|
||||
|
||||
@@ -65,6 +65,10 @@ $mb_9 = isset($_POST['mb_9']) ? trim($_POST['mb_9'])
|
||||
$mb_10 = isset($_POST['mb_10']) ? trim($_POST['mb_10']) : "";
|
||||
$mb_name = addslashes(clean_xss_tags(stripslashes($mb_name), 1, 1));
|
||||
$mb_email = get_email_address($mb_email);
|
||||
$security_mail_url = g5_security_mail_base_url();
|
||||
if ($config['cf_use_email_certify'] && ($w == '' || $member['mb_email'] != $mb_email)) {
|
||||
$security_mail_url = g5_require_security_mail_url();
|
||||
}
|
||||
$mb_homepage = addslashes(clean_xss_tags(stripslashes($mb_homepage), 1, 1));
|
||||
$mb_tel = addslashes(clean_xss_tags(stripslashes($mb_tel), 1, 1));
|
||||
$mb_zip1 = preg_replace('/[^0-9]/', '', $mb_zip1);
|
||||
@@ -325,9 +329,9 @@ if ($w == '') {
|
||||
|
||||
// 어떠한 회원정보도 포함되지 않은 일회용 난수를 생성하여 인증에 사용 (CSPRNG 사용)
|
||||
if ($config['cf_use_email_certify']) {
|
||||
$mb_md5 = get_random_token_string(16);
|
||||
$mb_md5 = get_email_certify_token();
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '$mb_id' ");
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
@@ -609,11 +613,11 @@ if ($config['cf_use_email_certify'] && $old_email != $mb_email) {
|
||||
$subject = '['.$config['cf_title'].'] 인증확인 메일입니다.';
|
||||
|
||||
// 어떠한 회원정보도 포함되지 않은 일회용 난수를 생성하여 인증에 사용 (CSPRNG 사용)
|
||||
$mb_md5 = get_random_token_string(16);
|
||||
$mb_md5 = get_email_certify_token();
|
||||
|
||||
sql_query(" update {$g5['member_table']} set mb_email_certify2 = '$mb_md5' where mb_id = '$mb_id' ");
|
||||
|
||||
$certify_href = G5_BBS_URL.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
$certify_href = $security_mail_url.'/'.G5_BBS_DIR.'/email_certify.php?mb_id='.$mb_id.'&mb_md5='.$mb_md5;
|
||||
|
||||
ob_start();
|
||||
include_once ('./register_form_update_mail3.php');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// 회원가입축하 메일 (회원님께 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,19 +19,24 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원가입을 축하합니다.
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<b><?php echo $mb_name ?></b> 님의 회원가입을 진심으로 축하합니다.<br>
|
||||
회원님의 성원에 보답하고자 더욱 더 열심히 하겠습니다.<br>
|
||||
<?php if ($config['cf_use_email_certify']) { ?>아래의 <strong>메일인증</strong>을 클릭하시면 회원가입이 완료됩니다.<br><?php } ?>
|
||||
<?php if ($config['cf_use_email_certify']) { ?>아래의 <strong>메일인증</strong>을 클릭하시면 회원가입이 완료됩니다.<br>
|
||||
<?php if (!empty($config['cf_email_certify_minutes'])) { ?>인증 링크는 발송 후 <?php echo (int) $config['cf_email_certify_minutes']; ?>분 동안 유효합니다.<br><?php } ?><?php } ?>
|
||||
감사합니다.
|
||||
</p>
|
||||
|
||||
<?php if ($config['cf_use_email_certify']) { ?>
|
||||
<a href="<?php echo $certify_href ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">메일인증</a>
|
||||
<?php } else { ?>
|
||||
<a href="<?php echo G5_URL ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">사이트바로가기</a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank" style="display:block;padding:30px 0;background:#484848;color:#fff;text-decoration:none;text-align:center">사이트바로가기</a>
|
||||
<?php } ?>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// 회원가입 메일 (관리자 메일로 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,7 +19,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원가입 알림 메일
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<b><?php echo $mb_name ?></b> 님께서 회원가입 하셨습니다.<br>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
// E-mail 수정시 인증 메일 (회원님께 발송)
|
||||
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
$mail_site_url = g5_security_mail_base_url();
|
||||
?>
|
||||
|
||||
<!doctype html>
|
||||
@@ -18,7 +19,9 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
회원 인증 메일입니다.
|
||||
</h1>
|
||||
<span style="display:block;padding:10px 30px 30px;background:#f7f7f7;text-align:right">
|
||||
<a href="<?php echo G5_URL ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php if ($mail_site_url) { ?>
|
||||
<a href="<?php echo htmlspecialchars($mail_site_url, ENT_QUOTES, 'UTF-8') ?>" target="_blank"><?php echo $config['cf_title'] ?></a>
|
||||
<?php } else { echo $config['cf_title']; } ?>
|
||||
</span>
|
||||
<p style="margin:20px 0 0;padding:30px 30px 50px;min-height:200px;height:auto !important;height:200px;border-bottom:1px solid #eee">
|
||||
<?php if($w == 'u') { ?>
|
||||
@@ -26,6 +29,7 @@ if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
|
||||
<?php } ?>
|
||||
|
||||
아래의 주소를 클릭하시면 인증이 완료됩니다.<br>
|
||||
<?php if (!empty($config['cf_email_certify_minutes'])) { ?>인증 링크는 발송 후 <?php echo (int) $config['cf_email_certify_minutes']; ?>분 동안 유효합니다.<br><?php } ?>
|
||||
<a href="<?php echo $certify_href ?>" target="_blank"><b><?php echo $certify_href ?></b></a><br><br>
|
||||
|
||||
회원님의 성원에 보답하고자 더욱 더 열심히 하겠습니다.<br>
|
||||
|
||||
@@ -309,12 +309,15 @@ if ($w == '') {
|
||||
$password_required = 'required';
|
||||
} else if ($w == 'u') {
|
||||
$password_required = '';
|
||||
set_session(g5_write_edit_auth_key($bo_table, $wr_id), '');
|
||||
|
||||
if (!$is_admin) {
|
||||
if (!($is_member && $member['mb_id'] === $write['mb_id'])) {
|
||||
if (!check_password($wr_password, $write['wr_password'])) {
|
||||
$is_wrong = run_replace('invalid_password', false, 'write', $write);
|
||||
if(!$is_wrong) alert('비밀번호가 틀립니다.');
|
||||
} else if (empty($write['mb_id'])) {
|
||||
g5_grant_write_edit_auth($bo_table, $write);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -1,9 +1,14 @@
|
||||
<?php
|
||||
include_once('./_common.php');
|
||||
|
||||
if(!$bo_table)
|
||||
// 그누보드4 자료 이전은 최고관리자에게만 허용되는 별도 용도이다.
|
||||
$is_import_token = $bo_table === 'g4_import' && $is_admin === 'super';
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST' || !$bo_table ||
|
||||
(!$is_import_token && (empty($board['bo_table']) || $board['bo_table'] !== $bo_table)))
|
||||
die(json_encode(array('error'=>'게시판 정보가 올바르지 않습니다.', 'url'=>G5_URL)));
|
||||
|
||||
check_request_origin();
|
||||
|
||||
set_session('ss_write_'.$bo_table.'_token', '');
|
||||
|
||||
$token = get_write_token($bo_table);
|
||||
|
||||
+38
-31
@@ -29,7 +29,7 @@ if($board['bo_use_category']) {
|
||||
|
||||
$wr_subject = '';
|
||||
if (isset($_POST['wr_subject'])) {
|
||||
$wr_subject = substr(trim($_POST['wr_subject']),0,255);
|
||||
$wr_subject = cut_str(trim($_POST['wr_subject']), 255, '');
|
||||
$wr_subject = preg_replace("#[\\\]+$#", "", $wr_subject);
|
||||
if (function_exists('normalize_utf8_string')) {
|
||||
$wr_subject = normalize_utf8_string($wr_subject);
|
||||
@@ -96,6 +96,39 @@ if ($w == 'u' || $w == 'r') {
|
||||
}
|
||||
}
|
||||
|
||||
// 수정 권한은 스킨 및 저장 처리 전에 검증한다.
|
||||
if ($w == 'u') {
|
||||
if (($is_member || $is_admin) && (get_session('ss_bo_table') != $bo_table || get_session('ss_wr_id') != $wr_id)) {
|
||||
alert('올바른 방법으로 수정하여 주십시오.', get_pretty_url($bo_table));
|
||||
}
|
||||
|
||||
$return_url = get_pretty_url($bo_table, $wr_id);
|
||||
|
||||
if ($is_admin == 'super') // 최고관리자 통과
|
||||
;
|
||||
else if ($is_admin == 'group') { // 그룹관리자
|
||||
$mb = get_member($wr['mb_id']);
|
||||
if ($member['mb_id'] != $group['gr_admin']) // 자신이 관리하는 그룹인가?
|
||||
alert('자신이 관리하는 그룹의 게시판이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
|
||||
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
|
||||
} else if ($is_admin == 'board') { // 게시판관리자이면
|
||||
$mb = get_member($wr['mb_id']);
|
||||
if ($member['mb_id'] != $board['bo_admin']) // 자신이 관리하는 게시판인가?
|
||||
alert('자신이 관리하는 게시판이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
|
||||
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
|
||||
} else if ($member['mb_id']) {
|
||||
if ($member['mb_id'] != $wr['mb_id'])
|
||||
alert('자신의 글이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
} else {
|
||||
if (!g5_has_write_edit_auth($bo_table, $wr))
|
||||
alert('비밀번호 확인 후 다시 수정하여 주십시오.', $return_url);
|
||||
if ($wr['mb_id'])
|
||||
alert('로그인 후 수정하세요.', G5_BBS_URL.'/login.php?url='.urlencode($return_url));
|
||||
}
|
||||
}
|
||||
|
||||
// 외부에서 글을 등록할 수 있는 버그가 존재하므로 비밀글은 사용일 경우에만 가능해야 함
|
||||
if (!$is_admin && !$board['bo_use_secret'] && (stripos($_POST['html'], 'secret') !== false || stripos($_POST['secret'], 'secret') !== false || stripos($_POST['mail'], 'secret') !== false)) {
|
||||
alert('비밀글 미사용 게시판 이므로 비밀글로 등록할 수 없습니다.');
|
||||
@@ -140,8 +173,8 @@ for ($i=1; $i<=10; $i++) {
|
||||
if (isset($_FILES['bf_file']['name']) && is_array($_FILES['bf_file']['name'])) {
|
||||
foreach ($_FILES['bf_file']['name'] as $filename) {
|
||||
$filename = get_safe_filename($filename);
|
||||
if (is_disallowed_svg_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다. (svg, svgz)');
|
||||
if (is_disallowed_active_filename($filename)) {
|
||||
alert('허용되지 않는 파일 확장자입니다.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -335,34 +368,6 @@ if ($w == '' || $w == 'r') {
|
||||
insert_point($member['mb_id'], $board['bo_comment_point'], "{$board['bo_subject']} {$wr_id} 글답변", $bo_table, $wr_id, '쓰기');
|
||||
}
|
||||
} else if ($w == 'u') {
|
||||
if (get_session('ss_bo_table') != $_POST['bo_table'] || get_session('ss_wr_id') != $_POST['wr_id']) {
|
||||
alert('올바른 방법으로 수정하여 주십시오.', get_pretty_url($bo_table));
|
||||
}
|
||||
|
||||
$return_url = get_pretty_url($bo_table, $wr_id);
|
||||
|
||||
if ($is_admin == 'super') // 최고관리자 통과
|
||||
;
|
||||
else if ($is_admin == 'group') { // 그룹관리자
|
||||
$mb = get_member($write['mb_id']);
|
||||
if ($member['mb_id'] != $group['gr_admin']) // 자신이 관리하는 그룹인가?
|
||||
alert('자신이 관리하는 그룹의 게시판이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
|
||||
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
|
||||
} else if ($is_admin == 'board') { // 게시판관리자이면
|
||||
$mb = get_member($write['mb_id']);
|
||||
if ($member['mb_id'] != $board['bo_admin']) // 자신이 관리하는 게시판인가?
|
||||
alert('자신이 관리하는 게시판이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
else if ($member['mb_level'] < $mb['mb_level']) // 자신의 레벨이 크거나 같다면 통과
|
||||
alert('자신의 권한보다 높은 권한의 회원이 작성한 글은 수정할 수 없습니다.', $return_url);
|
||||
} else if ($member['mb_id']) {
|
||||
if ($member['mb_id'] != $write['mb_id'])
|
||||
alert('자신의 글이 아니므로 수정할 수 없습니다.', $return_url);
|
||||
} else {
|
||||
if ($write['mb_id'])
|
||||
alert('로그인 후 수정하세요.', G5_BBS_URL.'/login.php?url='.urlencode($return_url));
|
||||
}
|
||||
|
||||
if ($member['mb_id']) {
|
||||
// 자신의 글이라면
|
||||
if ($member['mb_id'] === $wr['mb_id']) {
|
||||
@@ -428,6 +433,8 @@ if ($w == '' || $w == 'r') {
|
||||
where wr_id = '{$wr['wr_id']}' ";
|
||||
sql_query($sql);
|
||||
|
||||
set_session(g5_write_edit_auth_key($bo_table, $wr_id), '');
|
||||
|
||||
// 분류가 수정되는 경우 해당되는 코멘트의 분류명도 모두 수정함
|
||||
// 코멘트의 분류를 수정하지 않으면 검색이 제대로 되지 않음
|
||||
$sql = " update {$write_table} set ca_name = '{$ca_name}' where wr_parent = '{$wr['wr_id']}' ";
|
||||
|
||||
+8
-3
@@ -334,7 +334,6 @@ if( !function_exists('shop_check_is_pay_page') ){
|
||||
$mobile_dir.'/'.$shop_dir.'/lg/returnurl.php',
|
||||
$mobile_dir.'/'.$shop_dir.'/lg/xpay_approval.php',
|
||||
$mobile_dir.'/'.$shop_dir.'/kcp/order_approval_form.php',
|
||||
$shop_dir.'/kakaopay/inicis_kk_return.php', // 이니시스 카카오페이 (SIRK 로 시작하는 아이디 전용)
|
||||
$plugin_dir."/inicert/ini_result.php", // 이니시스 간편인증 모듈 2021-09-10 http <-> https 간 세션 공유 문제로 인해 추가
|
||||
$plugin_dir."/inicert/ini_find_result.php", // 이니시스 간편인증 모듈 2021-09-10 http <-> https 간 세션 공유 문제로 인해 추가
|
||||
);
|
||||
@@ -355,7 +354,11 @@ if( !function_exists('shop_check_is_pay_page') ){
|
||||
// PG 결제시에 세션이 없으면 내 호출페이지를 다시 호출하여 쿠키 PHPSESSID를 살려내어 세션값을 정상적으로 불러오게 합니다.
|
||||
// 위와 같이 코드를 전부 한페이지에 넣은 이유는 이전 버전 사용자들이 패치시 어려울수 있으므로 한페이지에 코드를 다 넣었습니다.
|
||||
if(XenoPostToForm::check()) {
|
||||
if ( shop_check_is_pay_page() ){ // PG 결제 리턴페이지에서만 사용
|
||||
// 토큰 복귀는 실제 엔드포인트에서 DB 해시를 검증한다. 여기서는 쿠키 재전송만 생략한다.
|
||||
$g5_order_token_return = isset($_REQUEST['g5_order_state']) && is_string($_REQUEST['g5_order_state']) &&
|
||||
preg_match('/\A[0-9]{1,20}\.[a-f0-9]{64}\z/D', $_REQUEST['g5_order_state']) &&
|
||||
preg_match('~/mobile/shop/kcp/order_approval_form\.php$~', str_replace('\\', '/', $_SERVER['SCRIPT_NAME']));
|
||||
if ( shop_check_is_pay_page() && !$g5_order_token_return ){ // PG 결제 리턴페이지에서만 사용
|
||||
XenoPostToForm::submit($_POST); // session_start(); 하기 전에
|
||||
}
|
||||
}
|
||||
@@ -831,7 +834,9 @@ if (G5_IS_MOBILE) {
|
||||
|
||||
|
||||
// 방문자수의 접속을 남김
|
||||
include_once(G5_BBS_PATH.'/visit_insert.inc.php');
|
||||
if (!defined('G5_IS_CLI') || !G5_IS_CLI) {
|
||||
include_once(G5_BBS_PATH.'/visit_insert.inc.php');
|
||||
}
|
||||
|
||||
|
||||
// 일정 기간이 지난 DB 데이터 삭제 및 최적화
|
||||
|
||||
+59
-5
@@ -816,11 +816,17 @@ box-shadow: 1px 2px 2px #eee;}
|
||||
#sod_frm_paysel .samsungpay_icon{background:url('../img/samsungpay.png') no-repeat 50% 50% #fff;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .ssgpay_icon{background:url('../img/ssgpay_icon.png') no-repeat 50% 50% #fff;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .skpay_icon{background:url('../img/skpay11_icon.png') no-repeat 50% 50% #fff; background-size: 70px;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .bank_icon{background:url('../img/pay_icon1.png') no-repeat 15px 50% #fff}
|
||||
#sod_frm_paysel .vbank_icon{background:url('../img/pay_icon2.png') no-repeat 15px 50% #fff;padding-top:13px}
|
||||
#sod_frm_paysel .iche_icon{background:url('../img/pay_icon2.png') no-repeat 15px 50% #fff;padding-top:13px}
|
||||
#sod_frm_paysel .hp_icon{background:url('../img/pay_icon3.png') no-repeat 24px 50% #fff}
|
||||
#sod_frm_paysel .card_icon{background:url('../img/pay_icon4.png') no-repeat 17px 50% #fff}
|
||||
/* 일반 결제수단은 아이콘 없이 텍스트를 표시한다. */
|
||||
#sod_frm_paysel .lb_icon.bank_icon,
|
||||
#sod_frm_paysel .lb_icon.vbank_icon,
|
||||
#sod_frm_paysel .lb_icon.iche_icon,
|
||||
#sod_frm_paysel .lb_icon.hp_icon,
|
||||
#sod_frm_paysel .lb_icon.card_icon{background-image:none;padding:0 12px;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;line-height:18px}
|
||||
#m_sod_frm_paysel .lb_icon.bank_icon,
|
||||
#m_sod_frm_paysel .lb_icon.vbank_icon,
|
||||
#m_sod_frm_paysel .lb_icon.iche_icon,
|
||||
#m_sod_frm_paysel .lb_icon.hp_icon,
|
||||
#m_sod_frm_paysel .lb_icon.card_icon{background-image:none;display:inline-block;padding:0;min-height:24px;line-height:24px;vertical-align:middle}
|
||||
#forderform .sod_right #sod_bsk_tot {border:1px solid #e3e5e8}
|
||||
#forderform .od_pay_buttons_el{margin-top:20px;border:1px solid #e3e5e8;background:#fff;padding:10px}
|
||||
#forderform .od_pay_buttons_el h3{font-size:1.167em;margin:0px 0 5px}
|
||||
@@ -1280,3 +1286,51 @@ html.no-overflowscrolling #sc_coupon_frm, html.no-overflowscrolling #od_coupon_f
|
||||
.chk_box input[type="radio"]:checked + label {color:#305af9}
|
||||
.chk_box input[type="radio"]:checked + label span:before {width:7px;height:7px;background:#305af9;content:'';position:absolute;top:3px;left:3px;border-radius:50%}
|
||||
|
||||
|
||||
/* 간편결제 로고: 작은 원본은 확대하지 않고 큰 로고만 축소한다. */
|
||||
#sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon){background-image:url('../img/payco.png');background-size:59px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon){background-image:url('../img/paynow.png');background-size:48px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon){background-image:url('../img/lpay_logo.png');background-size:35px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon){background-image:url('../img/kakao.png');background-size:43px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon){background-image:url('../img/ico-default-naverpay.png');background-size:42px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon){background-image:url('../img/samsungpay.png');background-size:72px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon){background-image:url('../img/ssgpay_icon.png');background-size:64px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon){background-image:url('../img/skpay11_icon.png');background-size:46px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon){background-image:url('../img/ico-mobile-applepay.png');background-size:32px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon){background-image:url('../img/tosspay-logo.svg');background-size:72px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon){background-image:url('../img/pinpay-logo.png');background-size:60px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#m_sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon){width:96px;height:28px;min-height:28px;margin:0;padding:0;border-radius:0;display:inline-block;vertical-align:middle}
|
||||
#sod_frm_paysel .lb_icon.inicis_kakaopay em{display:none}
|
||||
|
||||
+59
-5
@@ -919,11 +919,17 @@ box-shadow:0 0 8px rgba(65,98,255,0.8)}
|
||||
#sod_frm_paysel .samsungpay_icon{background:url('../img/samsungpay.png') no-repeat 50% 50% #fff;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .ssgpay_icon{background:url('../img/ssgpay_icon.png') no-repeat 50% 50% #fff;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .skpay_icon{background:url('../img/skpay11_icon.png') no-repeat 50% 50% #fff; background-size: 70px;display:inline-block;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .bank_icon{background:url('../img/pay_icon1.png') no-repeat 15px 50% #fff}
|
||||
#sod_frm_paysel .vbank_icon{background:url('../img/pay_icon2.png') no-repeat 15px 50% #fff;padding-top:13px}
|
||||
#sod_frm_paysel .iche_icon{background:url('../img/pay_icon2.png') no-repeat 15px 50% #fff;padding-top:13px}
|
||||
#sod_frm_paysel .hp_icon{background:url('../img/pay_icon3.png') no-repeat 24px 50% #fff}
|
||||
#sod_frm_paysel .card_icon{background:url('../img/pay_icon4.png') no-repeat 17px 50% #fff}
|
||||
/* 일반 결제수단은 아이콘 없이 텍스트를 표시한다. */
|
||||
#sod_frm_paysel .lb_icon.bank_icon,
|
||||
#sod_frm_paysel .lb_icon.vbank_icon,
|
||||
#sod_frm_paysel .lb_icon.iche_icon,
|
||||
#sod_frm_paysel .lb_icon.hp_icon,
|
||||
#sod_frm_paysel .lb_icon.card_icon{background-image:none;padding:0 12px;display:inline-flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;line-height:18px}
|
||||
#m_sod_frm_paysel .lb_icon.bank_icon,
|
||||
#m_sod_frm_paysel .lb_icon.vbank_icon,
|
||||
#m_sod_frm_paysel .lb_icon.iche_icon,
|
||||
#m_sod_frm_paysel .lb_icon.hp_icon,
|
||||
#m_sod_frm_paysel .lb_icon.card_icon{background-image:none;display:inline-block;padding:0;min-height:24px;line-height:24px;vertical-align:middle}
|
||||
#forderform .sod_right #sod_bsk_tot {border:1px solid #e3e5e8}
|
||||
#forderform .od_pay_buttons_el{margin-top:20px;border:1px solid #e3e5e8;background:#fff;padding:10px}
|
||||
#forderform .od_pay_buttons_el h3{font-size:1.167em;margin:0px 0 5px}
|
||||
@@ -1038,3 +1044,51 @@ box-shadow:0 0 8px rgba(65,98,255,0.8)}
|
||||
.chk_box input[type="radio"]:checked + label {color:#305af9}
|
||||
.chk_box input[type="radio"]:checked + label span:before {width:7px;height:7px;background:#305af9;content:'';position:absolute;top:3px;left:3px;border-radius:50%}
|
||||
|
||||
|
||||
/* 간편결제 로고: 작은 원본은 확대하지 않고 큰 로고만 축소한다. */
|
||||
#sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon){background-image:url('../img/payco.png');background-size:59px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon){background-image:url('../img/paynow.png');background-size:48px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon){background-image:url('../img/lpay_logo.png');background-size:35px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon){background-image:url('../img/kakao.png');background-size:43px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon){background-image:url('../img/ico-default-naverpay.png');background-size:42px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon){background-image:url('../img/samsungpay.png');background-size:72px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon){background-image:url('../img/ssgpay_icon.png');background-size:64px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon){background-image:url('../img/skpay11_icon.png');background-size:46px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon){background-image:url('../img/ico-mobile-applepay.png');background-size:32px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon){background-image:url('../img/tosspay-logo.svg');background-size:72px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon){background-image:url('../img/pinpay-logo.png');background-size:60px auto;background-repeat:no-repeat;background-position:50% 50%;background-color:#fff;overflow:hidden;text-indent:-999px}
|
||||
#m_sod_frm_paysel .lb_icon.PAYCO:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.paycopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.PAYNOW:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.lpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_lpay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.kakaopay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.inicis_kakaopay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.naverpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsungpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.samsung_pay:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.ssgpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.skpay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.applepay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.tosspay_icon:not(.nhnkcp_icon),
|
||||
#m_sod_frm_paysel .lb_icon.pinpay_icon:not(.nhnkcp_icon){width:96px;height:28px;min-height:28px;margin:0;padding:0;border-radius:0;display:inline-block;vertical-align:middle}
|
||||
#sod_frm_paysel .lb_icon.inicis_kakaopay em{display:none}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# 과거 데이터베이스 패치 조사
|
||||
|
||||
일반 실행 경로에서 제거한 DDL과 전체 Git 이력을 대조했다. 최초 공개 스키마에 이미 포함된 2012~2013년 관리 화면 보정 코드는 기준선으로 분류하고, 별도 업그레이드 진입점에 추가된 2014년 이후 변경은 도입 커밋과 날짜별 마이그레이션으로 옮겼다.
|
||||
|
||||
| 도입일 | 원본 커밋 | 마이그레이션 | 주요 변경 |
|
||||
|---|---|---|---|
|
||||
| 2014-03-31 | `3889e0235` | `20140331_001_shop_cart_shipping` | 장바구니 배송비 필드 |
|
||||
| 2014-11-05 | `de2b73436` | `20141105_001_shop_cart_select_time` | 장바구니 선택 시각 |
|
||||
| 2015-03-06 | `344b1983a` | `20150306_001_shop_cart_indexes` | 장바구니 조회 인덱스 |
|
||||
| 2015-05-29 | `a7cfae126` | `20150529_001_inicis_mobile_tables` | 모바일 이니시스·주문 임시 테이블 |
|
||||
| 2015-09-08 | `6495aa007` | `20150908_001_inicis_auth_number` | 이니시스 승인번호 |
|
||||
| 2016-04-22 | `4c9ac8b6b` | `20160422_001_shop_test_order` | 테스트 주문 구분 |
|
||||
| 2016-04-27 | `f1cd96dc3` | `20160427_001_shop_order_data_cart` | 미완료 주문 장바구니 정보 |
|
||||
| 2016-09-09 | `6e1aa4ac0` | `20160909_001_shop_coupon_zone` | 쿠폰존 |
|
||||
| 2017-09-21 | `db7e07da2` | `20170921_001_inicis_notification_data` | 이니시스 통지 원문 |
|
||||
| 2018-03-30 | `f7ac06d7d` | `20180330_001_social_login` | 소셜 로그인 설정·프로필 |
|
||||
| 2019-12-02 | `22aad37bf` | `20191202_001`, `20191202_002` | 5.4 SEO·메모·첨부 스키마 |
|
||||
| 2019-12-23 | `54dc82a5d`, `ffc43056b` | `20191223_001_gnuboard_5413_schema` | 주문 로그·스크랩·짧은 URL |
|
||||
| 2023-09-09 | `28bfcea9c` | `20230909_001_auth_menu_length` | 관리 메뉴 ID 길이 |
|
||||
| 2024-04-02 | `1e72d7e81` | `20240402_001_primary_keys` | 단일 행 테이블 등의 기본키 |
|
||||
| 2025-08-19 | `cb1fadba4` | `20250819_001_sms_table_prefix` | SMS5 테이블 접두어 |
|
||||
| 2025-09-04 | `66f6a75a1`, `7e8eff539` | `20250904_001_marketing_consent` | 제거되지 않고 유지된 마케팅 동의 필드 |
|
||||
| 2026-03-25 | `5054e2463` | `20260325_001_coupon_use_unique` | 쿠폰 중복 사용 방지 인덱스 |
|
||||
| 2026-04-16 | `4d5c59766` | `20260416_001_member_auto_login` | 자동 로그인 토큰 테이블 |
|
||||
| 2026-06-25 | `0301c45f5` | `20260625_001_order_cancel_log` | 주문 취소·환불 이력 |
|
||||
| 2026-07-23 | `acdbcfd8b` | `20260723_001_inicis_pro` | INIpay PRO 설정·결제 이력 |
|
||||
| 2026-09-03 | `530140902` | `20260904_001_add_email_certify_minutes` | 메일 인증 유효시간 |
|
||||
|
||||
2012~2013년에 `config_form.php`, `board_form.php`, `member_form.php`, `qa_config.php` 등에서 수행하던 보정은 그누보드5 최초 배포 스키마를 형성하는 과정이었다. 이 항목들은 위 연속 패치보다 앞선 기준선이며 현재 `install/gnuboard5.sql`과 `install/gnuboard5shop.sql`에 포함된다. 제거 후 설치 스키마에서 누락된 필드는 별도로 대조해 보완했다.
|
||||
|
||||
팝빌 알림톡 테이블·설정과 게시판 알림 필드는 `7e8eff539`에서 기능과 함께 제거됐으므로 현재 목표 스키마에 다시 만들지 않는다. 반면 같은 도입 커밋의 마케팅 동의 필드는 유지돼 별도 마이그레이션으로 남겼다. 데이터 삭제가 필요한 쿠폰 중복 정리는 자동 수행하지 않으며, 중복이 있으면 고유 인덱스 생성 실패를 그대로 보고해 운영자가 데이터를 검토하도록 한다.
|
||||
|
||||
초기 이전 작업에서 확인된 복합 `ALTER TABLE`의 부분 적용 가능성과 누락된 데이터 보정은 `20260904_002_reconcile_partial_schema`, `20260904_003_backfill_legacy_data`에서 보완한다. 두 마이그레이션은 누락 컬럼과 인덱스를 개별 검사하고, 장바구니 배송비·회원 이미지 기본값·콘텐츠 SEO 데이터를 보정하며 SEO 컬럼 길이를 신규 설치 정의와 일치시킨다.
|
||||
@@ -0,0 +1,79 @@
|
||||
# 데이터베이스 마이그레이션
|
||||
|
||||
데이터베이스 스키마 변경은 설정·목록·상세 화면이나 공통 라이브러리의 일반 실행 경로에서 수행하지 않는다. 신규 설치의 최종 스키마는 `install/gnuboard5.sql`에 반영하고, 기존 설치 변경은 `migrations` 디렉터리의 버전형 SQL 파일로 관리한다.
|
||||
|
||||
## 신규 설치 이력
|
||||
|
||||
기존 테이블이 없는 신규 설치는 최신 스키마와 기본 데이터를 생성한 뒤, `data/dbconfig.php`를 만들기 전에 배포본의 모든 마이그레이션 ID·설명·체크섬을 성공 이력으로 등록한다. 적용 시각은 설치 시각이고 실행시간은 0이다. 과거 마이그레이션 SQL은 실행하지 않으며, 쇼핑몰 설치 선택 여부와 관계없이 해당 배포본을 기준선으로 기록한다. 설치 직후 DB 업그레이드에서 별도의 기존 상태 확인 이력 등록이 필요하지 않다.
|
||||
|
||||
마이그레이션 디렉터리가 비어 있거나 파일 읽기 실패 또는 이력 저장 실패 시 설치 완료를 중단한다. 해당 접두어의 기존 테이블이 있거나 기존 DB를 유지·재설치하는 경로는 일괄 완료로 기록하지 않는다. 남아 있는 쇼핑몰·SMS 등의 구버전 테이블을 완료로 오인하지 않도록 DB 업그레이드에서 실제 상태를 확인한다.
|
||||
|
||||
`php tests/migration_install_history.php /path/to/isolated/mysql.sock`은 root/빈 비밀번호인 격리 MySQL에 임시 DB를 생성하여 사용자 지정 접두어, 쇼핑몰 선택 여부, 설치 이력과 체크섬, 재실행 방지, 이력 저장 실패 및 잘못된 이력 테이블의 1054 오류와 실행 차단을 검증한다. 검사 종료 시 임시 DB를 삭제한다. `python3 tests/migration_install_flow.py /path/to/isolated/mysql.sock`은 임시 설치 디렉터리에서 실제 설치 코드를 실행하여 일반·쇼핑몰 신규 설치, 기존 테이블 보존과 마이그레이션 파일이 없을 때 설정 파일 생성 차단을 검증한다.
|
||||
|
||||
## 파일 규칙
|
||||
|
||||
- 파일명은 `YYYYMMDD_NNN_설명.sql` 형식을 사용한다.
|
||||
- 파일명은 한 번 배포한 뒤 변경하거나 재사용하지 않는다.
|
||||
- `-- @description`에 관리자에게 표시할 설명을 작성한다.
|
||||
- 각 SQL 문 바로 앞에 `-- @if-table-missing`, `-- @if-table-exists`, `-- @if-column-missing`, `-- @if-index-missing` 조건을 둘 수 있다. 조건이 여러 개면 모두 만족할 때만 실행한다.
|
||||
- 게시판별 글 테이블은 `-- @foreach-write-table-if-column-missing column_name`과 `{{write_table}}`을 사용한다.
|
||||
- SQL에는 실제 접두어 대신 실행기가 지원하는 테이블 자리표시자를 사용한다.
|
||||
- 한 파일은 가능한 한 하나의 목적만 포함한다.
|
||||
|
||||
자리표시자는 `$g5`에 등록된 `*_table` 키를 `{{config_table}}` 형식으로 사용한다. 실행기가 실제 테이블명으로 치환하며, 치환되지 않은 자리표시자는 오류로 처리한다.
|
||||
|
||||
## 실행 방법
|
||||
|
||||
공식 배포본에서는 관리자 화면으로 실행한다. `bin/`은 Git 추적 및 배포 대상에서 제외한 로컬 운영·검증 스크립트 디렉터리이므로 아래 검증 스크립트 설명은 해당 파일을 별도로 보유한 개발 환경에만 적용한다.
|
||||
|
||||
웹에서는 최고관리자가 **환경설정 → DB업그레이드**에서 전체 또는 특정 마이그레이션을 명시적으로 실행할 수 있다. 성공한 마이그레이션의 개별 실행 버튼은 비활성화되며, 전체 실행에서도 성공 이력과 체크섬이 일치하는 항목은 건너뛴다. 화면 조회만으로는 DDL이 실행되지 않는다.
|
||||
|
||||
기존 설치본처럼 이력이 비어 있으면 상태 조회 시 실제 실행 조건을 읽기 전용으로 검사한다. 일부 이력이 있는 경우에도 미기록 항목은 검사한다. 현재 조건에서 모든 SQL을 건너뛰는 항목은 `실행 불필요 (미기록)`로 표시하며 개별 실행 버튼을 비활성화한다. 이는 과거 실행 성공이나 컬럼 정의 전체의 일치를 보증하는 표시가 아니다. 게시판별 컬럼도 검사하며, 조건 없는 DDL·데이터 보정·검사 오류는 대기로 남긴다. 조회 요청 안에서는 메타데이터를 공유해 중복 조회를 줄이고, 다음 요청에서는 다시 검사하므로 외부 변경이나 선행 작업 후 상태가 반영된다.
|
||||
|
||||
`기존 상태 확인 이력 등록` 버튼은 실행 잠금 아래 다시 검사하고, 처음부터 연속해서 실행 불필요한 미기록 항목만 성공 이력으로 등록한다. 등록 시각은 확인한 시각이며 실제 과거 적용 시각이 아니다. 이 동작은 필요하면 이력 테이블을 생성하지만, 마이그레이션 SQL 자체는 실행하지 않는다. 변경이 필요한 항목이나 실패 이력을 만나면 중단하며, 성공 이력의 체크섬 불일치는 오류로 처리한다. 화면 진입만으로 등록하지 않으며 웹 등록은 최고관리자 권한과 POST 토큰 검증을 거친다. 전체 마이그레이션 실행도 기존처럼 실행 조건을 다시 확인하고 건너뛴 항목의 이력을 함께 기록한다.
|
||||
|
||||
전체 실행은 파일명 오름차순, 즉 오래된 마이그레이션부터 처리하며 하나가 실패하면 이후 실행을 중단한다. 개별 실행도 대상보다 앞선 모든 마이그레이션의 성공 이력과 체크섬이 유효해야 한다. 선행 항목이 누락·실패·변경된 경우 대상만 건너뛰기 성공으로 기록하지 않고 오류로 차단한다.
|
||||
|
||||
실행기는 `g5_migrations` 테이블에 마이그레이션 ID, 체크섬, 상태, 오류, 실행시간과 적용시각을 기록한다. 프로세스 간 잠금을 사용해 동시 실행을 막고, 실패하면 이후 마이그레이션을 중단한다. 적용이 끝난 파일의 체크섬이 바뀌면 오류로 처리한다.
|
||||
|
||||
현재 마이그레이션은 공통 실행 취소를 제공하지 않는다. 컬럼·테이블 삭제에 따른 데이터 손실과 후속 마이그레이션 의존성을 자동으로 안전하게 해결할 수 없기 때문이다. `g5_migrations`의 성공 이력만 삭제하는 것도 실제 스키마를 되돌리지 않으므로 취소로 취급하지 않는다. 롤백이 필요하면 데이터베이스 백업을 복원하거나, 해당 변경에 맞춘 별도 복구 절차를 검토한다.
|
||||
|
||||
`check-migration-schema.php`는 마이그레이션의 테이블·컬럼·인덱스가 신규 설치 SQL에 포함되는지와 최종 컬럼 정의가 일치하는지 검사한다. `check-migration-runner.php`는 격리된 임시 테이블로 부분 적용 스키마, 쇼핑몰 미설치 건너뛰기, 기존 데이터 보정을 검사한다. 마이그레이션을 추가하거나 설치 SQL을 변경한 뒤 두 검사를 반드시 실행한다.
|
||||
|
||||
`check-migration-existing.php`는 실제 DB 대신 SQL 응답을 제공해 조회 시 쓰기 금지, 순차 이력 등록, 재검사, 실패 이력·체크섬·잠금 보호를 검사한다. 기존 상태 검사나 이력 등록 로직을 변경한 뒤 실행한다.
|
||||
|
||||
쇼핑몰을 설치하지 않은 사이트에서는 DB 업그레이드 화면의 `쇼핑몰 설치` 버튼으로 최신 쇼핑몰 스키마를 추가할 수 있다. 같은 접두어의 쇼핑몰 테이블이 하나라도 있으면 기존 데이터 훼손을 막기 위해 설치를 중단하며, 기존 `data/dbconfig.php`의 DB 접속 정보와 토큰 키는 유지하고 쇼핑몰 설정만 추가한다. `php bin/check-shop-install.php`로 격리된 접두어에서 후설치, 설정 보존과 중복 실행 차단을 검사할 수 있다.
|
||||
|
||||
쇼핑몰 미설치 사이트의 업그레이드는 다음 두 방식으로 진행할 수 있다.
|
||||
|
||||
- **쇼핑몰 없이 업그레이드:** `쇼핑몰 설치`를 누르지 않고 기존 상태 이력 등록 또는 마이그레이션 실행을 진행한다. 쇼핑몰 관련 SQL은 기존 쇼핑몰 테이블의 존재 조건을 확인하므로 해당 테이블이 없는 설치에서는 실행하지 않는다. 공통 스키마와 쇼핑몰 변경이 한 파일에 있어도 쇼핑몰 문장만 건너뛴다. 쇼핑몰 항목의 성공 이력이 생겨도 쇼핑몰이 설치됐다는 뜻은 아니다.
|
||||
- **쇼핑몰을 추가하고 업그레이드:** `쇼핑몰 설치`로 최신 쇼핑몰 스키마를 생성한 뒤 공통 DB 마이그레이션을 진행한다. 쇼핑몰 없이 업그레이드를 먼저 완료한 뒤 후설치해도 최신 설치 SQL을 사용하므로 과거에 건너뛴 쇼핑몰 이력을 삭제하거나 다시 실행할 필요가 없다.
|
||||
|
||||
이 구분은 `G5_USE_SHOP` 값만으로 판단하지 않고 실제 쇼핑몰 테이블 존재 여부를 사용한다. 기존 쇼핑몰 테이블을 남겨 둔 채 기능만 비활성화한 사이트는 미설치 사이트와 다르며, 기존 테이블의 스키마 보정 대상이 될 수 있다. `check-shop-install.php`는 전체 마이그레이션의 쇼핑몰 SQL을 검사해 미설치 상태의 모든 문장이 건너뛰어지는지, 후설치 뒤에는 오류 없이 처리되는지도 검증한다.
|
||||
|
||||
`check-runtime-ddl.php`는 관리자·게시판·라이브러리·쇼핑몰 등 일반 실행 경로에 DDL 문이 다시 추가되지 않았는지 검사한다. 테이블이나 컬럼 변경은 이 검사를 우회하지 말고 버전형 마이그레이션으로 작성한다.
|
||||
|
||||
게시판 삭제 시 게시판별 테이블을 함께 삭제하는 것처럼 사용자가 명시적으로 요청한 데이터 생명주기 작업은 마이그레이션과 구분해 제한된 허용 목록으로 관리한다. 일반 레코드 생성 과정에서 `AUTO_INCREMENT`를 초기화하는 DDL은 허용하지 않는다.
|
||||
|
||||
## 권한과 배포
|
||||
|
||||
`migrations/`는 업그레이드 완료 후에도 유지한다. Apache 2.4용 `migrations/.htaccess`를 함께 배포하며, Nginx는 별도의 서버 설정이 필요하다. 공유호스팅 적용 조건, 하위 경로 설치 예시와 개별 SQL 파일의 차단 확인 방법은 [마이그레이션 폴더 접근 차단](migration-directory-security.md)을 참고한다. HTTP 접근 차단 후에도 PHP의 서버 내부 파일 읽기는 허용해야 한다.
|
||||
|
||||
관리자 DB업그레이드 실행 시에는 DB 계정에 필요한 DDL 권한이 있어야 한다. 평상시 DDL 권한을 제거한 운영 환경에서는 업그레이드 시점에 필요한 권한을 부여하고, 완료 후 운영 권한으로 복원한다.
|
||||
|
||||
배포 전에는 백업과 테이블 크기, 예상 잠금 시간을 확인한다. MySQL 계열 DB의 DDL은 트랜잭션으로 완전히 되돌릴 수 없으므로 실패한 마이그레이션의 오류를 확인하고 수동 복구 후 재실행한다.
|
||||
|
||||
## 기존 업그레이드 코드
|
||||
|
||||
과거 설치본을 화면 접근 시점에 보정하던 누적 DDL은 일반 실행 파일에서 제거했다. `orderupgrade.php`와 `adm/dbupgrade.php`에 누적됐던 배포 후 변경은 실제 도입 시점별 마이그레이션으로 이전했다. 조사 근거와 대응 목록은 `database-migration-history.md`에 기록한다.
|
||||
|
||||
## 이력 테이블 구조 오류 복구
|
||||
|
||||
`Unknown column 'migration_id' in 'field list'`는 실행기가 사용하는 이력 테이블에 `migration_id` 컬럼이 없을 때 발생한다. 현재 설치 SQL에는 이 컬럼이 포함되어 있다. 기존의 `CREATE TABLE IF NOT EXISTS`는 이미 존재하는 테이블의 구조를 고치지 않으며, 과거 실행기는 이력 조회 실패를 빈 이력으로 취급하여 첫 이력 저장에서 오류가 드러날 수 있었다. 실제 발생 환경에서 구조가 달라진 경위는 테이블 정의와 설치 파일을 확인해야 한다.
|
||||
|
||||
조회와 실행 시 필수 컬럼 및 `migration_id` 단일 기본키를 확인한다. 구조 또는 이력 조회에 실패하면 오류를 표시하고 마이그레이션 실행과 이력 등록을 중단한다. 화면 조회에서는 테이블을 변경하지 않는다.
|
||||
|
||||
1. DB 백업 후 `SHOW CREATE TABLE g5_migrations`와 `SELECT COUNT(*) FROM g5_migrations`로 구조와 기존 이력을 확인한다. 사용자 지정 접두어를 사용했다면 실제 테이블명으로 바꾼다.
|
||||
2. **잘못된 구조이며 이력이 없는 테이블**은 다른 용도로 사용되는 테이블이 아닌지 확인하고, 사용하지 않는 이름으로 보존한다. 예: `RENAME TABLE g5_migrations TO g5_migrations_backup_20260914`. 이름이 중복되지 않는지 먼저 확인한다.
|
||||
3. 최신 파일을 배포한 뒤 DB 업그레이드의 전체 실행 또는 기존 상태 확인 이력 등록을 다시 수행한다. 정상 이력 테이블을 생성하고 실제 DB 상태에 따라 처리한다. 기존 상태 등록은 선행 실행 필요 항목에서 중단하므로 남은 작업은 전체 실행으로 처리한다.
|
||||
4. **기존 이력이 있는 테이블**은 컬럼 매핑과 체크섬을 검토하여 현재 정의로 복구한다. 정상 성공 이력을 잃으면 조건 없는 데이터 보정이 재실행될 수 있으므로, 이력을 임의 삭제하거나 빈 테이블로 대체하지 않는다. `install/gnuboard5.sql` 전체를 기존 DB에 실행하면 테이블이 삭제되므로 복구용으로 실행하지 않는다.
|
||||
@@ -0,0 +1,24 @@
|
||||
# 메일 미인증 회원의 만료 및 탈퇴 처리
|
||||
|
||||
메일인증을 사용하는 사이트에서 인증 링크의 유효시간이 지난 미인증 회원은 기존 회원탈퇴 절차로 정리한다. 기본 유효시간은 60분이며 기본환경설정에서 분 단위로 변경할 수 있다. `0`은 인증 링크 만료와 미인증 만료 정리를 모두 사용하지 않는 설정이다.
|
||||
|
||||
## 실행 시점과 대상
|
||||
|
||||
- 기존 데이터 정리와 동일하게 최고관리자 접속 시 하루 한 번 실행한다. 만료 시각에 즉시 실행되는 예약 작업은 아니며 최고관리자가 접속하지 않으면 정리가 지연된다.
|
||||
- 인증을 완료했거나 이미 탈퇴·삭제된 회원과 최고관리자는 제외한다.
|
||||
- 재발송한 토큰은 새 발급 시각부터 유효시간을 계산한다. 기존 형식의 토큰이나 이미 폐기된 토큰은 회원가입 시각을 기준으로 한다. 유효한 기준 시각을 확인할 수 없는 회원은 자동 처리하지 않는다.
|
||||
- 유효시간의 마지막 초까지는 인증할 수 있다. 실제 탈퇴 직전에 회원 상태를 다시 읽고, 그 이후 인증 완료·토큰 재발송·이메일 변경이 발생하면 해당 탈퇴 처리를 중단한다.
|
||||
- 메일인증을 사용하지 않는 사이트는 미인증 만료 정리를 실행하지 않는다. 소셜 가입 시 이미 인증 처리된 회원도 대상이 아니다.
|
||||
|
||||
## 두 단계의 처리
|
||||
|
||||
1. **만료 회원 탈퇴 처리**: 탈퇴일과 만료 사유를 기록하고 `mb_certify`, `mb_adult`, `mb_dupinfo`를 초기화한다. 이메일 인증·비밀번호 재설정 토큰을 폐기하고 소셜 연결을 해제한다. 일반 탈퇴와 같은 `member_leave` 이벤트를 실행한다. 이후 로그인 및 기존 인증 링크 사용은 불가능하다.
|
||||
2. **탈퇴 후 회원자료 정리**: 기존 `회원탈퇴후 삭제일` 설정과 `member_delete()`를 사용한다. 연락처·주소·비밀번호·포인트 및 기존 삭제 함수가 처리하는 연관 데이터를 정리한다. 이 설정이 `0`이면 두 번째 자동 정리는 하지 않지만 첫 단계의 본인확인 해제는 실행한다.
|
||||
|
||||
기존 정책대로 회원 아이디는 보존하여 재사용을 막는다. 첫 단계에서 회원 레코드를 삭제하거나 연락처·포인트를 즉시 제거하지 않는다. 개인정보 전체의 즉시 삭제 기능은 아니다.
|
||||
|
||||
## 기존 사이트 적용
|
||||
|
||||
적용 시 이미 만료된 미인증 회원도 다음 정리 대상이 된다. 기본환경설정의 메일인증 유효시간과 회원탈퇴후 삭제일을 확인하고, 운영 중인 확장의 `member_leave` 이벤트가 자동 정리에도 적합한지 점검한다. 자동 정리는 만료 회원 본인의 요청이 아니라 최고관리자 요청에서 실행되므로 이벤트 인수의 회원정보를 기준으로 처리해야 한다.
|
||||
|
||||
관리자 회원 목록은 정리 전에는 `메일 미인증`, 탈퇴 처리 후에는 `탈퇴함`으로 표시한다. 만료된 회원을 다시 정상 계정으로 복구하거나 동일 아이디로 재가입시키는 기능은 포함하지 않는다.
|
||||
@@ -0,0 +1,69 @@
|
||||
# 마이그레이션 폴더 접근 차단
|
||||
|
||||
`migrations/`는 DB 업그레이드에 필요한 SQL 파일을 보관한다. 업그레이드 완료 후에도 적용 상태와 체크섬 확인, 후속 업데이트를 위해 폴더와 파일을 유지한다. HTTP 접근만 차단하고, 서버 내부에서 PHP가 파일을 읽을 수 있도록 한다.
|
||||
|
||||
## Apache 2.4 및 공유호스팅
|
||||
|
||||
배포본의 `migrations/.htaccess`에는 다음 설정이 포함된다.
|
||||
|
||||
```apache
|
||||
Require all denied
|
||||
```
|
||||
|
||||
FTP·파일 관리자로 업로드할 때 숨김 파일인 `.htaccess`도 포함해야 한다. 기존에 해당 파일을 수정했다면 설정을 병합한다. 이 규칙은 폴더와 개별 SQL 파일의 HTTP 접근을 차단하며 관리자 DB 업그레이드의 파일 읽기에는 영향을 주지 않는다.
|
||||
|
||||
호스팅에서 `.htaccess`와 인증·접근 제어 지시문을 허용해야 적용된다. `AllowOverride None`이면 무시되며, 지시문 사용이 제한된 환경에서는 HTTP 500이 발생할 수 있다. 차단 여부를 아래 방법으로 확인하고, 적용되지 않으면 호스팅 업체에 해당 폴더와 하위 파일의 HTTP 접근 차단을 요청한다. 웹서버 관리자는 `AllowOverride AuthConfig` 또는 필요한 지시문을 허용하는 설정을 검토할 수 있다. 자세한 조건은 [Apache 공식 안내](https://httpd.apache.org/docs/2.4/howto/htaccess.html)를 참고한다.
|
||||
|
||||
## Nginx
|
||||
|
||||
Nginx는 `.htaccess`를 적용하지 않는다. 사이트를 서비스하는 `server` 블록에 다음 설정을 추가한다. 도메인 루트에 그누보드를 설치한 경우의 예시다.
|
||||
|
||||
```nginx
|
||||
location = /migrations {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ^~ /migrations/ {
|
||||
return 404;
|
||||
}
|
||||
```
|
||||
|
||||
하위 경로에 설치했다면 실제 공개 URL에 맞춰 두 경로를 모두 바꾼다. 예를 들어 `/gnuboard5-dev/`에 설치한 경우 다음과 같다.
|
||||
|
||||
```nginx
|
||||
location = /gnuboard5-dev/migrations {
|
||||
return 404;
|
||||
}
|
||||
|
||||
location ^~ /gnuboard5-dev/migrations/ {
|
||||
return 404;
|
||||
}
|
||||
```
|
||||
|
||||
`^~`는 해당 접두어가 선택되었을 때 일반 정규식 location보다 우선하도록 한다. 기존의 더 구체적인 location, alias, 별도 호스트나 프록시 경로로 같은 파일을 제공한다면 해당 경로도 함께 차단한다. [Nginx location 공식 문서](https://nginx.org/en/docs/http/ngx_http_core_module.html#location)를 참고한다.
|
||||
|
||||
서버 관리자는 설정 파일을 저장한 뒤 문법 검사를 통과한 경우에만 반영한다.
|
||||
|
||||
```bash
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
```
|
||||
|
||||
공유호스팅에서 Nginx 설정 권한이 없다면 위 설치 경로와 설정 예시를 호스팅 업체에 전달한다. Apache 앞에서 Nginx가 정적 파일을 직접 제공하는 구성도 Nginx 측 차단이 필요하다. 이 작업을 위해 SQL 확장자나 파일명을 임의로 변경하지 않는다.
|
||||
|
||||
## 적용 확인
|
||||
|
||||
사이트 주소와 설치 경로를 바꿔 확인한다. 마지막 URL은 실제 존재하는 SQL 파일이어야 한다.
|
||||
|
||||
```bash
|
||||
curl -i 'https://example.com/migrations'
|
||||
curl -i 'https://example.com/migrations/'
|
||||
curl -i 'https://example.com/migrations/20260910_001_kcp_notification.sql'
|
||||
```
|
||||
|
||||
- 폴더와 개별 파일 요청 모두 HTTP 403 또는 404가 반환되고 SQL 본문이 노출되지 않아야 한다. 폴더 목록만 숨기는 `index.html`이나 디렉터리 목록 비활성화만으로는 개별 파일 다운로드를 막을 수 없다.
|
||||
- 사이트 전체 인증으로 HTTP 401이 반환되는 것만으로 폴더 전용 차단을 확인할 수는 없다. 사이트 인증을 통과한 요청에서도 SQL 파일이 차단되는지 확인한다.
|
||||
- HTTP 500은 정상 차단으로 간주하지 않는다. 호스팅의 허용 지시문과 오류 로그를 확인한다.
|
||||
- 관리자 **환경설정 → DB업그레이드**에서 기존 마이그레이션 목록과 상태가 정상적으로 조회되는지 확인한다. 검증 목적으로 마이그레이션을 재실행할 필요는 없다.
|
||||
|
||||
PHP 실행 계정에는 가능한 한 읽기 권한만 부여하고 파일 변경은 배포 계정으로 관리한다. 두 계정이 같은 공유호스팅에서는 계정별 권한 분리가 어려울 수 있으므로 `chmod` 값만으로 변조 방지가 보장된다고 안내하지 않는다. 폴더를 쓰기 가능하게 만들거나 `777`로 설정하지 않는다. DB 백업·실제 비밀번호·개인정보가 포함된 파일은 이 폴더에 보관하지 않는다.
|
||||
@@ -0,0 +1,107 @@
|
||||
# 객체 캐시의 타입 격리와 호환성
|
||||
|
||||
`G5_object_cache`는 한 요청 안에서 사용하는 메모리 캐시다. `common.php`에서
|
||||
요청마다 객체를 생성하며 요청 간 값 공유, TTL, Redis/Memcached 연동은 제공하지 않는다.
|
||||
|
||||
## 메서드 계약
|
||||
|
||||
캐시 주소는 `(type, group, key)`로 결정된다. 타입, 그룹, 키 중 하나라도 다르면
|
||||
별도의 값을 저장한다. `bbs`, `content`, `shop`과 플러그인에서 정한 타입 모두에 적용된다.
|
||||
인자 순서와 기본값은 기존과 같다.
|
||||
|
||||
```php
|
||||
$cache->set($type, $key, $data = array(), $group = 'default');
|
||||
$cache->get($type, $key, $group = 'default');
|
||||
$cache->exists($type, $key, $group = 'default');
|
||||
$cache->delete($type, $key, $group = 'default');
|
||||
```
|
||||
|
||||
- `type`, `group`, `key`는 문자열 또는 정수 식별자를 사용한다. 배열과 객체를
|
||||
식별자로 사용하는 것은 지원하지 않는다. PHP 배열 키 규칙에 따라 정수 `123`과
|
||||
문자열 `'123'`은 같은 키다. 타입에도 같은 규칙이 적용된다.
|
||||
- 빈 문자열은 각 차원에서 유효하다. 빈 그룹 `''`은 생략 시 사용하는 `'default'`와 다르다.
|
||||
식별자에 별도의 정규화나 구분자 연결을 하지 않는다.
|
||||
- `set()`은 값을 저장하고 명시적인 반환값 없이 `NULL`을 반환한다.
|
||||
- `get()`은 저장한 값을 반환하며, 없는 값은 `false`를 반환한다.
|
||||
저장한 `false`와 cache miss는 `exists()`로 구분한다.
|
||||
- `exists()`는 `NULL`, `false`, `0`, `'0'`, `''`, 빈 배열을 저장한 경우에도 `true`다.
|
||||
- `delete()`는 해당 주소만 제거하며 성공 시 `true`, 없는 값이면 `false`를 반환한다.
|
||||
- 객체는 저장 시와 조회 시 각각 `clone`한다. 기존과 같은 얕은 복사이며,
|
||||
객체 내부의 중첩 객체나 배열 내부 객체까지 재귀적으로 복사하지 않는다.
|
||||
- 빈 타입·그룹 버킷은 정리하지 않는다. 기타 타입을 처음 조회할 때도 빈 타입 버킷이
|
||||
만들어질 수 있으며 요청 종료 시 함께 해제된다.
|
||||
|
||||
```php
|
||||
$cache->set('shop', 'same-key', 'shop-value', 'same-group');
|
||||
$cache->set('plugin', 'same-key', 'plugin-value', 'same-group');
|
||||
|
||||
$cache->get('shop', 'same-key', 'same-group'); // 'shop-value'
|
||||
$cache->delete('plugin', 'same-key', 'same-group');
|
||||
$cache->exists('shop', 'same-key', 'same-group'); // true
|
||||
```
|
||||
|
||||
## public 멤버 호환성과 업그레이드
|
||||
|
||||
세 public 멤버는 제거하지 않고 직접 접근을 deprecated로 표시했다.
|
||||
`$writes[$group][$key]`와 `$contents[$group][$key]` 구조는 유지한다.
|
||||
기타 타입의 충돌을 해결하기 위해 `$etcs`에는 타입 차원을 추가했다.
|
||||
|
||||
| 타입 | 기존 저장 구조 | 변경 후 저장 구조 |
|
||||
| --- | --- | --- |
|
||||
| `bbs` | `$writes[$group][$key]` | 동일 |
|
||||
| `content` | `$contents[$group][$key]` | 동일 |
|
||||
| 그 외 | `$etcs[$group][$key]` | `$etcs[$type][$group][$key]` |
|
||||
|
||||
메서드와 public 멤버는 같은 저장소를 사용한다. 호환용 데이터를 별도로 복제하거나
|
||||
기존 `$etcs` 주소로 fallback하지 않는다. 기존 주소에는 타입 정보가 없어 어느 타입의
|
||||
값인지 복원할 수 없기 때문이다. 메서드만 사용하는 호출부는 변경할 필요가 없다.
|
||||
|
||||
외부 테마·플러그인이 `$etcs`를 직접 읽거나 쓰거나 삭제한다면 다음과 같이 수정해야 한다.
|
||||
기존의 서로 다른 타입 간 캐시 공유에 의존했다면 공유할 호출부에서 동일한 타입을
|
||||
명시적으로 사용해야 한다.
|
||||
|
||||
```php
|
||||
// 변경 전: shop 등 모든 기타 타입이 같은 주소를 사용했다.
|
||||
$cache->etcs[$group][$key] = $item;
|
||||
$item = $cache->etcs[$group][$key];
|
||||
unset($cache->etcs[$group][$key]);
|
||||
|
||||
// 변경 후 권장: 메서드로 타입과 존재 여부를 명시한다.
|
||||
$cache->set('shop', $key, $item, $group);
|
||||
if ($cache->exists('shop', $key, $group)) {
|
||||
$item = $cache->get('shop', $key, $group);
|
||||
}
|
||||
$cache->delete('shop', $key, $group);
|
||||
|
||||
// 직접 접근이 불가피한 기존 코드의 새 주소 (deprecated)
|
||||
$cache->etcs['shop'][$group][$key] = $item;
|
||||
```
|
||||
|
||||
2026-09-09 저장소 PHP 코드 검색에서는 클래스 외부의 캐시 public 멤버 직접 접근을
|
||||
확인하지 못했다. 코어의 `bbs`, `content`, `shop` 호출은 메서드를 사용한다.
|
||||
[공개 구현](https://github.com/gnuboard/gnuboard5/blob/master/lib/Cache/obj.class.php)과
|
||||
[원 제보](https://sir.kr/boards/g5_issues/3)도 검토했다. 공개 웹에서
|
||||
`g5_object->writes`, `g5_object->contents`, `g5_object->etcs`를 검색했으나
|
||||
외부 플러그인의 직접 접근 사례는 확인하지 못했다. 검색에 잡히지 않는 코드의
|
||||
호환성까지 보장하는 결과는 아니므로 public 멤버 제거는 이번 변경에 포함하지 않는다.
|
||||
|
||||
## 검증
|
||||
|
||||
```sh
|
||||
php -l lib/Cache/obj.class.php
|
||||
php -l tests/object_cache.php
|
||||
php tests/object_cache.php
|
||||
```
|
||||
|
||||
회귀 테스트는 타입·그룹·키 격리, 삭제 범위, 빈 값과 기본값, 객체 clone,
|
||||
public 멤버와 메서드의 저장소 일치, 인스턴스 간 격리를 확인한다.
|
||||
DB 대역을 사용해 실제 `get_shop_item()`의 빈 그룹 및 `shop_{query hash}` 그룹,
|
||||
`get_content_by_field()`의 `bbs`/`content` 적재와 `get_content_db()`의 재사용도 확인한다.
|
||||
실제 DB나 웹 세션은 필요하지 않다.
|
||||
|
||||
구현과 테스트는 PHP 5.2.17에서 지원하는 문법으로 작성했다. PHP 8.4.22와 PHP 5.2.17
|
||||
CLI에서 변경 파일의 구문 검사와 회귀 테스트가 모두 통과했다. PHP 5.2.17은
|
||||
[공식 소스 배포본](https://museum.php.net/php5/php-5.2.17.tar.gz)을 임시 디렉터리에서
|
||||
CLI와 PCRE를 포함하도록 빌드해 검증했다. 시스템 PHP는 변경하지 않았다.
|
||||
이 검증은 객체 캐시와 위 코어 호출부에 한정하며, 사이트 전체의 PHP 5.2 호환성을
|
||||
검증한 것은 아니다.
|
||||
@@ -0,0 +1,23 @@
|
||||
# 배송완료 기준 주문 포인트
|
||||
|
||||
주문 포인트 지급 기간은 장바구니 상품·옵션 레코드(`ct_id`)별 배송완료 시각(`ct_complete_time`)부터 계산한다. 주문 접수 시각, 장바구니 생성 시각, 송장 등록 시각과는 구분한다.
|
||||
|
||||
## 상태 변경과 지급
|
||||
|
||||
- 관리자 주문 상세의 선택 상품 완료 처리와 주문 목록의 일괄 완료 처리에서 완료 시각을 기록한다.
|
||||
- 이미 완료된 상품을 다시 완료로 저장하면 기존 완료 시각을 유지한다.
|
||||
- 완료를 해제하면 완료 시각을 `NULL`로 초기화한다. 이후 다시 완료하면 새 시각부터 지급 기간을 계산한다.
|
||||
- 회원의 포인트가 있는 상품 중 완료 상태이고 아직 적립되지 않은 상품에 지급한다.
|
||||
- 설정이 0일이면 완료 처리 시 지급한다. 그 외에는 설정 기간 경과 후 관리자 주문 상세 조회 또는 완료 처리 시 지급한다. 별도 예약 작업으로 정시에 지급하는 방식은 아니다.
|
||||
|
||||
## 기존 설치 업그레이드
|
||||
|
||||
변경된 코드로 주문을 처리하기 전에 **환경설정 → DB업그레이드** 또는 CLI에서 `20260909_002_cart_complete_time` 마이그레이션을 적용한다. 선행 마이그레이션이 미적용 상태이면 먼저 적용해야 한다.
|
||||
|
||||
기존 레코드는 완료 시각을 `NULL`로 유지한다. 기존 배송완료·미지급 상품은 지급을 추가로 늦추지 않도록 종전의 `ct_time` 기준을 적용한다. 완료 시각을 추정하거나 업그레이드 시각으로 덮어쓰지 않는다. 기존 상품도 완료를 해제한 후 다시 완료하면 새 기준을 적용한다. 이미 지급한 포인트는 마이그레이션으로 변경하지 않는다.
|
||||
|
||||
신규 설치 스키마에도 같은 컬럼을 포함한다. 별도 확장 기능에서 상품 상태를 변경한다면 완료 시각 기록과 초기화도 함께 구현해야 한다. `get_cart_complete_time_sql()`의 반환식을 `ct_status` 대입보다 먼저 사용하면 기존 상태를 기준으로 완료 시각을 결정할 수 있다.
|
||||
|
||||
## 검증
|
||||
|
||||
`php tests/order_completion_point.php`는 `data/dbconfig.php`의 DB 연결로 세션 전용 임시 테이블을 만들어 상태 변경, 기존 상품 전환 및 지급 조건을 검증한다. 실제 주문·포인트 테이블은 변경하지 않으며 포인트 등록 함수는 테스트 대역을 사용한다. DB 계정에 임시 테이블 생성 권한이 필요하다.
|
||||
@@ -0,0 +1,50 @@
|
||||
# PG별 간편결제 설정과 검증
|
||||
|
||||
관리자 **쇼핑몰 설정 → 결제설정**에서 **PG사 간편결제 버튼 사용**을 `노출함`으로 설정한 뒤, 선택한 PG와 계약한 수단만 체크한다. 계약 상태는 PG사에 직접 확인해야 하며 관리자 화면에서 자동 조회하지 않는다.
|
||||
|
||||
| PG | 개별 수단 | 제한 |
|
||||
| --- | --- | --- |
|
||||
| NHN KCP | PAYCO, 네이버페이, 카카오페이, 애플페이 | 기존 직접 호출과 네이버페이 카드·머니/포인트 분리 유지 |
|
||||
| KG이니시스 | 삼성페이, L.pay, 카카오페이 | 구 INIpay 삼성페이는 모바일만, PRO는 PC·모바일 지원. 범용 KPAY 버튼 제거 |
|
||||
| 토스페이먼츠 API | 토스페이, 네이버페이, 삼성페이, 애플페이, L.pay, 카카오페이, 핀페이, PAYCO, SSG페이 | 계약된 수단의 자체창 호출 |
|
||||
| NICEPAY | 삼성페이, 네이버페이, 카카오페이, 애플페이, PAYCO, SK페이, SSG페이, L.pay | 기존 요청 파라미터 유지. 네이버페이는 `E020=CARD` 카드 경로 |
|
||||
|
||||
애플페이는 모든 PG에서 iOS 모바일에만 표시한다. 삼성페이는 PC에서 휴대폰 연결이 가능하며 구 INIpay만 모바일 전용이다. 표시 조건은 결제 가능 여부를 보증하지 않으며, 실제 기기·브라우저·MID 계약 상태를 확인해야 한다. NICEPAY 간편결제 및 KCP 네이버페이·카카오페이는 기존 관리자 안내처럼 테스트 결제가 제한된다. 다른 수단도 PG에 테스트 지원 여부를 확인한다.
|
||||
|
||||
## KCP 네이버페이 병용 우선순위
|
||||
|
||||
선택한 PG가 네이버페이를 지원하고 해당 수단을 사용 설정했다면 기본 PG만 제공한다. 기본 PG가 네이버페이를 지원하지 않거나 사용 설정하지 않았다면 병용 설정과 KCP 인증정보를 확인하여 KCP 경로를 제공한다. 중복 버튼뿐 아니라 병용 스크립트·요청 처리에도 동일한 판정을 적용한다.
|
||||
|
||||
기본 PG의 네이버페이가 활성화되어 있으면 KCP 머니/포인트 버튼도 숨긴다. 기본 PG마다 카드·머니 지원 범위가 다르므로, 예를 들어 NICEPAY의 카드 경로 대신 KCP 머니/포인트가 필요하다면 NICEPAY 네이버페이를 해제하고 KCP 병용과 포인트 사용을 설정한다. 기존 주문에 저장된 `od_pg`, 거래번호, 인증정보와 조회·영수증·취소 코드는 유지한다.
|
||||
|
||||
## 설정 저장과 배포
|
||||
|
||||
`lib/shop.easypay.lib.php`의 목록을 관리자와 PC·모바일 주문서가 함께 사용한다. `de_easy_pay_services`에 PG별 키를 저장한다. 첫 저장 전에는 기존 이니시스 개별 설정과 토스 PAYCO를 승계한다. 저장 후에는 `inicis_configured`, `toss_configured` 표식으로 전체 선택 해제도 보존하며, 기존 이니시스 컬럼을 동기화하여 결제 모듈과의 호환을 유지한다.
|
||||
|
||||
기존 설치는 새 설정을 저장하기 전에 관리자 **환경설정 → DB업그레이드**에서 `20260909_001_pg_easypay_services`를 적용한다. 저장 공간을 `varchar(255)`에서 `varchar(1024)`로 확장하며 신규 설치 SQL도 같은 크기를 사용한다. 일반 페이지 접근으로 스키마를 변경하지 않는다.
|
||||
|
||||
## 검증
|
||||
|
||||
DB나 외부 PG 연결 없이 실행하는 회귀 테스트:
|
||||
|
||||
```bash
|
||||
php tests/pg_easypay.php
|
||||
node tests/pg_easypay.js
|
||||
```
|
||||
|
||||
- PG별 버튼과 선택값, 기본 PG 우선과 KCP 병용 전환, 인증정보 누락, 전체 비활성, 기존 설정 승계 및 명시적 선택 해제
|
||||
- PC·모바일 구분, iOS·Android 애플페이, 구 INIpay 삼성페이 노출
|
||||
- 토스 9개 provider의 실제 SDK 요청 생성 함수에 SDK 대역을 연결하여 직접 호출과 일반 카드 재선택 검증
|
||||
- NICEPAY 직접 호출 코드와 일반 카드 재선택 시 직접 호출 옵션·에스크로 복원
|
||||
- 토스 승인 성공 처리에 카드 없는 전액 머니 결제와 복합결제 응답을 투입하여 총 승인금액, provider, 승인번호, 현금영수증 저장 검증
|
||||
|
||||
실제 PG 네트워크 요청과 운영 DB 마이그레이션은 이 테스트에서 실행하지 않는다. 배포 검수 시 계약된 MID에서 각 수단의 성공·실패·사용자 취소·재시도, 주문내역·관리자·영수증 표시, 전액 취소·부분 취소를 확인한다. 머니/포인트 및 카드 복합결제는 실제 총액·현금영수증·부분 취소 가능 여부도 확인한다. 커스텀 스킨이 주문서를 자체 구현했다면 공통 버튼 함수와 결제수단 변경 이벤트를 반영해야 한다.
|
||||
|
||||
토스 자체창 요청은 [SDK v2 결제창 문서](https://docs.tosspayments.com/sdk/v2/js/payment)의 `method=CARD`, `card.flowMode=DIRECT`, `card.easyPay` 규격을 사용한다. provider 코드는 [토스 ENUM 문서](https://docs.tosspayments.com/codes/enum-codes)를 따른다. 일반 카드로 변경하면 `DEFAULT`로 되돌리고 SDK 요청에서 `easyPay`를 제외한다.
|
||||
|
||||
## 결제 로고 출처
|
||||
|
||||
- `img/tosspay-logo.svg`: [토스페이 공식 연동 가이드](https://docs-pay.toss.im/)에서 제공하는 로고 ZIP의 `logo-toss-pay-blue.svg` 원본.
|
||||
- `img/pinpay-logo.png`: [스마트로페이 간편결제 안내](https://pg.smartro.co.kr/service/pg-service/payment/easypayment/)에서 제공하는 핀페이 로고 원본.
|
||||
|
||||
로고의 비율과 색상을 유지하고 저장소의 로컬 파일을 사용한다. 무통장입금·가상계좌 등 일반 결제수단은 아이콘 없이 텍스트로 표시한다. 간편결제 로고는 작은 원본을 확대하지 않는다. 원본의 여백과 종횡비를 고려하여 큰 로고만 축소하고, 원본이 작은 L.pay·카카오페이·PAYCO는 원래 크기를 유지한다.
|
||||
@@ -0,0 +1,48 @@
|
||||
# 그누보드(영카트) 5.6.37 배포 안내 초안
|
||||
|
||||
배포 준비 중이며 공개 릴리스 게시 전 검토용 문서입니다. 변경 기준은 5.6.36입니다.
|
||||
|
||||
## 업데이트 절차
|
||||
|
||||
1. 소스·설정·DB를 백업합니다. `config.php`의 `G5_DOMAIN`을 실제 운영 주소로 설정합니다. 보안 메일은 이 주소만 사용하며 미설정·잘못된 설정에서는 발송이 중단됩니다. 자세한 설정과 기존 첨부 점검은 [보안 업데이트 안내](security-release-draft.md)를 확인하십시오.
|
||||
2. SIRK 전용 카카오페이 이용 사이트는 [종료 및 기존 거래 지원 절차](sirk-kakaopay-retirement.md)에 따라 진행 중 거래와 취소 처리 경로를 정리한 뒤 업데이트합니다. 일반 PG의 카카오페이는 계속 지원합니다.
|
||||
3. 변경 파일을 반영하고 아래 삭제 대상 파일을 정리합니다. 사용자 수정 파일과 설정은 먼저 비교·병합합니다. 패치 ZIP은 5.6.36에서 업데이트할 때 사용하며 파일 삭제를 자동 처리하지 않습니다.
|
||||
4. 최고관리자로 **환경설정 → DB업그레이드**에서 상태를 확인하고 마이그레이션을 실행합니다. 일반 화면 접근 시 자동 DB 보정은 종료되었으므로 파일 복사만으로 업데이트가 끝나지 않습니다. 공식 배포본에는 `bin/db-migrate.php`가 포함되지 않습니다. [DB업그레이드 안내](database-migrations.md)를 확인하십시오.
|
||||
5. 회원가입·메일 인증·비회원 게시물 수정과 PC·모바일 주문·취소·포인트 지급을 확인합니다. 커스텀 테마와 결제 연동은 변경된 공통 함수 및 설정을 반영하십시오.
|
||||
|
||||
## 보안 변경
|
||||
|
||||
- KVE-2026-2269, KVE-2026-2331: 보안 메일 링크를 운영자가 설정한 고정 도메인으로 제한합니다.
|
||||
- KVE-2026-2330: 비회원 게시물 수정 인증과 토큰 발급 검증을 강화합니다.
|
||||
- KVE-2026-2329: XHTML 등 실행형 첨부 파일의 저장을 차단합니다. 기존 첨부는 별도 점검이 필요합니다.
|
||||
- KVE-2026-2344: 관리자 보관함·상품판매순위의 날짜 입력 검증과 속성 인코딩으로 XSS를 차단합니다.
|
||||
|
||||
## 기능 및 오류 수정
|
||||
|
||||
- 버전형 DB 마이그레이션, 기존 상태 확인 이력 등록, 쇼핑몰 후설치를 지원합니다.
|
||||
- 메일 인증 링크 유효시간 설정과 메일 미인증 만료 회원 정리 기능을 추가합니다.
|
||||
- 관리자 회원 수정 화면에서 본인확인 정보를 삭제할 수 있습니다.
|
||||
- 소셜 회원가입에서 휴대폰번호 수집·입력·저장을 지원합니다.
|
||||
- PG별 간편결제 개별 표시와 기본 PG 우선 네이버페이 처리를 적용하고 결제수단 표시를 정리합니다.
|
||||
- SIRK 전용 카카오페이 연동을 종료하고 기존 주문의 취소·환불 지원 안내를 제공합니다.
|
||||
- PC·모바일 주문서의 옵션가 표시에 수량을 반영합니다.
|
||||
- 상품별 배송완료 시각을 기준으로 주문 포인트를 지급합니다. 기존 완료 상품의 처리 기준은 [주문 포인트 안내](order-completion-point.md)를 확인하십시오.
|
||||
- 쇼핑몰 최하위 분류에서도 같은 단계 메뉴를 유지합니다.
|
||||
- 객체 캐시의 타입별 저장소를 분리해 값 충돌을 방지합니다.
|
||||
- 게시물 제목을 UTF-8 문자 단위로 제한해 저장 오류를 방지합니다.
|
||||
- 구 토스페이먼츠 테스트 MERT KEY 안내를 보강합니다.
|
||||
|
||||
## 패치 적용 시 삭제할 파일
|
||||
|
||||
진행 중 SIRK 거래를 정리하고 사용자 코드의 직접 참조를 수정한 뒤 삭제하십시오. 목록에 없는 이전 승인·반환·취소 URL의 종료 응답 파일은 유지합니다.
|
||||
|
||||
- `orderupgrade.php`
|
||||
- `shop/kakaopay/_common.php`
|
||||
- `shop/kakaopay/incKakaopayCommon.php`
|
||||
- `shop/kakaopay/kakaopay_result.php`
|
||||
- `shop/kakaopay/mobile_orderform.1.php`
|
||||
- `shop/kakaopay/mobile_pay_result.php`
|
||||
- `shop/kakaopay/orderform.1.php`
|
||||
- `shop/kakaopay/orderform.2.php`
|
||||
- `shop/kakaopay/orderform.3.php`
|
||||
- `shop/kakaopay/pc_pay_result.php`
|
||||
@@ -0,0 +1,37 @@
|
||||
# 그누보드(영카트) 5.6.38 배포 안내
|
||||
|
||||
배포일: 2026-09-10. 변경 기준은 5.6.37입니다.
|
||||
|
||||
이번 버전은 영카트 장바구니 옵션 검증과 KCP 가상계좌 통보 검증을 강화한 보안 업데이트입니다. **파일 반영 후 관리자 DB업그레이드가 필요합니다.**
|
||||
|
||||
## 업데이트 절차
|
||||
|
||||
1. 소스·설정·DB를 백업하고 주문·결제 접수를 잠시 중지합니다. 사용자 수정 파일과 커스텀 테마는 변경 내용을 비교·병합합니다.
|
||||
2. 5.6.37 설치본에 전체 소스 또는 `gnuboard5.6.38.patch.zip`의 변경 파일을 반영합니다. 이번 패치의 삭제 대상 파일은 없습니다. 이전 버전에서 업데이트하는 경우 [5.6.37 안내](release-5.6.37.md)의 설정 변경과 삭제 파일도 확인하십시오.
|
||||
3. 최고관리자로 **환경설정 → DB업그레이드**에서 `20260910_001_kcp_notification`까지 실행합니다. 파일 복사만으로 업데이트가 끝나지 않으며, DB업그레이드 전에는 주문 저장이나 KCP 통보가 실패할 수 있습니다. [DB업그레이드 안내](database-migrations.md)를 참고하십시오.
|
||||
4. KCP 이용 사이트는 DB 계정의 `LOCK TABLES` 및 관련 테이블의 조회·등록·수정 권한을 확인합니다. 기존 미결제 가상계좌 주문·개인결제에는 상점코드가 빈값으로 추가되므로, KCP 거래 기록과 대조해 개별 보정하고 재통보를 요청해야 합니다. 현재 상점 설정을 과거 거래 전체에 일괄 복사하지 마십시오. [KCP 적용·복구 절차](security-kve-2026-2346.md)를 반드시 확인하십시오.
|
||||
5. PC·모바일의 장바구니 추가·수정·주문과 사용하는 PG의 승인·취소를 확인합니다. KCP는 해당 계약의 통보 발신지, 실제 테스트 가상계좌 발급·입금·망취소·재통보를 확인한 뒤 접수를 재개합니다.
|
||||
|
||||
## 보안 변경
|
||||
|
||||
- **KVE-2026-2345:** 상품·옵션 종류와 구성 검증을 강화하여 상품대금이 주문 합계에서 누락되는 경로를 차단합니다. 공통 AJAX, 기본 테마 AJAX, 일반 장바구니 추가와 PC·모바일 주문 준비·확정에 적용합니다. 기존 장바구니의 잘못된 행과 가격 불일치도 거부하며, 정상 무료상품과 정상 0원 옵션은 유지합니다.
|
||||
- **KVE-2026-2346:** 테스트 모드를 포함한 KCP 통보 발신지·입력 검증과 저장된 상점·거래·결제수단·계좌·금액 대조를 적용합니다. 입금·망취소 이력을 기록하여 중복 반영을 방지하고, 중간 실패 시 재시도 및 충돌 검사를 수행합니다. 성공·실패에 맞는 통보 응답을 반환합니다.
|
||||
|
||||
## 오류 수정 및 코드 정리
|
||||
|
||||
- 모바일 이니시스 승인 전 장바구니 검증을 추가하고, 승인 후 검증 실패 시 서버 세션으로 확인한 거래의 승인 취소를 요청합니다. 취소 성공을 확인한 경우에만 완료를 안내합니다.
|
||||
- 기존 장바구니와 병합할 가격·누적 수량 등을 먼저 검사하여, 뒤쪽 옵션 검증 실패 전에 앞쪽 수량이 변경되는 문제를 수정합니다.
|
||||
- CRLF 및 줄 끝 공백이 있는 마이그레이션의 컬럼 생략 조건을 인식하여, 기존 컬럼 중복 추가 오류로 DB업그레이드가 중단되는 문제를 수정합니다.
|
||||
- KCP 처리 단계, 장바구니 조건문, 간편결제 HTML 조합의 가독성을 정리합니다.
|
||||
|
||||
## 적용 후 점검
|
||||
|
||||
- 기존 의심 주문과 장바구니는 금액·옵션·결제·출고 기록을 대조하십시오. 기존 주문을 자동 삭제·취소하는 업데이트는 아닙니다.
|
||||
- KCP 미완료 통보와 기존 미결제 거래를 점검하고 누락된 입금을 대조·복구하십시오. 이미 출고·완료된 주문의 망취소는 물류·콘텐츠 권한·포인트를 별도로 확인해야 합니다.
|
||||
- MyISAM의 여러 SQL 쓰기에 원자적 롤백을 보장하지 않습니다. 통보 처리 이력을 임의로 삭제하거나 완료 처리하지 말고 문서의 재시도·복구 절차를 따르십시오.
|
||||
|
||||
## 검증 범위
|
||||
|
||||
PHP 8.4.22와 격리 MySQL 8 환경에서 장바구니 공통 검사 46개, MyISAM·InnoDB 장바구니 HTTP/DB 검사 126개와 KCP HTTP/DB 검사 420개를 통과했습니다. 간편결제 PHP·JavaScript 회귀 검사 및 HTML 변경 전후 100개 조합 일치도 확인했습니다. 마이그레이션 줄바꿈 검사 13개, 변경 PHP 문법 검사와 신규 설치·마이그레이션 스키마 대조도 통과했습니다.
|
||||
|
||||
HTTP/DB 회귀 검사는 초기화·PG 응답 등을 대체한 환경이며, 실제 외부 PG 승인·은행 입금이나 모든 운영 환경의 연동 검증을 의미하지 않습니다. 사용 중인 PG 계약·프록시·커스텀 테마와 기존 거래의 운영 확인이 필요합니다.
|
||||
@@ -0,0 +1,35 @@
|
||||
# 그누보드(영카트) 5.6.39 배포 안내
|
||||
|
||||
배포일: 2026-09-14. 변경 기준은 5.6.38입니다.
|
||||
|
||||
이번 버전은 임시 주문 정보 접근과 결제 복귀 보호를 강화하고, 마이그레이션 경로 보호와 설치·DB업그레이드를 보완한 보안 업데이트입니다. **파일 반영 후 최고관리자 DB업그레이드가 필요합니다.**
|
||||
|
||||
## 업데이트 절차
|
||||
|
||||
1. 신규 결제 접수를 잠시 중지하고 소스·설정·DB를 백업합니다. 진행 중인 결제와 PG에서 승인됐으나 주문이 완료되지 않은 거래를 먼저 대조합니다.
|
||||
2. 5.6.38 설치본에 전체 소스 또는 `gnuboard5.6.39.patch.zip`을 반영합니다. 이번 패치의 삭제 대상 파일은 없습니다. 사용자 수정 파일은 비교·병합하고 `migrations/.htaccess`도 업로드합니다. 더 오래된 버전은 [5.6.38 안내](release-5.6.38.md)와 선행 버전의 설정·삭제 파일·DB 변경을 함께 확인하십시오.
|
||||
3. 최고관리자로 **환경설정 → DB업그레이드**에서 선행 마이그레이션과 `20260914_001_order_access_state`를 실행합니다. 새 주문 상태 테이블은 InnoDB를 사용합니다. 주문 잠금과 장바구니 잠금은 별도 DB 연결을 사용하므로 일반 결제 요청당 최대 2개의 추가 연결을 고려하십시오. [DB업그레이드 안내](database-migrations.md)를 확인하십시오.
|
||||
4. 사용자 주문 스킨·테마에 주문별 체크아웃 필드와 토큰 헤더 수신·결제 복귀 전달 변경을 반영하고 PHP·JavaScript 파일 및 캐시를 함께 갱신합니다. 공통 임시 저장에서 허용하지 않은 사용자 추가 필드는 저장되지 않으므로 [주문 보호 문서](security-kve-2026-2140.md)의 저장 허용 목록을 확인하십시오.
|
||||
5. Apache는 `.htaccess` 적용 여부를 확인하고, Nginx는 별도의 접근 차단 설정을 반영합니다. `migrations/`와 실제 SQL 파일 URL 모두 403 또는 404가 반환되어야 합니다. 업그레이드 후에도 마이그레이션 파일을 유지하십시오. [서버별 설정 안내](migration-directory-security.md)를 확인하십시오.
|
||||
6. 결제 복귀 URL은 HTTPS를 사용하고 웹 서버·프록시·APM에서 주문 상태 토큰을 기록하지 않도록 마스킹합니다. 과거 임시 주문은 PG 원장과 대조한 후 제공된 CLI 도구로 정리·복구합니다. 승인 여부가 불명확한 거래를 임의로 삭제하거나 다시 승인하지 마십시오.
|
||||
7. 사용 중인 PG의 테스트 환경에서 PC·모바일, 회원·비회원, 결제 앱 복귀, 승인·취소·재시도, 비회원 주문 조회를 확인합니다. 주문·PG 금액과 상태가 일치하고 미완료 거래 처리 방침이 정해진 뒤 접수를 재개하십시오.
|
||||
|
||||
## 보안 변경
|
||||
|
||||
- **KVE-2026-2140:** PC·모바일 Toss와 모바일 KCP의 임시 주문 복원·승인·주문 저장에 주문별 복귀 토큰, 소유자·장바구니·PG·유효기간 검증을 적용합니다. 쿠키 없는 복귀와 다중 탭을 주문별로 처리하며, 토큰으로 일반 로그인 세션을 발급하지 않습니다.
|
||||
- 승인 상태와 결과를 기록하여 중복 승인과 응답 유실 후의 잘못된 재처리를 방지합니다. Toss는 거래 조회·멱등 처리로 복구하며, KCP의 불명확 거래와 주문 일부 저장은 운영자의 원장 대조가 필요합니다.
|
||||
- 공통 임시 주문 저장 항목을 허용 목록으로 제한하고 비회원 비밀번호·토큰을 복원용 데이터와 주문 POST 로그에서 제외합니다. 비밀번호는 서버 해시로 보호하며 완료된 임시 정보와 만료 데이터를 정리하는 CLI 도구를 제공합니다. 기존 데이터는 파일 교체만으로 모두 정리되지 않습니다.
|
||||
- `migrations/.htaccess`와 Nginx 설정 안내를 제공하여 마이그레이션 SQL 파일의 HTTP 접근을 차단할 수 있도록 합니다.
|
||||
|
||||
## 설치·호환성 보완
|
||||
|
||||
- 신규 설치 완료 전에 해당 배포본의 마이그레이션 이력을 등록합니다. 이력 등록에 실패하면 설치 완료로 진행하지 않습니다.
|
||||
- 기존 마이그레이션 이력 테이블의 구조 불일치와 조회 실패를 감지해 잘못된 업그레이드 실행을 차단합니다.
|
||||
- PHP 5.2.17 구문 호환을 유지하도록 주문 보호 코드와 Toss 클래스 등을 보완합니다. 안전한 난수원·SHA-256·JSON이 필요하며, PG 연결에는 해당 PG가 요구하는 cURL/TLS 환경이 별도로 필요합니다.
|
||||
- MySQL 5.0의 연결당 이름 잠금 제약을 고려하여 업무·주문·장바구니 잠금 연결을 분리합니다. 연결 실패 또는 잠금 유실 시 승인 상태 처리를 중단합니다.
|
||||
|
||||
## 검증 범위와 운영 확인
|
||||
|
||||
주문 접근·저장 10건, 토큰 전달 8건, PHP 5.2.17·7.4.33·8.4.22의 호환 함수 각 35건과 운영 PHP 33개 파일의 문법 검사, 안전한 난수원 부재 시 거부 검사를 통과했습니다. MySQL 8.0.45의 실제 연결 잠금 9건, 마이그레이션 이력·줄바꿈 13건, 신규 설치 4개 시나리오와 설치 스키마 대조를 확인했습니다. 격리된 PHP 8.4·MySQL·HTTP 환경에서 결제 상태 50건과 주문 확정 37건을 통과했으며, PG 통신은 대역을 사용했습니다.
|
||||
|
||||
실제 외부 PG 승인·취소·앱 복귀, MySQL 5.0 서버 자체 및 모든 사용자 스킨·운영 환경의 연동을 검증한 결과는 아닙니다. 다른 PG도 공통 저장 변경의 영향을 받으므로 정상 주문 확인이 필요합니다. 지원 버전의 구문 호환과 구형 TLS 환경에서의 실제 PG 연결 가능성은 구분해야 합니다. 상세 복구·정리 절차와 검증 한계는 [KVE-2026-2140 문서](security-kve-2026-2140.md)를 참고하십시오.
|
||||
@@ -0,0 +1,26 @@
|
||||
# 그누보드(영카트) 5.6.391 배포 안내
|
||||
|
||||
배포일: 2026-09-15. 변경 기준은 5.6.39입니다.
|
||||
|
||||
쇼핑몰 4·5단계 분류의 현재 경로와 서브메뉴 표시를 보완하는 버그 수정 버전입니다.
|
||||
|
||||
## 변경 사항
|
||||
|
||||
- PC 기본 스킨과 basic 테마의 breadcrumb에 4·5단계 분류 경로를 표시합니다. 모든 단계에서 기존 위치와 스타일을 유지합니다.
|
||||
- PC·모바일 기본 스킨과 basic 테마의 서브메뉴는 선택한 4단계에 사용 중인 5단계가 있으면 5단계 목록만 표시하고, 없으면 4단계 형제 목록만 표시합니다.
|
||||
- 5단계에서는 같은 부모 아래의 5단계 형제 목록과 현재 선택을 표시합니다.
|
||||
- 공통 전체 분류 트리의 기존 3단계 구조는 유지하고 현재 경로에 필요한 깊은 단계만 추가 조회합니다.
|
||||
|
||||
## 업데이트 방법
|
||||
|
||||
1. 기존 소스와 사용자 수정 파일을 백업합니다.
|
||||
2. 5.6.39 설치본에 전체 소스 또는 `gnuboard5.6.391.patch.zip`을 반영합니다. 공통 라이브러리와 스킨 파일을 함께 업데이트합니다. 사용자 수정 스킨은 변경 내용을 비교하여 병합합니다.
|
||||
3. 4·5단계 분류의 breadcrumb, 하위 분류 유무에 따른 메뉴, 현재 선택 표시를 확인합니다.
|
||||
|
||||
이번 버전의 추가 DB 마이그레이션과 삭제 대상 파일은 없습니다. 5.6.39보다 오래된 설치본은 [5.6.39 보안 업데이트 및 DB업그레이드 안내](release-5.6.39.md)를 포함한 선행 업데이트를 적용해야 합니다.
|
||||
|
||||
## 검증
|
||||
|
||||
8개 분류 경로에서 PC·basic 테마의 목록/상세 breadcrumb와 네 스킨의 메뉴·선택 상태를 확인했습니다. Chromium에서 1·3·4·5단계 전환 시 breadcrumb 위치·높이와 메뉴·상품 정렬 영역 위치가 동일함을 확인했습니다. 변경된 운영 PHP 7개 파일은 현재 PHP와 PHP 5.2 구문 검사를 통과했습니다.
|
||||
|
||||
분류 데이터 대역을 사용한 회귀 검사이며 실제 운영 DB와 모든 사용자 스킨을 검증한 결과는 아닙니다.
|
||||
@@ -0,0 +1,28 @@
|
||||
# 그누보드(영카트) 5.6.40 배포 안내
|
||||
|
||||
배포일: 2026-09-20. 변경 기준은 5.6.391입니다.
|
||||
|
||||
KVE-2026-2424 LG 가상계좌 입금 통보 위조를 차단하고 중복·역순 통보와 은행 취소 처리를 보완한 보안 업데이트입니다. **파일 반영 후 최고관리자 DB업그레이드가 필요합니다.**
|
||||
|
||||
## 변경 사항
|
||||
|
||||
- 통보 발신 IP·상점키·서명과 저장된 상점 MID·PG·거래번호·계좌·금액을 함께 검증합니다. 빈 키나 다른 PG 주문을 이용한 입금 처리를 차단합니다.
|
||||
- PC·모바일 일반 주문 및 개인결제에 결제 당시 LG MID를 저장합니다.
|
||||
- 통보 이력과 테이블 잠금으로 중복·동시·역순 통보를 처리하고, 중간 실패 시 같은 통보 재전송으로 복구할 수 있도록 합니다.
|
||||
- 은행 취소 시 확인된 입금액만 복원합니다. 개인결제 연결 주문이 입금 후 바뀌면 차감을 차단하며, 사용 중지된 개인결제도 기존 입금의 취소를 처리합니다.
|
||||
|
||||
## 업데이트 방법
|
||||
|
||||
1. 신규 주문·결제 접수를 잠시 중지하고 파일·DB를 백업합니다.
|
||||
2. 5.6.391 설치본에 전체 소스 또는 `gnuboard5.6.40.patch.zip`을 반영합니다. 사용자 수정 파일은 비교·병합합니다. 이번 패치의 삭제 대상 파일은 없습니다. 이전 버전은 선행 업데이트도 적용하십시오.
|
||||
3. 최고관리자의 **환경설정 → DB업그레이드**에서 `20260918_001_lg_notification`을 적용합니다. DB 계정에는 관련 테이블의 `LOCK TABLES`, `SELECT`, `INSERT`, `UPDATE` 권한이 필요합니다. 스키마 적용 전에는 주문 저장이나 통보 처리가 실패할 수 있습니다.
|
||||
4. 기존 미입금 LG 거래는 PG 원장과 대조한 뒤 확인된 건에만 `od_lg_mid`·`pp_lg_mid`를 보정하고 재통보를 요청합니다. 기존 행의 MID는 빈값으로 추가되며 이 상태에서는 통보를 거부합니다. 현재 설정을 과거 거래 전체에 일괄 복사하지 마십시오.
|
||||
5. HTTPS, 프록시의 원본 IP 복원과 실제 계약 PG 테스트 환경에서 PC·모바일 일반/개인결제의 계좌 발급·입금·은행 취소·재통보를 확인한 뒤 접수를 재개합니다.
|
||||
|
||||
일반형 가상계좌 전액 입금을 대상으로 합니다. 고정식 계좌·부분입금 및 계약별 취소 금액·순번 형식은 별도 대조가 필요합니다. 출고 후 은행 취소와 미완료 처리 이력은 운영자가 확인해야 합니다. 상세 적용·복구 절차와 검증 한계는 [KVE-2026-2424 안내](security-kve-2026-2424.md)를 참고하십시오.
|
||||
|
||||
## 검증
|
||||
|
||||
변경 PHP 8개 파일은 PHP 5.2.17·8.4.22 구문 검사를 통과했고, 마이그레이션 줄바꿈 13건을 확인했습니다. 격리된 MySQL의 MyISAM/InnoDB에서 실제 콜백과 SQL을 사용하는 HTTP·DB 회귀 검사 및 신규 설치 마이그레이션 이력 검사를 수행했습니다.
|
||||
|
||||
PG 발신 IP와 일부 실패 조건은 대역을 사용했습니다. 실제 외부 PG 통신·은행 입금, 전체 주문서 제출, 운영 서버와 모든 사용자 수정 환경의 검증을 포함하지 않습니다.
|
||||
@@ -0,0 +1,110 @@
|
||||
# KVE-2026-2140 주문 상태·복귀·복구
|
||||
|
||||
## 구현 범위
|
||||
|
||||
1차 세션 검사에 주문별 복귀 토큰, 영속 승인 기록, 데이터 정리와 운영 대조 도구를 추가했다. PC·모바일 Toss와 모바일 KCP의 반환·승인·주문 저장을 대상으로 한다. 공통 임시 저장의 필드 제한과 비밀번호 보호는 다른 PG에도 적용된다. 다른 PG의 쿠키 없는 복귀·승인 재시도를 이 구현이 대신 처리하지는 않는다.
|
||||
|
||||
- 임시 저장은 256비트 난수 토큰을 발급한다. DB에는 SHA-256 해시만 저장하고, 토큰 원문은 `X-G5-Order-State` 응답 헤더로 현재 탭에 전달한다.
|
||||
- 토큰은 주문번호·PG·회원/비회원·장바구니·일반/개인결제·저장 본문 해시·만료 시각에 연결된다. 원문 임시 데이터와 비밀번호 해시를 읽기 전에 토큰과 최소 메타데이터를 검증한다.
|
||||
- 새 테이블 `order_access`의 주문별 잠금과 최종 처리 시 장바구니별 잠금을 사용한다. 서로 다른 세션/프로세스에서도 승인부터 주문 저장까지 중복 처리를 막는다. 같은 장바구니의 다른 미완료 승인도 차단한다.
|
||||
- 폼을 표시할 때 주문별 체크아웃 nonce와 장바구니를 세션에 등록한다. 다른 탭이 현재 주문 세션을 바꾸더라도 원래 탭은 자기 주문을 저장·복원한다. 같은 상품 장바구니를 두 번 결제하는 것은 허용하지 않는다.
|
||||
- 쿠키 없는 복귀는 토큰으로 서버 상태를 복원한다. 회원 주문의 경우 해당 결제 요청 안에서만 원래 회원 컨텍스트를 적용하고 로그인 세션은 발급하지 않는다. 다른 회원으로 로그인한 브라우저의 토큰 사용은 거부한다. 결제 토큰은 해당 주문에 대한 bearer 권한이므로 로그·URL 보관에 주의한다.
|
||||
- 최종 주문의 이름·주소·금액·포인트·쿠폰·배송비 등 업무 필드는 서버가 저장한 데이터로 복원한다. 결제 키·PG 암호문 등 승인 응답 필드는 별도로 검증한다. 정상 결제 금액은 기존 서버 장바구니 계산값 및 개인결제 금액과 대조한다.
|
||||
- 공통 임시 저장은 `lib/shop_order_fields.lib.php`의 기본 주문서/PG별 허용 목록만 저장한다. 비밀번호와 토큰은 복원용 데이터에 보관하지 않는다. 사용자 스킨의 추가 업무 필드는 서버 허용 목록에 명시적으로 추가해야 한다.
|
||||
- 비회원 비밀번호는 서버 해시로 분리 보관하며, 최종 주문에서 재해싱하지 않는다. 완료 후 임시 행·토큰·해시·응답 상태를 정리한다. 주문 POST 로그에서 비밀번호와 상태/체크아웃 토큰을 제외한다.
|
||||
|
||||
## 승인 상태와 복구
|
||||
|
||||
| 상태 | 처리 |
|
||||
|---|---|
|
||||
| `pending` | 승인 미시작. 기본 복귀 수명 2시간. `G5_ORDER_DATA_ACCESS_TTL`로 조정 가능 |
|
||||
| `approving` | PG 요청 직전에 금액·요청 식별자를 영속 기록. 응답 유실 가능성이 있으므로 무조건 재승인하지 않음 |
|
||||
| `approved` | 검증된 PG 결과를 저장. 후속 주문 저장을 계속할 수 있음 |
|
||||
| `finalizing` | 주문 DB 반영을 시작함. 일부만 저장되었을 가능성이 있음 |
|
||||
| `completed` | 주문 처리가 끝남. 토큰·비밀번호 해시·PG 응답은 지워짐 |
|
||||
| `cancel_pending` | 취소 요청 시작. 결과 대조 전에는 승인 결과를 재사용하지 않음 |
|
||||
| `cancelled`, `failed` | 확인된 종료 상태. 유예 후 정리 가능 |
|
||||
| `legacy`, `unknown` | 운영자 대조 필요. 자동 삭제·재승인·토큰 발급 금지 |
|
||||
| `purged` | 임시 개인정보 및 인증 상태 제거. 주문번호 재사용 방지용 기록만 유지 |
|
||||
|
||||
Toss는 안정적인 `Idempotency-Key`로 승인한다. 응답을 놓친 요청과 승인 후 중단된 요청은 PG 조회를 먼저 수행하고 주문번호·결제 키·금액·상태를 대조한다. 취소·실패한 거래의 저장된 성공 응답을 재사용하지 않는다. PG가 같은 거래의 `IN_PROGRESS` 상태를 확인한 경우에만 동일 키로 승인 재시도하며, 멱등 키 유효기간보다 짧은 14일로 제한한다. 조회 자체가 실패하면 재승인하지 않는다. 취소에도 동일 거래의 고정 멱등 키와 거래번호 대조를 적용한다.
|
||||
|
||||
KCP는 검증된 승인 결과를 별도 기록해 같은 요청 재처리 시 결과를 복원한다. 통신 도중 중단되어 승인 결과가 없는 경우에는 KCP 거래 원장을 확인한 운영자가 복구 자료를 제공해야 한다. 자동 조회 API를 임의로 가정하지 않았다.
|
||||
|
||||
일반 주문이 `finalizing`에서 중단됐으나 주문 행이 없고 원래 선택 장바구니가 남아 있으면 승인된 거래를 계속 처리한다. 주문 행이 이미 생겼거나 개인결제가 일부 반영된 경우에는 자동 덮어쓰기/재승인하지 않는다. 포인트·쿠폰·주문·장바구니를 운영자가 대조한 뒤 완료 처리한다. 기존 주문/장바구니 테이블이 MyISAM인 환경에서 전체 업무 트랜잭션을 원자적으로 되돌릴 수 있다고 가정하지 않는다.
|
||||
|
||||
## 설치·배포
|
||||
|
||||
신규 설치 SQL과 `migrations/20260914_001_order_access_state.sql`을 함께 제공한다. 기존 설치는 최고관리자 DB업그레이드에서 선행 마이그레이션을 처리한 후 이 마이그레이션을 적용한다. 일반 페이지에서는 DDL을 실행하지 않는다. 새 테이블은 InnoDB를 사용한다. MySQL 5.0의 연결당 이름 잠금 하나 제약에 맞춰 주문 잠금과 장바구니 잠금은 각각 별도 비영속 DB 연결을 사용한다. 기존 업무·포인트 DB 연결의 잠금과 분리되며, 연결 종료 시 서버가 잠금을 해제한다. 이 처리에는 일반 결제 요청당 최대 2개의 추가 DB 연결이 필요하다. 연결·잠금 획득 실패나 잠금 연결 유실이 확인되면 승인 상태 처리를 중단한다. 정리 도구는 항목별로 잠금을 해제하여 처리 건수만큼 연결이 누적되지 않는다.
|
||||
|
||||
배포 순서:
|
||||
|
||||
1. 신규 결제 접수를 일시 중단하고 DB 및 웹 공개 경로 밖의 미완료 거래 백업을 확보한다.
|
||||
2. 미완료 주문·PG 승인 내역을 확인한다. 승인된 거래를 정리 대상으로 표시하지 않는다.
|
||||
3. 마이그레이션과 PHP/JS 변경을 함께 적용한다. 사용자 스킨에도 체크아웃 필드, 응답 헤더 수신 및 복귀 토큰 전달 변경을 반영한다. 새 PHP에 예전 JS가 캐시되지 않도록 배포 캐시를 갱신한다.
|
||||
4. 반환 URL의 HTTPS와 토큰 전달을 확인한다. Toss 성공/실패 URL과 KCP Ret_URL에 `g5_order_state`가 전달된다. KCP의 쿠키 재전송 우회는 토큰 형태를 인식하는 것뿐이며, 실제 인증은 엔드포인트의 DB 해시 검증으로 수행한다.
|
||||
5. 웹서버·프록시·APM에서 결제 복귀 URL의 쿼리와 Referer를 로그에 기록하지 않거나 `g5_order_state`를 마스킹한다. 예를 들어 Nginx 결제 반환 로그는 `$request_uri`/`$request` 대신 쿼리 없는 `$uri`를 사용한다. 응답은 `no-store` 및 `Referrer-Policy: no-referrer`를 설정한다. 응답 헤더와 POST 본문을 별도 수집하는 서비스에도 같은 마스킹을 적용한다.
|
||||
6. 계약된 PG 테스트 환경에서 외부 앱 복귀·성공·실패·취소·재통보를 확인한 후 접수를 재개한다. 코드 push와 운영 배포/PG 검증은 구분한다.
|
||||
|
||||
## 기존 요청과 정리 도구
|
||||
|
||||
`php tools/shop-order-maintenance.php`는 CLI 전용이며 기본값은 읽기 전용 목록이다. 웹에서는 실행되지 않는다. 목록과 오류에 토큰·비밀번호·전체 개인정보·PG 응답을 출력하지 않는다.
|
||||
|
||||
```sh
|
||||
php tools/shop-order-maintenance.php --action=list
|
||||
php tools/shop-order-maintenance.php --action=cleanup
|
||||
php tools/shop-order-maintenance.php --action=cleanup --apply
|
||||
php tools/shop-order-maintenance.php --action=reconcile-toss --order=주문번호 --apply
|
||||
php tools/shop-order-maintenance.php --action=quarantine-legacy --order=주문번호 --apply
|
||||
```
|
||||
|
||||
`cleanup`은 기본 24시간의 유예 후 완료·확인된 실패·취소 및 만료된 미승인 Toss/KCP 요청만 처리한다. 1회 최대 500건이다. `--grace=초`는 최소 1시간이다. `approving/approved/finalizing/unknown/cancel_pending`과 검토 전 과거 데이터는 자동 정리하지 않는다. 운영 환경에서 명시적으로 예약 실행할 수 있다. 일반 조회 요청에 정리 작업을 붙이지 않는다.
|
||||
|
||||
1차 패치의 주문·세션·본문 해시 검증을 통과하는 과거 요청은 새 상태로 전환할 수 있다. 그보다 오래된 요청은 주문번호 또는 PAYREQ_MAP만으로 신뢰하거나 토큰을 재발급하지 않는다. `quarantine-legacy`는 지정 주문의 평문 비밀번호를 제거하고 해시를 별도 보관하되 접근 불가능한 `legacy` 상태로 격리한다. 승인된 미완료 거래를 먼저 PG/주문 자료와 대조해야 한다.
|
||||
|
||||
KCP의 불명확한 승인 결과는 원장을 확인한 뒤 다음 명령으로 반영한다. JSON에는 `orderId`, `amount`, `tno`, `res_cd`와 해당 결제수단의 `app_time`, `app_no`, 은행/가상계좌 등의 실제 승인 결과를 넣는다. 자료는 웹 공개 경로 밖에 보관한다. 이 명령은 운영자의 원장 확인을 명시적으로 신뢰하며, 파일 자체를 PG 서명으로 간주하지 않는다.
|
||||
|
||||
```sh
|
||||
php tools/shop-order-maintenance.php --action=reconcile-kcp --order=주문번호 --receipt=/private/verified-kcp.json --verified --apply
|
||||
php tools/shop-order-maintenance.php --action=complete-reviewed --order=주문번호 --verified --apply
|
||||
php tools/shop-order-maintenance.php --action=purge-reviewed-legacy --order=주문번호 --receipt=/private/legacy-review.json --verified --apply
|
||||
```
|
||||
|
||||
`complete-reviewed`는 `finalizing` 거래의 주문·PG·장바구니·포인트·쿠폰 대조를 마친 경우에만 사용한다. 저장된 거래번호도 승인 기록과 비교한다. 기존 데이터 삭제 자료는 `orderId`, `resolution`(`unpaid/cancelled/completed`), `reference`(원장 대조 기록)를 포함한다. `completed`를 선택하면 실제 저장된 주문/개인결제의 거래번호가 있어야 한다. 이 도구는 불명확한 거래를 자동으로 미승인으로 판정하지 않는다.
|
||||
|
||||
## 검증 방법과 한계
|
||||
|
||||
- `php tests/shop_order_access_test.php`: 1차 세션 상태의 안전한 전환 경계와 저장 허용 목록.
|
||||
- `node tests/shop_order_state_browser_test.js`: 토큰 헤더·폼 전달·콜백 URL 인코딩. DOM 대역이며 실제 PG SDK 실행은 아니다.
|
||||
- `tests/shop_order_state_test.py`: 실제 PHP/DB/HTTP, 쿠키 없는 Toss/KCP 복귀, 위조 토큰, 주문/금액/키 변조, 다중 탭, 병렬 프로세스 승인, 응답 유실·부분 저장·취소 상태, 정리와 과거 요청, 신규/기존/쇼핑몰 미설치 마이그레이션.
|
||||
- `tests/shop_order_finalize_test.py`: 별도 테스트 설치본에서 PG 전송 클래스만 대체하고 실제 PC·모바일 주문 확정, 개인결제, 비회원 주문 조회, 완료 정리, 중복 POST를 실행한다. 테스트가 끝나면 원래 전송 클래스를 복원한다.
|
||||
|
||||
Python 검증에는 `G5_ORDER_TEST_CONFIG`로 별도 설치본의 JSON(`root`, `php`, `ini`, `port`)을 지정한다. 해당 설치본은 loopback DB 주소와 `issue48_` 접두어의 데이터베이스를 사용해야 하며 `data/.order-state-test`에 `KVE-2026-2140-local-test`를 적어 명시적으로 표시한다. fixture PHP는 운영 DB에서 실행을 거부한다. 테스트 파일은 Git에는 보관하지만 일반 배포 아카이브에서 제외한다.
|
||||
|
||||
실제 Toss/KCP 네트워크 승인·취소·재통보, PG 앱/브라우저별 쿠키 정책, 다른 PHP/DB 버전, 모든 사용자 스킨의 호환성은 이 로컬 대역 검증으로 확정하지 않는다. KCP 불명확 거래와 부분 주문 저장은 위 운영자 대조 경로로 처리하며 무조건 자동 복구한다고 안내하지 않는다.
|
||||
|
||||
Toss API 근거: [결제 API](https://docs.tosspayments.com/reference), [멱등성과 결제 후처리](https://docs.tosspayments.com/guides/v2/get-started/llms-quick-reference).
|
||||
|
||||
## 최소 버전 호환 처리
|
||||
|
||||
PHP 최소 버전은 기존 PHP 5.2.17을 유지한다. 공통 주문 라이브러리의 익명 함수와 `__DIR__`, PHP 7 전용 난수·역직렬화 옵션 의존을 제거했다. PC·모바일 주문서의 익명 콜백과 PHP 5.4 이후 JSON 출력 옵션, Toss 클래스의 스칼라·반환·프로퍼티 타입 선언도 구버전에서 읽을 수 있게 변경했다. 운영 도구는 구버전 HTTP 상태 헤더·JSON 출력·예외 처리를 사용한다.
|
||||
|
||||
`lib/shop_order_compat.lib.php`에서 다음을 공통 처리한다.
|
||||
|
||||
- 난수는 `random_bytes`, 강한 OpenSSL 난수, `/dev/urandom`, `MCRYPT_DEV_URANDOM` 순으로 사용 가능한 안전한 소스를 찾는다. 약한 난수로 대체하지 않으며 안전한 소스가 없으면 결제를 진행하지 않는다.
|
||||
- 토큰·해시는 길이와 자료형을 확인하고 같은 길이의 문자열은 끝까지 비교한다.
|
||||
- 기존 Base64/serialize 임시 데이터는 배열·문자열·정수·유한 실수·불리언·null만 읽는 전용 파서로 복원한다. 객체·참조·잘못된 길이·과도한 크기/깊이는 거부하며 PHP 객체 생성이나 `unserialize()`를 호출하지 않는다. 원문 길이와 바이너리 문자열을 유지하므로 기존 주문 필드와 KCP 응답을 읽을 수 있다.
|
||||
|
||||
인증 토큰 생성과 주문 상태 처리에는 hash(SHA-256)·JSON 및 안전한 난수원이 필요하다. PG 통신에는 해당 PG가 요구하는 cURL/TLS 환경이 별도로 필요하며, PHP 구문 호환이 구형 TLS 라이브러리로 실제 PG 연결까지 가능함을 보장하지 않는다. 사용자 주문 스킨의 토큰 전달 반영 요건은 그대로 유지한다.
|
||||
|
||||
검증 명령:
|
||||
|
||||
```sh
|
||||
php tests/shop_order_compat_test.php
|
||||
php -d disable_functions=random_bytes,openssl_random_pseudo_bytes,mcrypt_create_iv tests/shop_order_compat_test.php --no-rng
|
||||
php tests/shop_order_lock_test.php 127.0.0.1:포트 issue48_테스트DB
|
||||
```
|
||||
|
||||
설계 근거: [MySQL의 GET_LOCK 버전별 동작](https://dev.mysql.com/blog-archive/making-get_lock-behavior-more-predictable-cross-version-with-query-rewrite/)과 [mysql_connect의 new_link 옵션](https://www.php.net/manual/en/function.mysql-connect.php)을 기준으로 잠금 연결을 분리했다. PHP의 [random_bytes 지원 범위](https://www.php.net/manual/en/function.random-bytes.php)와 [PHP 7의 역직렬화 필터 도입](https://www.php.net/manual/en/migration70.new-features.php)을 확인하고 구버전용 처리를 추가했다. DB 파일 잠금은 여러 웹서버 간 보호를 보장하지 않으므로 사용하지 않는다.
|
||||
|
||||
호환 함수와 구형 mysql 드라이버의 단일 잠금 동작 대역은 PHP 5.2.17·7.4·8.4에서 검사한다. 실제 잠금 검사는 root/빈 비밀번호인 격리 MySQL에서 업무·주문·장바구니 연결 분리, 다른 프로세스의 경합, 종료·연결 단절 시 해제와 유실 감지를 확인한다. MySQL 5.0 서버 자체의 실행 검증과 실제 PG 통신은 이 검사에 포함하지 않는다. 기존 주문 상태·최종 주문 회귀 검증도 별도 설치본에서 실행한다.
|
||||
@@ -0,0 +1,41 @@
|
||||
# KVE-2026-2344 관리자 날짜 필터 보완
|
||||
|
||||
보관함(`wishlist.php`, 메뉴 `500140`)과 상품판매순위(`itemsellrank.php`, 메뉴 `500100`)의 날짜 필터를 보완했다. 기존에는 입력에 숫자가 하나라도 있으면 원문을 허용하고 HTML 속성에 직접 출력하여 반사 XSS가 발생할 수 있었다.
|
||||
|
||||
## 변경 동작
|
||||
|
||||
- 두 화면은 공통 `date_filter.lib.php`로 날짜를 검사한다. 관리자 권한 확인 뒤, 화면 헤더 출력과 조회 SQL 실행 전에 검증한다.
|
||||
- `fr_date`와 `to_date`는 모두 문자열이며 8자리 ASCII 숫자여야 한다. `checkdate()`로 실제 날짜인지 확인하고 시작일이 종료일보다 늦으면 거부한다.
|
||||
- 날짜 파라미터가 하나라도 전달되면 두 값 모두 유효해야 한다. 배열·빈 문자열·한쪽 누락·개행·잘못된 달력 날짜·기간 역전은 HTTP 400과 고정 안내 문구로 거부한다. 공격 문자열은 안내에 반영하지 않는다.
|
||||
- 날짜 파라미터가 모두 없는 최초 조회는 전체 기간을 조회한다. 보관함은 빈 날짜, 판매순위는 기존과 같이 종료일에 오늘 날짜를 표시한다.
|
||||
- 날짜 input은 `htmlspecialchars`의 `ENT_QUOTES | ENT_SUBSTITUTE`와 UTF-8로 인코딩한다.
|
||||
- 페이지 이동·정렬 링크의 필터는 `http_build_query`로 URL 인코딩하고 HTML 속성에 맞게 인코딩한다. 필터를 사용하지 않은 조회에서는 빈 날짜를 링크에 넣지 않는다. 페이지 이동 중 분류·날짜·정렬 조건을 유지한다.
|
||||
|
||||
일반 관리자 권한 검사와 상품·회원·주문 데이터 저장 동작은 변경하지 않는다. 스키마 변경이나 데이터 마이그레이션은 필요하지 않다.
|
||||
|
||||
## 검증
|
||||
|
||||
```bash
|
||||
php tests/admin_date_filter.php
|
||||
node tests/admin_date_filter.js
|
||||
```
|
||||
|
||||
브라우저 검증에는 Playwright와 Chromium이 필요하다. 별도 설치 경로를 사용하는 경우 `PLAYWRIGHT_MODULE`과 `CHROME_EXECUTABLE` 환경변수로 지정할 수 있다.
|
||||
|
||||
PHP 검증은 실제 두 페이지를 실행하며 인증·DB·공통 헤더는 대역을 사용한다. 실제 입력·출력·페이징 및 정렬 함수로 다음을 확인한다.
|
||||
|
||||
- 두 페이지와 두 날짜 필드에 대한 100개 잘못된 입력의 HTTP 400 거부 및 조회 SQL 미실행
|
||||
- 한쪽 날짜 누락, 명시적 빈값, 기간 역전 거부
|
||||
- 최초 전체 조회와 페이지 이동, 윤년 정상 조회, 분류 필터와 정렬 조건 유지
|
||||
- Chromium에서 추가 이벤트 속성과 스크립트 실행 표식이 생성되지 않음
|
||||
- 검증 단계와 별도로, 실제 input 출력문에 속성 주입 문자열을 넣어도 출력 인코딩으로 값 안에만 남고 포커스 이벤트가 실행되지 않음
|
||||
|
||||
운영 DB·실제 관리자 세션을 사용하거나 회원 권한을 변경하는 검증은 수행하지 않는다. 배포 전 운영과 동등한 격리 환경에서 관리자 로그인·권한별 접근과 정상 조회를 추가 확인한다.
|
||||
|
||||
## 배포 및 보고 상태
|
||||
|
||||
현재 상태는 코드 수정 및 격리 회귀 검증 완료다. 운영 반영과 수정 버전 배포 완료를 의미하지 않는다. 배포 버전·배포일은 확정 후 별도로 기록한다.
|
||||
|
||||
인터넷진흥원 중간 회신 문구:
|
||||
|
||||
> KVE-2026-2344와 관련하여 관리자 보관함 및 동일 원인이 확인된 상품판매순위의 날짜 입력 검증과 출력 인코딩을 보완하였습니다. 잘못된 날짜 입력은 조회 전에 거부하도록 수정하였으며, 정상 조회·정렬·페이지 이동과 브라우저의 이벤트 실행 차단을 격리 환경에서 검증하였습니다. 현재 코드 수정 및 회귀 검증을 완료한 상태이며, 운영 반영 및 배포 버전·배포일은 확정 후 조치 결과와 함께 별도 통보하겠습니다.
|
||||
@@ -0,0 +1,67 @@
|
||||
# KVE-2026-2346 KCP 통보 검증 보강
|
||||
|
||||
KCP 공통 통보의 테스트 모드 발신 검증 누락으로 미결제 주문을 입금 처리할 수 있는 경로를 차단한다. `shop/settle_kcp_common.php`에서 요청을 검증한 뒤 `lib/kcp_notification.lib.php`가 주문·개인결제·장바구니와 처리 이력을 반영한다.
|
||||
|
||||
## 적용 절차
|
||||
|
||||
1. 소스와 DB를 백업하고 주문·결제 접수를 잠시 중지한다. 변경 파일 반영 후 최고관리자의 **환경설정 → DB업그레이드**에서 `20260910_001_kcp_notification`까지 실행한다. 신규 설치 SQL에도 같은 스키마를 포함했다. 일반 요청에서는 DDL을 실행하지 않는다. 업그레이드 전에는 새 주문 저장 및 KCP 통보가 실패할 수 있다.
|
||||
2. DB 계정의 `LOCK TABLES`, 관련 테이블의 `SELECT`·`INSERT`·`UPDATE` 권한을 확인한다. 테스트 환경은 운영 주문과 분리한다.
|
||||
3. 기존 KCP 미결제 가상계좌 주문과 개인결제를 KCP 거래 내역에 대조한다. 새 `od_kcp_site_cd`·`pp_kcp_site_cd`는 기존 행에 빈값으로 추가되며, 빈 상점코드인 거래의 통보는 실패 응답한다. **현재 설정을 전체 과거 거래에 일괄 복사하지 않는다.** 거래번호·주문번호·결제수단·청구액·계좌·테스트 여부·실제 상점코드를 확인한 건만 관리자가 별도 보정하고 KCP 재통보를 요청한다. 이미 입금된 기존 거래에는 새 이력을 임의로 만들지 말고 PG 기록과 대조하여 수동 정산한다.
|
||||
4. PC·모바일에서 실제 KCP 테스트 가상계좌 발급과 모의 입금을 확인한 뒤 접수를 재개한다. 기존 연동의 통보 발신 IP 및 처리구분이 아래 공식 안내와 다른 경우 KCP에 해당 연동의 값을 확인한다. 검증을 통째로 생략하거나 임의 전달 헤더로 우회하지 않는다.
|
||||
5. 실패 통보와 `shop_kcp_noti`의 미완료 이력을 점검하고 재통보 제한을 넘긴 건은 KCP 내역과 대조해 복구한다.
|
||||
|
||||
## 입력과 거래 검증
|
||||
|
||||
- 테스트는 `210.122.176.144`, 운영은 `103.215.144.173`, `103.215.144.174`, `210.122.72.173`의 POST만 받는다. 과거 코드의 다른 IP는 자동 승계하지 않는다. 기준은 [KCP 웹훅 가이드](https://developer.kcp.co.kr/guide/webhook), 확인일은 2026-09-10이다. 이 목록이 구형 계약의 모든 발신지를 포함하는지 KCP에 별도 확인한 상태는 아니다.
|
||||
- `REMOTE_ADDR`만 사용한다. 리버스 프록시 환경은 신뢰하는 프록시와 원본 IP 복원 설정을 웹 서버에서 제한해야 한다. 요청 헤더·상점코드·관리자 로그인 자체를 발신 인증으로 취급하지 않는다.
|
||||
- 필수값은 문자열 형식과 길이를 검사하고 배열·빈 거래번호·잘못된 날짜·0원·음수·소수·지수 표기·DB 정수 범위 초과를 거부한다. 가상계좌 입금은 `TX00`, `op_cd=50`, 망취소는 `op_cd=13`으로 구분한다. 다른 처리구분은 실패 응답한다.
|
||||
- PC·모바일 주문과 개인결제 저장 시 서버의 `$g_conf_site_cd`를 보존한다. 테스트 `T0000`·`T0007`, 운영 `SR` 접두어를 포함한 실제 사용값을 비교하므로 이후 상점 설정 변경으로 과거 거래를 다른 상점에 연결하지 않는다.
|
||||
- 저장된 PG·상점·거래번호·가상계좌 결제수단·계좌를 모두 대조한다. 연결 주문은 존재해야 하고 테스트 환경도 일치해야 한다. 개인결제와 일반 주문의 번호가 충돌하면 실패한다.
|
||||
- 일반 입금은 서버 주문 합계에서 쿠폰·포인트 등을 반영한 청구액 전액과 일치하고, 입금액 0·주문 상태여야 한다. 개인결제는 `pp_price`와 대조하며 연결 주문 미수금을 초과할 수 없다. 연결 주문의 원래 PG·결제수단은 개인결제와 다를 수 있으므로 개인결제 자체의 KCP 거래를 검사한다. 연결 주문이 없는 독립 개인결제도 처리한다.
|
||||
- 기존에 처리하지 않던 `TX01`~`TX07` 이벤트는 상점·거래를 확인하고 수신 확인만 한다. 환불·에스크로 상태 자동화 기능을 새로 추가한 것은 아니다.
|
||||
|
||||
## 중복과 중간 실패
|
||||
|
||||
이력 키는 상점·거래·주문·통보 ID·입금/망취소 구분을 포함한다. 같은 통보 ID의 입금과 망취소가 각각 기록되며, 이미 완료한 동일 통보는 주문이 완료 상태가 되었어도 성공 응답한다. 같은 통보의 금액·계좌가 바뀌면 거부한다. 활성 입금이 있을 때 다른 통보 ID로 다시 입금할 수 없고, 망취소 후 지연 도착한 입금 통보도 금액을 다시 올리지 않는다.
|
||||
|
||||
MyISAM과 InnoDB 모두 관련 네 테이블의 쓰기 잠금을 잡고 검증·반영한다. 변경 전 값과 목표값을 `kn_plan`에 먼저 기록하고, 개인결제 금액과 연결 주문 입금액을 목표값으로 저장하여 재시도에서 중복 가산하지 않는다. 주문·장바구니·개인결제 반영과 결과 재조회가 끝난 뒤 `kn_done=1`을 기록해야 `result=0000`을 응답한다. 입력 불일치·스키마/권한 누락·SQL 오류·미완료 복구 충돌은 `9999`로 응답한다.
|
||||
|
||||
**MyISAM의 여러 테이블 쓰기가 원자적 트랜잭션이 되는 것은 아니다.** 중간 실패 시 일부 변경이 남을 수 있으며 같은 통보의 재시도로 완료한다. 물리적 서버 장애·디스크 손상에 대한 롤백도 보장하지 않는다. 재시도 시 현재 값이 기록된 변경 전/후 값과 다르면 외부 변경으로 판단하여 덮어쓰지 않는다. 해당 주문의 미완료 통보가 있으면 다른 신규 통보도 막는다. 테이블 전체 잠금을 사용하므로 통보 처리 중 주문·관리자 쿼리가 잠시 대기할 수 있다.
|
||||
|
||||
망취소는 해당 입금액만 차감하고 미수금을 복원한다. 주문/장바구니의 `입금`은 `주문`으로 되돌린다. 이미 `준비`·`배송`·`완료`인 주문은 금액과 미수금을 수정하되 물류 상태를 되돌리지 않는다. 이런 건은 출고 중지·회수·콘텐츠 권한·지급 포인트 등을 운영자가 별도로 대조해야 한다. 이미 취소·반품된 주문 또는 이력 없이 입금액이 남아 있는 기존 거래는 자동 차감하지 않고 실패로 남긴다.
|
||||
|
||||
복구 점검 예시(실제 설치의 테이블 접두어로 바꾼다):
|
||||
|
||||
```sql
|
||||
SELECT kn_key, kn_trade, kn_noti_id, kn_op_cd, od_id, kn_created_at
|
||||
FROM g5_shop_kcp_noti WHERE kn_done = 0 ORDER BY kn_created_at;
|
||||
```
|
||||
|
||||
미완료 행을 단순 삭제하거나 `kn_done=1`로 바꾸면 중복 가산 또는 미반영 통보 유실이 발생할 수 있다. 주문·개인결제·장바구니의 현재 값과 `kn_plan`, KCP 거래 기록을 대조하고 충돌 원인을 해결한 뒤 동일 통보를 재전송한다. 이력에는 금액·계좌 등 거래 정보가 포함되므로 DB 백업과 접근권한을 기존 주문 데이터와 같이 관리한다.
|
||||
|
||||
## 검증
|
||||
|
||||
```bash
|
||||
python3 tests/kcp_notification_http.py 33443
|
||||
php bin/check-migration-schema.php
|
||||
```
|
||||
|
||||
첫 명령은 **격리 MySQL 전용**이다. 로컬 지정 포트에 root/빈 비밀번호로 접속하여 고유 임시 DB를 만들고 종료 시 삭제한다. PHP CLI·mysqli, MySQL CLI, Python 3이 필요하다. `bin`의 스키마 검사는 로컬 검증 도구로 배포본에 포함되지 않는다.
|
||||
|
||||
테스트는 실제 콜백 파일을 로컬 HTTP로 호출하고 MySQL 8의 MyISAM/InnoDB 테이블을 읽고 쓴다. 공통 초기화와 PG 발신 IP는 테스트 부트스트랩으로 대체한다. 주문·개인결제는 격리 DB에 준비하며 실제 KCP 승인·은행 입금·상점 로그인·전체 주문서 제출을 실행하지 않는다. 각 SQL 쓰기 직전 실패와 DB 반영 후 응답 유실은 테스트 SQL 래퍼에서 주입한다. 정상/거부 응답과 실제 DB 상태, 동시 통보, 중복·망취소·역순, 중간 실패 복구와 외부 수정 충돌, 실제 마이그레이션 적용·재실행을 확인한다.
|
||||
|
||||
2026-09-10 검증 결과: PHP 8.4.22, MySQL 8.0.45에서 HTTP·DB 검사 420개와 PHP 문법 검사, 신규 설치/마이그레이션 스키마 대조가 통과했다.
|
||||
|
||||
### Windows CRLF 설치본 업그레이드 보완
|
||||
|
||||
추가 설치 검증에서 기존 `@skip-if-column` 파서가 CRLF 줄 끝을 인식하지 못해, 이미 존재하는 `cf_email_certify_minutes` 컬럼을 다시 추가하려다 오류 1060으로 중단되는 문제가 재현됐다. 이 선행 오류 때문에 KCP 마이그레이션까지 실행되지 않았다. 파서가 CRLF 및 줄 끝 공백·탭을 허용하도록 수정했다. 인자 구분은 수평 공백으로 제한하여 불완전한 지시문이 다음 줄을 소비하지 않도록 했으며, SQL 파일과 체크섬 산정 방식은 변경하지 않았다.
|
||||
|
||||
`php tests/migration_line_endings.php`로 LF·CRLF, 줄 끝 공백·탭, 파일 끝 지시문, 불완전한 지시문 및 원본 체크섬 보존을 포함한 13개 검사가 통과했다.
|
||||
|
||||
PHP 8.4.25·MySQL 8.0.46의 별도 Windows 프로세스와 전용 DB에서 수정 전 소스를 실제 설치하고, 비회원 PC·회원 모바일 HTTP 주문 2건을 저장한 뒤 수정 소스를 적용했다. 원본 CRLF SQL을 유지한 상태에서 실제 관리자 로그인·CSRF 토큰을 사용한 전체 DB 업그레이드 및 KCP 마이그레이션 재실행이 통과했다. 기존 주문 금액·상태와 과거 상점코드 빈값이 유지됐고, 통보 테이블 컬럼이 신규 설치본과 일치했다. 결과는 `tests/reports/kve-2026-2346-migration-fixed.json`에 기록했다. VM·컨테이너 및 실제 KCP 발급·입금 연동 시험은 포함하지 않았다.
|
||||
|
||||
## 배포·회신 상태
|
||||
|
||||
수정 코드는 5.6.38 배포에 포함한다. 신고 대상은 5.6.35이며 검토 기준 5.6.37 개발 코드에도 원인이 있었다. 전체 영향 버전 범위는 추가 확인이 필요하다. 실제 KCP 연동 시험, 구형 발신 범위 확인, 운영 반영, KISA 회신 완료를 의미하지 않는다. 원본 PDF는 저장소에 포함하지 않는다.
|
||||
|
||||
규격 참고: [가상계좌 입금·망취소](https://developer.kcp.co.kr/reference/account), [웹훅 응답](https://developer.kcp.co.kr/reference/webhook). 공개 규격에서 확인되지 않은 수신 서명 필드를 임의로 필수화하지 않았다. 인증서·서명을 사용하는 서버 간 거래조회 API의 도입은 사용 중인 계약/연동 지원 확인 후 별도로 검토한다.
|
||||
@@ -0,0 +1,87 @@
|
||||
# KVE-2026-2424 LG 가상계좌 입금 통보 위조 차단
|
||||
|
||||
`shop/settle_lg_common.php`가 빈 LG 상점키로 만든 통보를 받아 다른 PG의 미입금 주문을 입금 처리할 수 있던 경로를 차단한다. PC·모바일의 가상계좌 통보는 이 공통 URL을 사용한다. 주문 처리 로직은 `lib/lg_notification.lib.php`로 분리했다.
|
||||
|
||||
## 적용 절차
|
||||
|
||||
1. 파일·DB를 백업하고 신규 주문·결제 접수를 일시 중지한다. 변경 파일 반영과 함께 최고관리자의 **환경설정 → DB업그레이드**에서 `20260918_001_lg_notification`을 적용한다. 새 설치 SQL에도 같은 컬럼과 이력 테이블이 포함된다. 일반 요청에서 DDL을 실행하지 않으며, 스키마 적용 전에는 새 주문 저장이나 통보 처리가 실패할 수 있다.
|
||||
2. DB 계정의 `LOCK TABLES` 및 관련 주문·개인결제·장바구니·통보 이력 테이블의 `SELECT`·`INSERT`·`UPDATE` 권한을 확인한다.
|
||||
3. **기존 미입금 LG 거래를 원장과 대조한다.** `od_lg_mid`·`pp_lg_mid`는 기존 행에 빈값으로 추가되며, 이 상태에서는 통보를 거부한다. 현재 설정을 과거 거래 전체에 일괄 복사하지 않는다. PG 거래 내역의 주문번호·거래번호·상점 MID·운영/테스트 여부·계좌·청구액·입금 여부를 확인한 건만 담당자가 해당 MID를 보정하고 PG 재통보를 요청한다. 운영 MID는 `si_` 접두어, 테스트 MID는 `tsi_` 접두어까지 포함한 실제 값이다. 이미 입금된 기존 거래에는 처리 이력을 임의로 만들지 말고 수동 대조한다.
|
||||
4. 웹서버에서 HTTPS 및 통보 발신지 처리를 확인한다. 애플리케이션은 `REMOTE_ADDR`만 신뢰한다. 프록시를 사용하는 경우 관리자가 신뢰 프록시 범위를 제한하고 원본 IP를 웹서버에서 복원해야 한다. `X-Forwarded-For` 등을 애플리케이션에서 무조건 신뢰하거나 프록시 IP를 PG IP 목록에 추가해서는 안 된다.
|
||||
5. 실제 계약된 XPay 테스트 환경에서 PC·모바일·일반/개인결제의 계좌 발급, 입금, 은행 입금 취소 및 재통보를 확인한 뒤 접수를 재개한다. 특히 계약별 취소 통보의 금액·순번 형식은 아래 지원 범위와 대조한다.
|
||||
6. 실패 통보, 미완료 처리 이력과 출고 후 은행 취소를 감시한다. 현재 설정의 MID/키가 과거 계약과 달라진 거래는 자동 처리하지 않는다. PG 전환만 있었고 LG 인증정보와 저장 거래를 검증할 수 있는 경우에는 기존 LG 거래를 처리한다.
|
||||
|
||||
신고된 위조 통보가 실제로 있었는지는 별도 확인 사항이다. 과거 LG 통보 기록과 입금 원장·주문 상태를 대조하고, 코드 적용만으로 과거 주문 정합성까지 복구됐다고 판단하지 않는다.
|
||||
|
||||
## 인증과 거래 검증
|
||||
|
||||
- 공식 XPay 공통사항 6.2의 운영·테스트 공통 통보 IP 10개에서 온 POST만 받는다. 임의의 클라이언트 IP, 관리자 세션, 전달 헤더는 인증 근거가 아니다.
|
||||
- 설정 MID·상점키가 없으면 거부한다. 요청 MID는 설정에서 계산한 `si_`/`tsi_` MID 중 하나여야 한다. 계약에서 확인되지 않은 임의의 키 길이 조건은 추가하지 않는다.
|
||||
- XPay 응답 규격의 MD5를 동일 길이 상수 시간 비교로 검증한다. MD5 입력에는 입금 플래그·거래번호·통보 순번이 포함되지 않으므로 **해시 검사만으로 전체 요청의 무결성이 보장된다고 간주하지 않는다.** PG 발신지와 서버 저장 거래 검증을 함께 적용한다.
|
||||
- 필수 입력의 문자열 여부·길이·허용 문자, 실제 달력 날짜와 시각, 양의 청구액 및 DB signed INT 범위를 검사한다. 배열·빈 TID·지수 표기·음수·0원·소수·범위 초과는 거부한다.
|
||||
- 결제 당시 서버에서 구성한 `$LGD_MID`를 PC·모바일 일반 주문과 개인결제에 저장한다. 현재 활성 PG나 테스트 설정으로 과거 주문을 판단하지 않는다. 통보 MID와 저장 MID, PG=`lg`, 결제수단=`가상계좌`, 거래번호·계좌를 대조하며 연결 주문의 테스트 여부도 검사한다.
|
||||
- 일반/개인결제 번호가 충돌하거나 연결 주문이 없으면 거부한다. 개인결제에 연결된 주문의 원래 PG는 다를 수 있으므로 개인결제 자체의 LG 거래를 검사한다.
|
||||
- 일반 입금은 서버의 상품·배송비·쿠폰·포인트·취소·환불 계산과 저장 미수금이 일치해야 한다. 미입금 주문의 청구액 전액만 반영한다. 개인결제는 `pp_price` 전액을 반영하며 연결 주문 미수금을 초과할 수 없다.
|
||||
|
||||
## 지원하는 통보와 재처리
|
||||
|
||||
기본 통합결제창의 **일반형 가상계좌 전액 입금**을 대상으로 한다. 고정식 계좌나 부분입금 기능을 새로 지원하는 패치는 아니다.
|
||||
|
||||
| 플래그 | 금액 검증 및 동작 |
|
||||
| --- | --- |
|
||||
| `R` | 누적/현입금액 0. 저장된 거래와 대조한 뒤 수신만 확인하며 입금액을 바꾸지 않는다. |
|
||||
| `I` | `LGD_AMOUNT`, `LGD_CASTAMOUNT`, `LGD_CASCAMOUNT`가 청구액 전액과 같아야 한다. 해당 거래가 미입금 상태인 경우에만 반영한다. |
|
||||
| `C` | 누적액 0, 현입금액 0 또는 청구액 전액인 은행 취소를 처리한다. 확인된 입금 이력이 있으면 해당 금액만 차감한다. 입금보다 먼저 도착하면 취소 이력을 남겨 지연 입금 통보를 차단한다. |
|
||||
|
||||
통보 이력은 MID·TID·주문번호·숫자로 정규화한 `LGD_CASSEQNO`·플래그로 구별한다. 같은 이벤트의 금액·계좌 등 업무 값이 다르면 거부한다. 완료된 이벤트 재전송은 금액을 다시 반영하지 않고 `OK`로 응답한다. 타임스탬프의 임의 만료 시간으로 수동 재통보를 막지 않는다.
|
||||
|
||||
순번은 입금 순서를 나타내는 숫자(최대 6자리)로 취급한다. 같은 순번의 입금보다 취소가 우선하며, 더 낮은 순번의 지연 통보는 업무 데이터를 변경하지 않는다. 취소 후 새 입금은 증가한 순번에서만 허용한다. 계약 환경이 순번을 재사용하거나 다른 취소 금액 형식을 사용하면 원장과 대조하여 연동 정책을 확정해야 하며, 검증을 제거해 우회하지 않는다. 실제 PG 통보의 모든 변형을 확인한 것은 아니다.
|
||||
|
||||
은행 취소 시 입금액·미수금을 복원하고 `입금`·`준비` 상태를 `주문`으로 되돌린다. 이미 `배송`·`완료`인 경우 물류 이력은 유지하고 미수금 및 운영자 대조 필요 로그를 남긴다. 출고 중단·회수·디지털 상품 접근·포인트 회수는 운영자가 별도로 조치해야 한다. 이력 없이 입금액이 남아 있는 과거 거래, 취소·반품 주문, 부분입금과 불일치 금액은 자동으로 추정·차감하지 않는다.
|
||||
|
||||
개인결제의 은행 취소는 활성 입금 이력에 기록된 연결 주문과 현재 연결 주문을 대조한다. 입금 후 연결 주문 변경·해제·추가가 있으면 금액과 이력을 변경하지 않고 `FAIL` 및 연결 충돌 로그를 남긴다. 다른 주문에서 대신 차감하지 않는다. 담당자는 PG 원장과 입금 이력을 대조하고 원래 연결 관계를 복원한 뒤 동일 취소 통보를 재전송한다. 개인결제를 `사용 안 함`으로 바꿔도 기존 입금의 은행 취소는 처리하며, 사용 중지 상태의 신규 입금은 계속 거부한다.
|
||||
|
||||
## 중간 실패와 복구
|
||||
|
||||
MyISAM·InnoDB 모두 관련 네 테이블의 쓰기 잠금 안에서 검증·처리한다. `ln_plan`에 행별 변경 전 값과 목표값을 기록한 다음 개인결제·장바구니·주문을 갱신한다. 재시도에서는 가산 연산 대신 같은 목표값을 저장하므로 개인결제의 연결 주문 금액이 중복 가산되지 않는다. 모든 결과를 다시 읽어 확인하고 `ln_done=1`로 기록해야 `OK`로 응답한다. 실패 응답은 `FAIL`이다.
|
||||
|
||||
이 방식이 MyISAM 여러 테이블을 원자적 트랜잭션으로 바꾸지는 않는다. 중간 실패 시 일부 값이 반영될 수 있고, 같은 통보 재전송으로 나머지를 완료한다. 현재 행이 기록된 변경 전/후 어느 쪽과도 맞지 않으면 외부 변경으로 보고 복구를 중단한다. 미완료 이벤트가 남은 거래 및 연결 주문의 다른 신규 LG 통보도 차단한다. 다른 PG나 관리자 작업에서 발생한 충돌은 원장 대조가 필요하다. 테이블 전체 잠금 동안 주문 및 관리자 쿼리가 잠시 대기할 수 있다.
|
||||
|
||||
복구 점검 SQL(실제 설치의 테이블 접두어로 변경):
|
||||
|
||||
```sql
|
||||
SELECT ln_key, ln_trade, ln_seq, ln_flag, od_id, ln_created_at
|
||||
FROM g5_shop_lg_noti WHERE ln_done = 0 ORDER BY ln_created_at;
|
||||
```
|
||||
|
||||
미완료 이력을 삭제하거나 `ln_done`만 1로 변경하지 않는다. PG 원장과 주문·개인결제·장바구니·`ln_plan`을 대조하고 충돌 원인을 해결한 뒤 동일 통보를 재전송한다. 공식 가이드는 자동 재전송 후에도 처리되지 않은 거래를 구 상점관리자의 전송실패내역에서 재전송할 수 있다고 안내한다. 이력에는 계좌·금액 등 거래 정보가 포함되므로 주문 데이터와 같은 접근·백업 보호를 적용한다.
|
||||
|
||||
## 검증
|
||||
|
||||
```sh
|
||||
python3 tests/lg_notification_http.py 격리_MySQL_포트
|
||||
php tests/migration_line_endings.php
|
||||
php tests/migration_install_history.php 격리_MySQL_소켓
|
||||
```
|
||||
|
||||
HTTP 검사는 localhost 지정 포트의 root/빈 비밀번호인 **별도 테스트 MySQL**에 고유 임시 DB를 만들고 삭제한다. 실제 콜백 파일과 SQL을 실행하되 공통 초기화·PG 발신 IP·SQL 실패 주입만 대역으로 사용한다. Python 3, PHP CLI/mysqli, MySQL CLI가 필요하다. 테스트 파일은 일반 배포 아카이브에서 제외된다.
|
||||
|
||||
2026-09-18: PHP 8.4.22·MySQL 8.0.46의 MyISAM/InnoDB에서 HTTP·DB 645건 통과. 빈 키 제보 조건, 서명/IP/상점/거래/금액 거부, 일반·개인결제, 동시·중복·역순 통보, 은행 취소, 각 쓰기 전 실패·반영 후 응답 유실·실제 SQL 오류와 복구, 외부 수정 충돌, 스키마 누락, 새 설치/기존 업그레이드·재실행·쇼핑몰 미설치 분기를 확인했다. 마이그레이션 줄바꿈 13건 및 기존 신규 설치 이력·체크섬·재실행 검사가 통과했다. 변경 PHP 8개 파일의 구문 검사는 PHP 5.2.17·8.4.22에서 통과했다.
|
||||
|
||||
실제 PG 승인·은행 입금, 전체 주문서 제출, 프록시 설정, MySQL 5.0 실행, 실제 운영 배포는 이 격리 검증에 포함하지 않는다. 자동 거래조회 API는 이번 패치에 추가하지 않았다. 공식 IP·서명과 저장 거래를 함께 검증하고, 불명확한 기존 거래는 원장 대조로 처리한다.
|
||||
|
||||
2026-09-18 추가 보완 검증: PHP 8.4.25·별도 localhost MySQL 8.0.46에서 MyISAM/InnoDB 회귀 검사 715건과 실제 설치의 공통 초기화·DB 함수를 사용하는 검사 391건을 통과했다. 입금 후 개인결제 연결 주문 변경·해제·추가의 차단 및 원래 연결 복원 후 재통보, 사용 중지 후 동시 은행 취소·중복 차감 방지·신규 입금 거부를 추가했다. Windows 실행에 맞춰 테스트 사본의 경로·프로세스 종료를 조정하고 PHP 서버 4개로 동시 요청을 처리했다. 실제 초기화 검사에서도 PG 발신 IP는 로컬 라우터로 대입했으며 실제 PG 통신을 수행한 것은 아니다. 두 검사 집합의 시나리오는 중복되며, 이번 보완은 추가 DB 마이그레이션을 요구하지 않는다.
|
||||
|
||||
## 배포 및 인터넷진흥원 회신
|
||||
|
||||
현재는 코드 수정과 격리 검증을 완료한 상태이다. 공개 배포 버전·일정, 실제 계약 PG 연동 검사, 운영 반영과 KISA 제출은 별도 기록한다.
|
||||
|
||||
> KVE-2026-2424와 관련하여 미설정 LG 인증정보 차단, 통보 발신지·서명·저장 거래·결제금액 검증을 보완하였습니다. 일반 주문과 개인결제에 중복 통보 방지 및 은행 입금 취소 처리를 적용하고, DB 처리 중 실패한 건을 동일 통보로 복구하도록 수정하였습니다. 격리 환경에서 위조 요청 차단, 동시·역순 통보, 중간 실패 복구 및 신규/기존 DB 적용을 검증하였습니다. 기존 미입금 거래는 PG 원장 대조 후 결제 당시 MID를 확인해야 하며, 실제 PG 연동 검증과 공개 배포 버전·일정은 확정 후 별도로 통보하겠습니다.
|
||||
|
||||
## 규격 근거
|
||||
|
||||
2026-09-18에 공식 [구 모듈 가이드](https://docs.tosspayments.com/legacy/guide)에서 연결되는 다음 문서를 확인했다.
|
||||
|
||||
- [XPay 결제모듈 공통사항 v1.0.8](https://docs.google.com/document/d/1kyhcyXmaRuxZAlNJ_Oiuo2EoFVMlQbHZewZW40E6u2w/edit): 6.2의 가상계좌 입금 통보 발신지. [현행 방화벽 안내](https://docs.tosspayments.com/reference/using-api/security)의 인바운드 IP와도 일치한다.
|
||||
- [PC 통합결제창 가상계좌 v1.0.3](https://docs.google.com/document/d/1YZ5AuUVFlxRZriWkq3YlMrjPu6iEJSZFbI2KcNijdac/edit): 일반형 계좌의 일회성 입금, 발급/입금/은행 취소 구분, 통보 응답 및 재전송 안내.
|
||||
- 저장소의 `plugin/lgxpay/lgdacom/XPayClient.php::GetHashData2()`와 기존 콜백의 응답 해시 계산식도 대조했다. 키 길이·자동 조회 API를 임의로 가정하지 않았다.
|
||||
@@ -0,0 +1,41 @@
|
||||
# 보안 업데이트 배포 게시물 초안
|
||||
|
||||
배포 버전과 배포일은 릴리스 확정 후 기재합니다.
|
||||
|
||||
## 변경 사항
|
||||
|
||||
- KVE-2026-2269 / KVE-2026-2331: 비밀번호 재설정·회원가입 인증·이메일 재인증·수신 거부 메일의 보안 링크를 운영자가 지정한 `G5_DOMAIN`으로 생성합니다. 요청의 Host 또는 프록시 전달 헤더를 대체 주소로 사용하지 않습니다.
|
||||
- KVE-2026-2330: 비회원 게시물 수정 시 기존 비밀번호를 확인한 게시물별 세션 인증을 저장 단계에서 검증합니다. 인증은 30분 동안 유효하며 저장 후 폐기됩니다. 비밀번호가 변경된 경우에도 무효화됩니다. 답변 작성 이력이나 일반 쓰기 토큰만으로 수정할 수 없습니다. 1:1문의 토큰 발급은 로그인한 회원의 해당 용도로 제한합니다.
|
||||
- KVE-2026-2329: PHP 첨부 처리에서 XHTML/XHT, SVG/SVGZ, XML/XSL/XSLT, MHT/MHTML, HTC 확장자를 저장 전에 거부합니다. 대소문자, 다중 확장자 및 파일명 정규화도 검사합니다. 게시판·1:1문의·폼메일·쇼핑몰 로고의 기존 SVG 검사 경로에 적용합니다.
|
||||
|
||||
## 업데이트 전 고정 도메인 설정
|
||||
|
||||
`config.php`의 기존 `G5_DOMAIN` 값을 사이트의 실제 공개 주소로 설정하십시오. HTTPS 운영 사이트는 HTTPS 주소를 사용합니다. 하위 디렉터리에 설치했다면 해당 경로까지 포함합니다.
|
||||
|
||||
```php
|
||||
define('G5_DOMAIN', 'https://www.example.com/community');
|
||||
```
|
||||
|
||||
스킴은 `http` 또는 `https`이며 필요하면 포트를 지정할 수 있습니다. 사용자 정보, 쿼리, fragment, 공백·제어문자는 포함할 수 없습니다. 다중 도메인으로 접속하더라도 보안 메일은 이 대표 주소를 사용합니다. 해당 주소에서 인증 페이지와 사이트를 실제로 이용할 수 있는지 확인하십시오.
|
||||
|
||||
`G5_HTTPS_DOMAIN`은 기존 HTTPS 페이지 전환 설정입니다. 보안 메일 주소는 `G5_DOMAIN`만 사용하므로 `G5_HTTPS_DOMAIN`만 설정하는 것으로 대체할 수 없습니다.
|
||||
|
||||
`G5_DOMAIN`이 비어 있거나 유효하지 않으면 비밀번호 재설정 인증값을 변경하거나 메일을 발송하지 않습니다. 회원 존재 여부를 노출하지 않도록 비밀번호 찾기의 응답 문구는 기존과 같습니다. 메일 인증이 필요한 가입·이메일 변경·재발송 및 수신 거부 링크를 포함한 회원메일 발송도 중단되므로 업데이트 전에 설정하십시오. 설정 오류는 서버 PHP 오류 로그에 기록됩니다.
|
||||
|
||||
## CAPTCHA 서비스 설정
|
||||
|
||||
기본환경설정의 캡챠 선택에서 기존 지원 기능인 Google reCAPTCHA V2 또는 Invisible reCAPTCHA 사용을 권장합니다. 서비스 관리 콘솔에서 실제 운영 도메인을 등록하고 선택한 유형에 맞는 Site key와 Secret key를 발급받아 설정하십시오. PC·모바일의 가입, 비밀번호 찾기 및 비회원 글쓰기에서 정상 동작을 확인하십시오.
|
||||
|
||||
이번 업데이트는 기본 Kcaptcha의 음성 자동 복원 문제 자체를 수정하지 않습니다. 게시물 수정 인증은 CAPTCHA 통과 여부와 별도로 검증합니다.
|
||||
|
||||
## 이미 저장된 첨부 및 웹 서버 점검
|
||||
|
||||
신규 업로드 차단만으로 기존 위험 파일이 제거되거나 직접 접근이 차단되지는 않습니다. 운영자는 업데이트 전에 백업을 확보하고 `data/file`, `data/qa` 등 첨부 보관 영역에서 위험 확장자와 다중 확장자 파일을 점검하십시오. DB의 첨부 정보와 실제 파일의 대응 관계를 확인한 뒤 필요한 파일을 웹 공개 영역 밖으로 격리하거나 직접 접근을 차단하십시오. 일괄 삭제로 정상 첨부나 DB 참조를 훼손하지 않도록 주의하십시오.
|
||||
|
||||
Apache에서는 첨부 영역의 실제 경로에 디렉터리 목록 비활성화(`Options -Indexes`), 위험 파일 직접 접근 거부 및 스크립트 실행 방지를 적용하십시오. `.htaccess`의 존재만으로 보호가 보장되지는 않습니다. 해당 경로의 `AllowOverride` 적용 여부와 규칙의 실제 차단 확장자를 확인해야 합니다.
|
||||
|
||||
nginx에서는 첨부 경로의 `autoindex off`와 위험 파일 접근 거부 규칙을 확인하고, PHP 등 실행 핸들러로 첨부 경로가 전달되지 않도록 location 우선순위를 점검하십시오. 서버별 PHP 연동 방식과 경로가 다르므로 운영 환경에 맞춰 적용해야 합니다.
|
||||
|
||||
서버 설정 변경은 운영자가 수행합니다. 적용 후 첨부 직접 URL이 실행되지 않고 디렉터리 목록이 노출되지 않는지 확인하십시오. 직접 URL은 PHP 다운로드 경로를 거치지 않으므로 `download.php`의 다운로드 헤더만으로 보호되지 않습니다. 정상 이미지 표시 및 권한에 따른 일반 첨부 다운로드도 함께 확인하십시오.
|
||||
|
||||
사용자 스킨이나 외부 플러그인이 별도 저장·메일·토큰 경로를 구현했다면 해당 경로에도 같은 검증이 적용되는지 별도 점검이 필요합니다.
|
||||
@@ -0,0 +1,44 @@
|
||||
# SIRK 전용 카카오페이 연동 종료와 기존 고객 지원
|
||||
|
||||
## 적용 범위
|
||||
|
||||
적용 예정 버전은 **5.6.37**이다. `SIRK`로 시작하는 KG이니시스 카카오페이 전용 MID와 주문의 `od_pg = 'KAKAOPAY'` 경로를 정리한다. 일반 KG이니시스/INIpay PRO, NHN KCP, NICEPAY 등 PG에 포함된 카카오페이는 계속 사용할 수 있다.
|
||||
|
||||
기존 이용 고객 사후지원 담당은 **김민섭(minsup@sir.kr)**이다. 실제 고객 안내·거래 확인·지원 완료 여부는 배포 담당자가 별도로 기록한다. 이 문서 작성이나 코드 변경만으로 고객 안내가 완료된 것은 아니다.
|
||||
|
||||
## 변경 내용
|
||||
|
||||
- 쇼핑몰설정의 전용 MID·웹결제 사인키·키패스워드·사용 여부·숨겨진 HashKey 입력과 저장을 제거한다. 기존 전용 설정이 남아 있는 사이트에는 종료와 지원 연락처를 안내한다.
|
||||
- PC·모바일 주문서에서 전용 결제 선택지를 제거하고, 이전 주문서의 POST와 임시 주문 저장도 서버에서 차단한다.
|
||||
- 전용 서명·승인 요청 코드를 제거한다. 이전 결제창의 서명·승인·반환·통보 URL은 HTTP 410과 종료 안내를 반환하며, PG 요청이나 DB 변경, 통보 성공 응답을 수행하지 않는다. 서명 URL은 오류 JSON을 반환한다.
|
||||
- 기존 주문 조회·매출 표시·영수증 표시는 유지한다. 구매자 자동 취소와 관리자 자동 전체·부분취소는 지원 안내로 차단한다. 다른 PG의 기본 분기로 넘기지 않는다.
|
||||
- 운영자가 실제 PG 취소를 확인한 경우에만 주문관리의 확인 항목을 체크하고 상품 상태를 취소·반품·품절로 정리할 수 있다. 이 동작은 PG 취소나 환불금액의 자동 반영을 수행하지 않는다.
|
||||
- 신규 설치 SQL에서 전용 설정 컬럼 5개를 제외한다. 기존 DB의 컬럼·설정값·주문·임시 주문·결제 로그는 삭제하지 않는다. `de_kakaopay_iniapi_key`를 추가하거나 다른 인증키로 치환하지 않는다.
|
||||
- `shop/settle_kakaopay.inc.php`는 이전 테마의 초기화 include 호환을 위해 비활성화 상태만 반환한다. 자동 취소 경로의 이전 파일도 실패 안내용으로 유지한다. 결제용 내부 폼·결과 파일은 제거하므로 커스텀 테마의 직접 참조는 배포 전에 정리한다.
|
||||
|
||||
## 배포 전 진행 중 거래 정리
|
||||
|
||||
1. 담당자가 기존 이용 고객에게 적용 버전과 실제 배포 시각, 지원 연락처를 안내하고 현재 사용 여부와 취소 처리 방식을 확인한다.
|
||||
2. 전용 결제를 사용 중인 사이트는 배포 전 점검 시간을 잡고, 주문 접수 점검으로 새 주문서 진입과 신규 결제 시작을 막는다. 기존 승인·반환 URL은 점검 차단 대상에서 제외한다. 기존 코드에서는 사용 설정을 끄면 진행 중 승인도 차단되므로, 거래 정리 전에 전용 사용 설정부터 해제하지 않는다. 이미 열린 결제창은 별도로 확인한다.
|
||||
3. 이 단계에서는 기존 승인·반환 경로를 유지한다. 진행 중 결제의 종료를 기다리고 이니시스 거래내역과 쇼핑몰 주문·임시 주문·결제 로그를 대조한다. 고정된 대기 시간만으로 완료됐다고 판단하지 않는다.
|
||||
4. PG 승인만 있고 주문이 없는 거래, 처리 중 취소, 미확인 반환 요청이 있으면 담당자가 먼저 해결한다. 미완료 주문의 자동 복구는 종료 버전에서 차단하므로 배포 전에 정리하거나 담당자 지원 대상으로 명시한다.
|
||||
5. 진행 중 거래가 정리된 것을 확인하고 DB·설정·소스를 백업한 뒤 종료 버전을 배포한다. 고객별 배포·안내·미해결 거래 여부를 기록한다.
|
||||
6. 배포 후에도 이전 URL 요청이 도착하면 성공 처리하지 않는다. 재결제를 유도하지 않고 담당자가 PG 승인 여부를 확인해 주문 누락·중복 결제를 처리한다.
|
||||
|
||||
고객의 운영 상황을 확인하지 않은 상태에서 종료 코드를 먼저 배포하면 진행 중 거래의 주문 반영이 중단될 수 있다. 배포 전 확인이 끝나지 않은 고객은 일정을 조정한다.
|
||||
|
||||
## 기존 거래 취소·부분취소 지원
|
||||
|
||||
1. 운영자가 거래 당시의 SIRK MID로 이니시스 상점관리자에 접속하고 주문번호·거래번호·승인금액·기존 취소금액을 대조한다. 이미 취소된 건을 다시 취소하지 않는다.
|
||||
2. 해당 거래의 전체 또는 부분취소 가능 여부를 확인해 처리한다. 상점관리자에서 처리할 수 없는 건은 사후지원 담당자가 KG이니시스와 확인한다.
|
||||
3. 이니시스의 실제 완료 상태·취소금액·잔액을 확인한다. 쇼핑몰 로그의 `cancel`이나 주문 상태만으로 PG 취소 완료를 판단하지 않는다.
|
||||
4. 쇼핑몰 주문관리에서 실제 취소 확인 항목을 체크한 뒤 해당 상품의 상태를 정리한다. 상태 변경만으로 PG 취소가 실행되거나 환불금액이 입력되지는 않는다.
|
||||
5. 주문관리의 결제취소/환불 금액에 PG에서 확인한 **누적 환불금액**을 반영하고 미수금·잔액을 대조한다. 상점메모에 처리 시각·금액·담당자·PG 확인 결과를 기록한다.
|
||||
6. 부분취소는 선택 상품과 세금·배송비·쿠폰 처리 범위를 함께 확인한다. 주문 상태 변경에 따른 재고 복원·포인트 환급과 실제 지급 내역도 대조하여 중복 처리하지 않는다.
|
||||
7. 구매자에게 완료 결과를 안내한다. 주문·임시 주문·로그는 확인 자료로 보존하며, 인증키·개인정보·거래 식별정보를 공개 이슈나 변경 기록에 게시하지 않는다.
|
||||
|
||||
## 검증과 지원 기록
|
||||
|
||||
`php bin/check-sirk-retirement.php`로 이전 결제·취소 요청의 차단, PG 성공 오인 방지, 잔여 참조와 설정 제거를 검사한다. 신규 설치 SQL 변경 후에는 마이그레이션 스키마 검사와 쇼핑몰 설치 검사를 함께 수행한다.
|
||||
|
||||
배포 담당자는 고객별로 안내 일시, 적용 버전/시각, 진행 중 거래 확인 결과, 실제 취소 경로, 지원 상태와 후속 담당자를 비공개로 관리한다. 테스트 결제·실거래 확인과 고객 안내는 코드 검사와 구분해서 기록한다.
|
||||
@@ -80,5 +80,4 @@ add_replace('get_pretty_url', 'add_pretty_shop_url', 10, 5);
|
||||
add_replace('false_short_url_clean', 'shop_short_url_clean', 10, 4);
|
||||
add_replace('add_nginx_conf_rules', 'add_shop_nginx_conf_rules', 10, 3);
|
||||
add_replace('add_mod_rewrite_rules', 'add_shop_mod_rewrite_rules', 10, 3);
|
||||
add_replace('admin_dbupgrade', 'add_shop_admin_dbupgrade', 10, 3);
|
||||
add_replace('exist_check_seo_title', 'shop_exist_check_seo_title', 10, 4);
|
||||
add_replace('exist_check_seo_title', 'shop_exist_check_seo_title', 10, 4);
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 26.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1038.2 200" enable-background="new 0 0 1038.2 200" xml:space="preserve">
|
||||
<g>
|
||||
<path fill="#0064FF" d="M906.8,75.3c-9.5-11-23-17.1-38.1-17.1c-29.6,0-51.9,23.4-51.9,54.5s22.3,54.5,51.9,54.5
|
||||
c15.6,0,29.8-6.7,39.2-18.3v16.9h15.7V59.7h-16.8L906.8,75.3z M871.3,150.8c-21.2,0-36.7-16-36.7-38.1s15.4-38.1,36.7-38.1
|
||||
c20.2,0,35.2,14.4,36.6,35v6.2C906.5,136.4,891.5,150.8,871.3,150.8z"/>
|
||||
<path fill="#0064FF" d="M1019.9,59.3l-34.8,81.5l-33.8-81.5h-18.6l43.1,101.1l-16.6,39.4h17.4l14.3-34.4l47.3-106.1L1019.9,59.3z"
|
||||
/>
|
||||
<path fill="#0064FF" d="M754.3,58.2c-15.6,0-29.8,6.7-39.2,18.3V59.9h-15.7v139.9h16.8v-49.7c9.5,11,23,17.1,38.1,17.1
|
||||
c29.6,0,51.9-23.4,51.9-54.5S783.9,58.2,754.3,58.2L754.3,58.2z M751.7,150.8c-20.2,0-35.2-14.4-36.6-35v-6.2
|
||||
c1.4-20.6,16.4-35,36.6-35c21.2,0,36.7,16,36.7,38.1S773,150.8,751.7,150.8z"/>
|
||||
<path fill="#0064FF" d="M264,126.5V82.7h-22.8v-23H264V31.9l27.9-7.5v35.2h34.8v23h-34.8V126c0,11.5,6.2,18.3,16.9,18.3
|
||||
c6.1,0,10.6-1.2,15.3-4.7l10,20.8c-7.4,5.7-17.1,8.4-27.7,8.4C279.3,168.9,264,153.6,264,126.5L264,126.5z"/>
|
||||
<path fill="#0064FF" d="M335.5,112.8c0-31.9,24.7-55.7,58-55.7s58,23.9,58,55.7c0,31.9-24.7,55.8-58,55.8S335.5,144.7,335.5,112.8z
|
||||
M393.5,143.7c17,0,29.8-13.2,29.8-30.8S410.7,82,393.5,82c-17,0-29.8,13.2-29.8,30.8S376.5,143.7,393.5,143.7L393.5,143.7z"/>
|
||||
<path fill="#0064FF" d="M458.8,140.8l27.7-7.5c1.8,8.6,9.8,13.5,22.7,13.5c10.6,0,18.3-4.8,18.3-11.1c0-4.7-5.4-8-13.7-9.8
|
||||
l-20.7-4.4c-19.6-4.2-31.7-14.6-31.7-29.6C461.4,71,479.5,57,506,57c23.7,0,41.7,10.8,46.5,28.2l-26.1,7.1
|
||||
c-1.7-8.3-9.9-13.8-21.3-13.8c-9.6,0-16,4.5-16,10.8c0,4.9,4.4,7.5,11.5,9.1l24.5,5.6c18.5,4.2,29.7,14.5,29.7,29.5
|
||||
c0,21.9-18.3,35.1-48,35.1C480.8,168.6,462.7,158.1,458.8,140.8L458.8,140.8z"/>
|
||||
<path fill="#0064FF" d="M561.6,140.8l27.7-7.5c1.8,8.6,9.8,13.5,22.7,13.5c10.6,0,18.3-4.8,18.3-11.1c0-4.7-5.4-8-13.7-9.8
|
||||
l-20.7-4.4c-19.6-4.2-31.7-14.6-31.7-29.6c0-20.9,18.1-34.9,44.6-34.9c23.7,0,41.7,10.8,46.5,28.2l-26.1,7.1
|
||||
c-1.7-8.3-9.9-13.8-21.3-13.8c-9.6,0-16,4.5-16,10.8c0,4.9,4.4,7.5,11.5,9.1l24.2,5.5c18.5,4.2,29.7,14.5,29.7,29.5
|
||||
c0,21.9-18.3,35.1-48,35.1C583.5,168.6,565.4,158.1,561.6,140.8z"/>
|
||||
<path fill="#0064FF" d="M118.4,45.4l8.9-41.8c0.4-2.1-1.4-4-3.5-3.5c-12.9,2.3-26.3,6.3-39.6,12C24.4,37.4-11.7,87,3.5,122.9
|
||||
c11.5,26.9,48.6,38.4,91.7,31.7l-8.9,41.8c-0.4,2.1,1.4,4,3.5,3.5c12.9-2.3,26.3-6.3,39.6-12c59.8-25.3,95.9-74.9,80.7-110.9
|
||||
C198.7,50.2,161.6,38.7,118.4,45.4z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
+22
-1
@@ -200,6 +200,7 @@ CREATE TABLE IF NOT EXISTS `g5_config` (
|
||||
`cf_point_term` int(11) NOT NULL DEFAULT '0',
|
||||
`cf_use_copy_log` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`cf_use_email_certify` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`cf_email_certify_minutes` int(11) NOT NULL DEFAULT '60',
|
||||
`cf_login_point` int(11) NOT NULL DEFAULT '0',
|
||||
`cf_cut_name` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`cf_nick_modify` int(11) NOT NULL DEFAULT '0',
|
||||
@@ -222,6 +223,8 @@ CREATE TABLE IF NOT EXISTS `g5_config` (
|
||||
`cf_intercept_ip` text NOT NULL,
|
||||
`cf_analytics` text NOT NULL,
|
||||
`cf_add_meta` text NOT NULL,
|
||||
`cf_syndi_token` varchar(255) NOT NULL DEFAULT '',
|
||||
`cf_syndi_except` text NOT NULL,
|
||||
`cf_member_skin` varchar(50) NOT NULL DEFAULT '',
|
||||
`cf_use_homepage` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`cf_req_homepage` tinyint(4) NOT NULL DEFAULT '0',
|
||||
@@ -987,4 +990,22 @@ CREATE TABLE IF NOT EXISTS `g5_menu` (
|
||||
`me_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`me_mobile_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`me_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `g5_migrations`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `g5_migrations`;
|
||||
CREATE TABLE IF NOT EXISTS `g5_migrations` (
|
||||
`migration_id` varchar(100) NOT NULL,
|
||||
`description` varchar(255) NOT NULL DEFAULT '',
|
||||
`checksum` char(64) NOT NULL DEFAULT '',
|
||||
`status` varchar(20) NOT NULL DEFAULT 'pending',
|
||||
`error_message` text NOT NULL,
|
||||
`execution_ms` int(11) NOT NULL DEFAULT '0',
|
||||
`applied_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`migration_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
+110
-6
@@ -58,6 +58,7 @@ CREATE TABLE IF NOT EXISTS `g5_shop_cart` (
|
||||
`ct_direct` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ct_select` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ct_select_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`ct_complete_time` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`ct_id`),
|
||||
KEY `od_id` (`od_id`),
|
||||
KEY `it_id` (`it_id`),
|
||||
@@ -364,7 +365,7 @@ CREATE TABLE IF NOT EXISTS `g5_shop_default` (
|
||||
`de_inicis_pro_monitor_message` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_iche_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_easy_pay_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_easy_pay_services` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_easy_pay_services` varchar(1024) NOT NULL DEFAULT '',
|
||||
`de_samsung_pay_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_inicis_lpay_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_inicis_kakaopay_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
@@ -384,11 +385,6 @@ CREATE TABLE IF NOT EXISTS `g5_shop_default` (
|
||||
`de_hp_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_escrow_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_tax_flag_use` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`de_kakaopay_mid` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_kakaopay_key` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_kakaopay_enckey` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_kakaopay_hashkey` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_kakaopay_cancelpwd` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_naverpay_mid` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_naverpay_cert_key` varchar(255) NOT NULL DEFAULT '',
|
||||
`de_naverpay_button_key` varchar(255) NOT NULL DEFAULT '',
|
||||
@@ -585,6 +581,9 @@ CREATE TABLE IF NOT EXISTS `g5_shop_item_use` (
|
||||
`is_score` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`is_subject` varchar(255) NOT NULL DEFAULT '',
|
||||
`is_content` text NOT NULL,
|
||||
`is_reply_subject` varchar(255) NOT NULL DEFAULT '',
|
||||
`is_reply_content` text NOT NULL,
|
||||
`is_reply_name` varchar(25) NOT NULL DEFAULT '',
|
||||
`is_time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`is_ip` varchar(25) NOT NULL DEFAULT '',
|
||||
`is_confirm` tinyint(4) NOT NULL DEFAULT '0',
|
||||
@@ -684,6 +683,8 @@ CREATE TABLE IF NOT EXISTS `g5_shop_order` (
|
||||
`od_test` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`od_mobile` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`od_pg` varchar(255) NOT NULL DEFAULT '',
|
||||
`od_kcp_site_cd` varchar(5) NOT NULL DEFAULT '',
|
||||
`od_lg_mid` varchar(104) NOT NULL DEFAULT '',
|
||||
`od_tno` varchar(255) NOT NULL DEFAULT '',
|
||||
`od_app_no` varchar(20) NOT NULL DEFAULT '',
|
||||
`od_escrow` tinyint(4) NOT NULL DEFAULT '0',
|
||||
@@ -782,6 +783,8 @@ CREATE TABLE IF NOT EXISTS `g5_shop_personalpay` (
|
||||
`pp_use` TINYINT(4) NOT NULL DEFAULT '0',
|
||||
`pp_price` INT(11) NOT NULL DEFAULT '0',
|
||||
`pp_pg` varchar(255) NOT NULL DEFAULT '',
|
||||
`pp_kcp_site_cd` varchar(5) NOT NULL DEFAULT '',
|
||||
`pp_lg_mid` varchar(104) NOT NULL DEFAULT '',
|
||||
`pp_tno` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
`pp_app_no` VARCHAR(20) NOT NULL DEFAULT '',
|
||||
`pp_casseqno` VARCHAR(255) NOT NULL DEFAULT '',
|
||||
@@ -899,6 +902,59 @@ CREATE TABLE IF NOT EXISTS `g5_shop_inicis_log` (
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `g5_shop_order_cancel_log`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `g5_shop_order_cancel_log`;
|
||||
CREATE TABLE IF NOT EXISTS `g5_shop_order_cancel_log` (
|
||||
`cl_id` int(11) NOT NULL AUTO_INCREMENT,
|
||||
`od_id` bigint(20) unsigned NOT NULL,
|
||||
`mb_id` varchar(255) NOT NULL DEFAULT '',
|
||||
`cl_pg` varchar(30) NOT NULL DEFAULT '',
|
||||
`cl_pg_version` varchar(30) NOT NULL DEFAULT '',
|
||||
`cl_settle_case` varchar(100) NOT NULL DEFAULT '',
|
||||
`cl_test` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`cl_source` varchar(20) NOT NULL DEFAULT '',
|
||||
`cl_type` varchar(20) NOT NULL DEFAULT '',
|
||||
`cl_status` varchar(30) NOT NULL DEFAULT '',
|
||||
`cl_origin_tno` varchar(255) NOT NULL DEFAULT '',
|
||||
`cl_cancel_tno` varchar(255) NOT NULL DEFAULT '',
|
||||
`cl_pg_order_no` varchar(100) NOT NULL DEFAULT '',
|
||||
`cl_seq` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_pg_seq` varchar(20) NOT NULL DEFAULT '',
|
||||
`cl_idempotency_key` char(32) NOT NULL DEFAULT '',
|
||||
`cl_request_amount` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_approved_amount` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_remaining_amount` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_tax_mny` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_vat_mny` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_free_mny` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_refund_before` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_refund_after` int(11) NOT NULL DEFAULT '0',
|
||||
`cl_result_code` varchar(50) NOT NULL DEFAULT '',
|
||||
`cl_result_msg` varchar(255) NOT NULL DEFAULT '',
|
||||
`cl_reason` varchar(255) NOT NULL DEFAULT '',
|
||||
`cl_request_data` mediumtext NOT NULL,
|
||||
`cl_response_data` mediumtext NOT NULL,
|
||||
`cl_noti_data` mediumtext NOT NULL,
|
||||
`cl_ip` varchar(45) NOT NULL DEFAULT '',
|
||||
`cl_requested_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`cl_approved_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`cl_local_applied_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`cl_created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
`cl_updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`cl_id`),
|
||||
KEY `od_id` (`od_id`),
|
||||
KEY `cl_pg` (`cl_pg`),
|
||||
KEY `cl_pg_version` (`cl_pg_version`),
|
||||
KEY `cl_status` (`cl_status`),
|
||||
KEY `cl_idempotency_key` (`cl_idempotency_key`),
|
||||
KEY `cl_origin_tno` (`cl_origin_tno`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
||||
--
|
||||
-- Table structure for table `g5_shop_inicis_pay`
|
||||
--
|
||||
@@ -985,3 +1041,51 @@ CREATE TABLE IF NOT EXISTS `g5_shop_inicis_pay_event` (
|
||||
KEY `pe_status` (`pe_status`),
|
||||
KEY `pe_created_at` (`pe_created_at`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
-- KCP 통보 처리 및 중간 실패 복구 이력
|
||||
CREATE TABLE `g5_shop_kcp_noti` (
|
||||
`kn_key` char(64) NOT NULL,
|
||||
`kn_trade` char(64) NOT NULL,
|
||||
`kn_noti_id` varchar(20) NOT NULL,
|
||||
`kn_op_cd` char(2) NOT NULL,
|
||||
`kn_payload` char(64) NOT NULL,
|
||||
`od_id` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||
`kn_plan` mediumtext NOT NULL,
|
||||
`kn_done` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`kn_created_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`kn_key`),
|
||||
KEY `kn_trade` (`kn_trade`),
|
||||
KEY `od_id` (`od_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
CREATE TABLE `g5_shop_order_access` (
|
||||
od_id bigint(20) unsigned NOT NULL,
|
||||
token_hash char(64) NOT NULL DEFAULT '',
|
||||
pg varchar(20) NOT NULL DEFAULT '',
|
||||
cart_id bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||
status varchar(20) NOT NULL DEFAULT 'pending',
|
||||
expires bigint(20) NOT NULL DEFAULT '0',
|
||||
updated_at bigint(20) NOT NULL DEFAULT '0',
|
||||
state_json mediumtext NOT NULL,
|
||||
payment_key varchar(200) NOT NULL DEFAULT '',
|
||||
response_json mediumtext NOT NULL,
|
||||
PRIMARY KEY (od_id),
|
||||
KEY cart_status (cart_id,status),
|
||||
KEY state_expiry (status,expires)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
|
||||
-- LG 입금 통보 및 중간 실패 복구 이력
|
||||
CREATE TABLE `g5_shop_lg_noti` (
|
||||
`ln_key` char(64) NOT NULL,
|
||||
`ln_trade` char(64) NOT NULL,
|
||||
`ln_seq` int(11) NOT NULL,
|
||||
`ln_flag` char(1) NOT NULL,
|
||||
`ln_payload` char(64) NOT NULL,
|
||||
`od_id` bigint(20) unsigned NOT NULL DEFAULT '0',
|
||||
`ln_plan` mediumtext NOT NULL,
|
||||
`ln_done` tinyint(4) NOT NULL DEFAULT '0',
|
||||
`ln_created_at` datetime NOT NULL,
|
||||
PRIMARY KEY (`ln_key`),
|
||||
KEY `ln_trade` (`ln_trade`),
|
||||
KEY `od_id` (`od_id`)
|
||||
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
||||
|
||||
@@ -451,3 +451,26 @@ if( ! function_exists('install_file_write') ){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 최신 설치 SQL을 새로 생성한 경우에만 호출한다. 과거 SQL은 실행하지 않는다.
|
||||
function install_record_migrations($table_prefix)
|
||||
{
|
||||
$files = glob(g5_migration_path() . '/*.sql');
|
||||
if (!$files) return '마이그레이션 파일을 찾을 수 없습니다. 배포본의 migrations 디렉터리를 확인해 주십시오.';
|
||||
sort($files, SORT_STRING);
|
||||
$migrations = array();
|
||||
foreach ($files as $file) {
|
||||
$migration = g5_migration_read_file($file);
|
||||
if (isset($migration['error'])) return $migration['error'];
|
||||
$migrations[] = $migration;
|
||||
}
|
||||
$table = $table_prefix . 'migrations';
|
||||
$error = g5_migration_validate_table($table);
|
||||
if ($error !== '') return $error;
|
||||
foreach ($migrations as $migration) {
|
||||
if (!g5_migration_save_record($migration, 'success', '', 0, $table)) {
|
||||
return $migration['id'] . ' 설치 이력을 저장하지 못했습니다: ' . sql_error_info();
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -108,6 +108,18 @@ if ($table_check_error) {
|
||||
install_fail_page('기존 테이블 존재 여부를 확인하지 못했습니다. DB 계정 권한을 확인해 주십시오.', $dblink, $install_table_prefixes);
|
||||
}
|
||||
|
||||
// 기존 테이블을 보존하거나 재사용하는 설치는 DB 업그레이드에서 실제 상태를 검사한다.
|
||||
$install_record_baseline = $is_install === false;
|
||||
$existing_tables = install_query_or_fail('SHOW TABLES', $dblink, '신규 설치 여부를 확인하지 못했습니다.', $install_table_prefixes);
|
||||
while ($existing_table = sql_fetch_array($existing_tables)) {
|
||||
$existing_table_name = reset($existing_table);
|
||||
foreach (array($table_prefix, $g5_shop_prefix, $table_prefix . 'shop_') as $existing_prefix) {
|
||||
if (strpos($existing_table_name, $existing_prefix) === 0) {
|
||||
$install_record_baseline = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 그누보드5 재설치에 체크하였거나 그누보드5가 설치되어 있지 않다면
|
||||
if ($g5_install || $is_install === false) {
|
||||
// 테이블 생성 ------------------------------------
|
||||
@@ -580,6 +592,16 @@ if($g5_shop_install) {
|
||||
<?php
|
||||
//-------------------------------------------------------------------------------------------------
|
||||
|
||||
// 최신 기본 스키마를 생성한 설치에서만 배포 시점의 이력을 등록한다.
|
||||
// 실패하면 dbconfig.php를 만들지 않아 설치 완료로 취급하지 않는다.
|
||||
if ($install_record_baseline) {
|
||||
include_once('../lib/migration.lib.php');
|
||||
$migration_error = install_record_migrations($table_prefix);
|
||||
if ($migration_error !== '') {
|
||||
install_fail_page($migration_error, $dblink, $install_table_prefixes);
|
||||
}
|
||||
}
|
||||
|
||||
// DB 설정 파일 생성
|
||||
$file = '../'.G5_DATA_DIR.'/'.G5_DBCONFIG_FILE;
|
||||
$install_file_write_error = false;
|
||||
@@ -655,13 +677,17 @@ if($g5_shop_install) {
|
||||
install_file_write($f, "\$g5['g5_shop_coupon_log_table'] = G5_SHOP_TABLE_PREFIX.'coupon_log'; // 쿠폰사용정보 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_sendcost_table'] = G5_SHOP_TABLE_PREFIX.'sendcost'; // 추가배송비 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_personalpay_table'] = G5_SHOP_TABLE_PREFIX.'personalpay'; // 개인결제 정보 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_kcp_noti_table'] = G5_SHOP_TABLE_PREFIX.'kcp_noti'; // KCP 통보 처리 이력 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_lg_noti_table'] = G5_SHOP_TABLE_PREFIX.'lg_noti'; // LG 통보 처리 이력 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_order_address_table'] = G5_SHOP_TABLE_PREFIX.'order_address'; // 배송지이력 정보 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_item_stocksms_table'] = G5_SHOP_TABLE_PREFIX.'item_stocksms'; // 재입고SMS 알림 정보 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_post_log_table'] = G5_SHOP_TABLE_PREFIX.'order_post_log'; // 주문요청 로그 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_order_data_table'] = G5_SHOP_TABLE_PREFIX.'order_data'; // 모바일 결제정보 임시저장 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_order_access_table'] = G5_SHOP_TABLE_PREFIX.'order_access'; // 주문 복귀 인증 및 승인 상태 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_inicis_log_table'] = G5_SHOP_TABLE_PREFIX.'inicis_log'; // 이니시스 모바일 계좌이체 로그 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_inicis_pay_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay'; // 이니시스 결제 처리 현황 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_inicis_pay_event_table'] = G5_SHOP_TABLE_PREFIX.'inicis_pay_event'; // 이니시스 결제 처리 이력 테이블\n");
|
||||
install_file_write($f, "\$g5['g5_shop_order_cancel_log_table'] = G5_SHOP_TABLE_PREFIX.'order_cancel_log'; // 주문 취소·환불 이력 테이블\n");
|
||||
}
|
||||
|
||||
install_file_write($f, "?>");
|
||||
|
||||
+13
-4
@@ -70,6 +70,15 @@ function is_disallowed_svg_filename(filename)
|
||||
return /\.(svg|svgz)$/i.test(filename);
|
||||
}
|
||||
|
||||
function is_disallowed_active_filename(filename)
|
||||
{
|
||||
filename = filename || "";
|
||||
if (/[\x00-\x1f\x7f]/.test(filename)) return true;
|
||||
filename = filename.replace(/["'<>=#&!%\\()*+?]/g, "");
|
||||
filename = filename.substring(filename.lastIndexOf("/") + 1).replace(/[ .]+$/, "");
|
||||
return /\.(svgz?|xhtml|xht|xml|xsl|xslt|mht|mhtml|htc)([. :]|$)/i.test(filename);
|
||||
}
|
||||
|
||||
function is_svg_upload_target(input)
|
||||
{
|
||||
var name = input.name || "";
|
||||
@@ -92,7 +101,7 @@ function has_disallowed_svg_file(input)
|
||||
|
||||
if (input.files && input.files.length) {
|
||||
for (i=0; i<input.files.length; i++) {
|
||||
if (is_disallowed_svg_filename(input.files[i].name)) {
|
||||
if (is_disallowed_active_filename(input.files[i].name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -100,7 +109,7 @@ function has_disallowed_svg_file(input)
|
||||
return false;
|
||||
}
|
||||
|
||||
return is_disallowed_svg_filename(input.value);
|
||||
return is_disallowed_active_filename(input.value);
|
||||
}
|
||||
|
||||
function check_disallowed_svg_upload(form)
|
||||
@@ -118,7 +127,7 @@ function check_disallowed_svg_upload(form)
|
||||
return true;
|
||||
}
|
||||
|
||||
alert("허용되지 않는 파일 확장자입니다. (svg, svgz)");
|
||||
alert("허용되지 않는 파일 확장자입니다.");
|
||||
$(form).find("input:submit, button:submit, input:image").prop("disabled", false);
|
||||
invalid_file.focus();
|
||||
|
||||
@@ -799,7 +808,7 @@ $(function() {
|
||||
return true;
|
||||
}
|
||||
|
||||
alert("허용되지 않는 파일 확장자입니다. (svg, svgz)");
|
||||
alert("허용되지 않는 파일 확장자입니다.");
|
||||
this.value = "";
|
||||
|
||||
return false;
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/* 주문 임시저장 응답의 토큰은 현재 탭의 결제 폼에만 전달한다. */
|
||||
function g5_order_state_accept(xhr) {
|
||||
var token = xhr.getResponseHeader('X-G5-Order-State');
|
||||
if (!token || !/^[0-9]{1,20}\.[a-f0-9]{64}$/.test(token)) {
|
||||
return '결제 요청을 저장하지 못했습니다. 다시 시도해 주십시오.';
|
||||
}
|
||||
['forderform', 'sm_form', 'nhnkcp_pay_form'].forEach(function(name) {
|
||||
var form = document.forms[name];
|
||||
if (!form) return;
|
||||
var input = form.elements.g5_order_state;
|
||||
if (!input) {
|
||||
input = document.createElement('input');
|
||||
input.type = 'hidden'; input.name = 'g5_order_state'; form.appendChild(input);
|
||||
}
|
||||
input.value = token;
|
||||
});
|
||||
return '';
|
||||
}
|
||||
|
||||
function g5_order_state_url(url, form) {
|
||||
var input = form.elements.g5_order_state;
|
||||
if (!input || !/^[0-9]{1,20}\.[a-f0-9]{64}$/.test(input.value)) {
|
||||
throw new Error('결제 요청을 다시 저장해 주십시오.');
|
||||
}
|
||||
return url + (url.indexOf('?') < 0 ? '?' : '&') + 'g5_order_state=' + encodeURIComponent(input.value);
|
||||
}
|
||||
+35
-66
@@ -2,98 +2,67 @@
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
Class G5_object_cache {
|
||||
/** @deprecated 직접 접근 대신 get/set/exists/delete 메서드를 사용한다. */
|
||||
public $writes = array();
|
||||
/** @deprecated 직접 접근 대신 get/set/exists/delete 메서드를 사용한다. */
|
||||
public $contents = array();
|
||||
/** @deprecated 타입별 $etcs[$type][$group][$key] 구조. 메서드 사용 권장. */
|
||||
public $etcs = array();
|
||||
|
||||
function get($type, $key, $group ='default') {
|
||||
|
||||
switch ($type) {
|
||||
case 'bbs':
|
||||
$datas = $this->writes;
|
||||
break;
|
||||
case 'content' :
|
||||
$datas = $this->contents;
|
||||
break;
|
||||
default :
|
||||
$datas = $this->etcs;
|
||||
break;
|
||||
// 각 타입의 유일한 저장소를 참조로 반환한다. 빈 타입 버킷은 요청 종료까지 유지한다.
|
||||
private function &get_type_cache($type) {
|
||||
if ($type === 'bbs') {
|
||||
return $this->writes;
|
||||
}
|
||||
|
||||
if( $this->exists($type, $key, $group) ){
|
||||
if ( is_object($datas[$group][$key]) )
|
||||
return clone $datas[$group][$key];
|
||||
else
|
||||
return $datas[$group][$key];
|
||||
if ($type === 'content') {
|
||||
return $this->contents;
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!isset($this->etcs[$type])) {
|
||||
$this->etcs[$type] = array();
|
||||
}
|
||||
return $this->etcs[$type];
|
||||
}
|
||||
|
||||
function exists($type, $key, $group = 'default' ) {
|
||||
|
||||
$return_data = '';
|
||||
|
||||
switch ($type) {
|
||||
case 'bbs':
|
||||
$datas = $this->writes;
|
||||
break;
|
||||
case 'content':
|
||||
$datas = $this->contents;
|
||||
break;
|
||||
default :
|
||||
$datas = $this->etcs;
|
||||
break;
|
||||
function get($type, $key, $group = 'default') {
|
||||
if (!$this->exists($type, $key, $group)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($datas[$group]) && ( isset($datas[$group][$key]) || array_key_exists($key, $datas[$group]) );
|
||||
$datas = &$this->get_type_cache($type);
|
||||
if (is_object($datas[$group][$key])) {
|
||||
return clone $datas[$group][$key];
|
||||
}
|
||||
return $datas[$group][$key];
|
||||
}
|
||||
|
||||
function set($type, $key, $data=array(), $group='default') {
|
||||
if ( is_object( $data ) )
|
||||
function exists($type, $key, $group = 'default') {
|
||||
$datas = &$this->get_type_cache($type);
|
||||
return isset($datas[$group]) && (isset($datas[$group][$key]) || array_key_exists($key, $datas[$group]));
|
||||
}
|
||||
|
||||
function set($type, $key, $data = array(), $group = 'default') {
|
||||
if (is_object($data)) {
|
||||
$data = clone $data;
|
||||
|
||||
switch ($type) {
|
||||
case 'bbs':
|
||||
$this->writes[$group][$key] = $data;
|
||||
break;
|
||||
case 'content':
|
||||
$this->contents[$group][$key] = $data;
|
||||
break;
|
||||
default :
|
||||
$this->etcs[$group][$key] = $data;
|
||||
break;
|
||||
}
|
||||
|
||||
$datas = &$this->get_type_cache($type);
|
||||
$datas[$group][$key] = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* cache 데이터 제거
|
||||
* 지정한 타입, 그룹, 키의 캐시 데이터만 제거한다.
|
||||
* @param string $type
|
||||
* @param string $key
|
||||
* @param string $group
|
||||
* @return bool
|
||||
*/
|
||||
function delete($type, $key, $group = 'default')
|
||||
{
|
||||
function delete($type, $key, $group = 'default') {
|
||||
if (!$this->exists($type, $key, $group)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case 'bbs':
|
||||
$datas = &$this->writes;
|
||||
break;
|
||||
case 'content':
|
||||
$datas = &$this->contents;
|
||||
break;
|
||||
default:
|
||||
$datas = &$this->etcs;
|
||||
break;
|
||||
}
|
||||
|
||||
$datas = &$this->get_type_cache($type);
|
||||
unset($datas[$group][$key]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} //end Class;
|
||||
}
|
||||
|
||||
+196
-33
@@ -2696,6 +2696,121 @@ function get_email_cert_key($mb_id, $mb_datetime)
|
||||
return hash_hmac('sha256', $payload, $key);
|
||||
}
|
||||
|
||||
// 보안 메일은 요청 헤더가 아닌 운영자가 지정한 공개 URL만 사용한다.
|
||||
function g5_security_mail_base_url($domain = null)
|
||||
{
|
||||
if ($domain === null) {
|
||||
$domain = defined('G5_DOMAIN') ? G5_DOMAIN : '';
|
||||
}
|
||||
if (!is_string($domain) || $domain === '' || preg_match('/[\x00-\x20\x7f\\\\<>"\']/', $domain)) {
|
||||
return false;
|
||||
}
|
||||
$parts = @parse_url($domain);
|
||||
if (!$parts || empty($parts['scheme']) || !in_array(strtolower($parts['scheme']), array('http', 'https'), true)
|
||||
|| empty($parts['host']) || isset($parts['user']) || isset($parts['pass'])
|
||||
|| isset($parts['query']) || isset($parts['fragment'])) {
|
||||
return false;
|
||||
}
|
||||
// 호스트에는 포트 구분용 IPv6 대괄호 외의 URL 구문을 허용하지 않는다.
|
||||
$host = $parts['host'];
|
||||
if ($host[0] === '[') {
|
||||
if (substr($host, -1) !== ']' || !filter_var(substr($host, 1, -1), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) return false;
|
||||
} elseif (!preg_match('/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9.])?$/i', $host)) {
|
||||
return false;
|
||||
}
|
||||
if ($host[0] !== '[') {
|
||||
if (strlen($host) > 254) return false;
|
||||
$hostname = substr($host, -1) === '.' ? substr($host, 0, -1) : $host;
|
||||
foreach (explode('.', $hostname) as $label) {
|
||||
if (!preg_match('/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/i', $label)) return false;
|
||||
}
|
||||
}
|
||||
if (isset($parts['port']) && ($parts['port'] < 1 || $parts['port'] > 65535)) return false;
|
||||
if (isset($parts['path']) && (preg_match('/%(?:0[0-9a-f]|1[0-9a-f]|7f|2f|5c)/i', $parts['path'])
|
||||
|| preg_match('~(?:^|/)(?:\.|%2e){1,2}(?:/|$)~i', $parts['path']))) return false;
|
||||
|
||||
return rtrim($domain, '/');
|
||||
}
|
||||
|
||||
function g5_require_security_mail_url()
|
||||
{
|
||||
$url = g5_security_mail_base_url();
|
||||
if ($url === false) {
|
||||
error_log('[g5 security mail] Valid G5_DOMAIN is required.');
|
||||
alert('메일 인증을 위한 사이트 주소가 설정되지 않았습니다. 사이트 관리자에게 문의해 주십시오.');
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 발급 시각을 포함한 메일 인증용 일회용 토큰을 생성한다.
|
||||
function get_email_certify_token()
|
||||
{
|
||||
return get_random_token_string(16) . '.' . G5_SERVER_TIME;
|
||||
}
|
||||
|
||||
// 기존 형식 또는 폐기된 토큰은 회원가입 시각을 기준으로 한다.
|
||||
function get_email_certify_issued_at($stored_token, $mb_datetime)
|
||||
{
|
||||
if (preg_match('/\.([0-9]{10})$/', $stored_token, $matches)) {
|
||||
return (int) $matches[1];
|
||||
}
|
||||
|
||||
return strtotime($mb_datetime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 메일 인증 토큰의 일치 여부와 유효시간을 확인한다.
|
||||
*
|
||||
* 발급 시각이 없는 기존 토큰은 회원가입 시각을 기준으로 만료 여부를 확인한다.
|
||||
*
|
||||
* @param string $token
|
||||
* @param string $stored_token
|
||||
* @param string $mb_datetime
|
||||
* @param int $valid_minutes
|
||||
* @param int|null $now
|
||||
* @return bool
|
||||
*/
|
||||
function is_valid_email_certify_token($token, $stored_token, $mb_datetime, $valid_minutes, $now = null)
|
||||
{
|
||||
if (!$token || !$stored_token || !slow_equals((string) $stored_token, (string) $token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$valid_minutes = (int) $valid_minutes;
|
||||
if ($valid_minutes < 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$issued_at = get_email_certify_issued_at($stored_token, $mb_datetime);
|
||||
|
||||
if (!$issued_at) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$now = $now === null ? G5_SERVER_TIME : (int) $now;
|
||||
|
||||
return $issued_at + ($valid_minutes * 60) >= $now;
|
||||
}
|
||||
|
||||
// 인증 완료·탈퇴·삭제 회원과 만료시간을 사용하지 않는 사이트는 제외한다.
|
||||
function is_expired_email_certify_member($mb, $now = null)
|
||||
{
|
||||
global $config;
|
||||
|
||||
$valid_minutes = isset($config['cf_email_certify_minutes']) ? (int) $config['cf_email_certify_minutes'] : 60;
|
||||
if (!$config['cf_use_email_certify'] || $valid_minutes < 1 || empty($mb['mb_id'])
|
||||
|| $mb['mb_id'] === $config['cf_admin'] || !empty($mb['mb_leave_date'])
|
||||
|| preg_match('/[1-9]/', $mb['mb_email_certify'])
|
||||
|| preg_match('#^[0-9]{8}.*삭제함#', $mb['mb_memo'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$issued_at = get_email_certify_issued_at($mb['mb_email_certify2'], $mb['mb_datetime']);
|
||||
$now = $now === null ? G5_SERVER_TIME : (int) $now;
|
||||
|
||||
return $issued_at > 0 && $issued_at + ($valid_minutes * 60) < $now;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSRF 방지용 Origin/Referer 검증 (OWASP 권장 패턴).
|
||||
*
|
||||
@@ -3615,21 +3730,6 @@ function insert_member_cert_history($mb_id, $name, $hp, $birth, $type)
|
||||
$g5['member_cert_history_table'] = G5_TABLE_PREFIX.'member_cert_history';
|
||||
}
|
||||
|
||||
// 멤버 본인인증 정보 변경 내역 테이블 없을 경우 생성
|
||||
if(isset($g5['member_cert_history_table']) && !sql_query(" DESC {$g5['member_cert_history_table']} ", false)) {
|
||||
sql_query(" CREATE TABLE IF NOT EXISTS `{$g5['member_cert_history_table']}` (
|
||||
`ch_id` int(11) NOT NULL auto_increment,
|
||||
`mb_id` varchar(20) NOT NULL DEFAULT '',
|
||||
`ch_name` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_hp` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_birth` varchar(255) NOT NULL DEFAULT '',
|
||||
`ch_type` varchar(20) NOT NULL DEFAULT '',
|
||||
`ch_datetime` datetime NOT NULL default '0000-00-00 00:00:00',
|
||||
PRIMARY KEY (`ch_id`),
|
||||
KEY `mb_id` (`mb_id`)
|
||||
) ", true);
|
||||
}
|
||||
|
||||
$sql = " insert into {$g5['member_cert_history_table']}
|
||||
set mb_id = '{$mb_id}',
|
||||
ch_name = '{$name}',
|
||||
@@ -4016,6 +4116,50 @@ function conv_unescape_nl($str)
|
||||
return str_replace($search, $replace, $str);
|
||||
}
|
||||
|
||||
// 자진 탈퇴와 메일 미인증 만료 정리에서 동일한 탈퇴 처리를 사용한다.
|
||||
function g5_leave_member($mb_id, $expired_email_only = false)
|
||||
{
|
||||
global $config, $g5;
|
||||
|
||||
$mb = get_member($mb_id);
|
||||
if (empty($mb['mb_id']) || $mb['mb_id'] === $config['cf_admin'] || !empty($mb['mb_leave_date'])
|
||||
|| preg_match('#^[0-9]{8}.*삭제함#', $mb['mb_memo'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$condition = '';
|
||||
if ($expired_email_only) {
|
||||
if (!is_expired_email_certify_member($mb)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 조회 이후 인증 완료·재발송·회원정보 변경이 있었다면 탈퇴시키지 않는다.
|
||||
foreach (array('mb_email_certify', 'mb_email_certify2', 'mb_datetime', 'mb_email') as $field) {
|
||||
$condition .= " and {$field} = '".sql_real_escape_string($mb[$field])."'";
|
||||
}
|
||||
}
|
||||
|
||||
$date = date('Ymd', G5_SERVER_TIME);
|
||||
$memo = $date . ($expired_email_only ? ' 메일 미인증 만료로 탈퇴함' : ' 탈퇴함') . "\n";
|
||||
$esc_mb_id = sql_real_escape_string($mb['mb_id']);
|
||||
$sql = " update {$g5['member_table']}
|
||||
set mb_leave_date = '{$date}',
|
||||
mb_memo = CONCAT('".sql_real_escape_string($memo)."', IFNULL(mb_memo, '')),
|
||||
mb_certify = '', mb_adult = 0, mb_dupinfo = '',
|
||||
mb_email_certify2 = '', mb_lost_certify = ''
|
||||
where mb_id = '{$esc_mb_id}' and mb_leave_date = '' {$condition} ";
|
||||
if (!sql_query($sql) || get_sql_affected_rows() < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
run_event('member_leave', $mb);
|
||||
if (function_exists('social_member_link_delete')) {
|
||||
social_member_link_delete($mb['mb_id']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 회원 삭제
|
||||
function member_delete($mb_id)
|
||||
{
|
||||
@@ -4092,7 +4236,18 @@ function get_safe_filename($name)
|
||||
return $name;
|
||||
}
|
||||
|
||||
// 업로드 파일명이 SVG 또는 SVGZ 확장자인지 확인
|
||||
// 브라우저에서 실행될 수 있는 첨부 확장자를 저장 전에 거부한다.
|
||||
function is_disallowed_active_filename($filename)
|
||||
{
|
||||
if (!is_string($filename)) return true;
|
||||
if (preg_match('/[\x00-\x1f\x7f]/', $filename)) return true;
|
||||
$filename = basename(str_replace('\\', '/', $filename));
|
||||
$filename = rtrim($filename, ' .');
|
||||
// 다중 확장자 및 Windows 대체 데이터 스트림 표기도 차단한다.
|
||||
return (bool) preg_match('/\.(?:svgz?|xhtml|xht|xml|xsl|xslt|mht|mhtml|htc)(?:[. :]|$)/i', $filename);
|
||||
}
|
||||
|
||||
// 기존 스킨 및 플러그인과의 호환을 위한 SVG 검사 함수
|
||||
function is_disallowed_svg_filename($filename)
|
||||
{
|
||||
if (!is_string($filename) || $filename === '') {
|
||||
@@ -4187,21 +4342,6 @@ function login_password_check($mb, $pass, $hash)
|
||||
if(!$mb_id)
|
||||
return false;
|
||||
|
||||
if(G5_STRING_ENCRYPT_FUNCTION === 'create_hash' && (strlen($hash) === G5_MYSQL_PASSWORD_LENGTH || strlen($hash) === 16)) {
|
||||
if( sql_password($pass) === $hash ){
|
||||
|
||||
if( ! isset($mb['mb_password2']) ){
|
||||
$sql = "ALTER TABLE `{$g5['member_table']}` ADD `mb_password2` varchar(255) NOT NULL default '' AFTER `mb_password`";
|
||||
sql_query($sql);
|
||||
}
|
||||
|
||||
$new_password = create_hash($pass);
|
||||
$sql = " update {$g5['member_table']} set mb_password = '$new_password', mb_password2 = '$hash' where mb_id = '$mb_id' ";
|
||||
sql_query($sql);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return check_password($pass, $hash);
|
||||
}
|
||||
|
||||
@@ -4562,7 +4702,29 @@ function get_sql_affected_rows($link=null)
|
||||
}
|
||||
}
|
||||
|
||||
// 불법접근을 막도록 토큰을 생성하면서 토큰값을 리턴
|
||||
// 비회원의 기존 비밀번호 확인 이력을 게시판과 게시물에 결합한다.
|
||||
function g5_write_edit_auth_key($bo_table, $wr_id)
|
||||
{
|
||||
return 'ss_write_edit_'.hash('sha256', $bo_table.':'.(int)$wr_id);
|
||||
}
|
||||
|
||||
function g5_grant_write_edit_auth($bo_table, $write)
|
||||
{
|
||||
set_session(g5_write_edit_auth_key($bo_table, $write['wr_id']), array(
|
||||
'expires' => G5_SERVER_TIME + 1800,
|
||||
'password' => hash('sha256', $write['wr_password'])
|
||||
));
|
||||
}
|
||||
|
||||
function g5_has_write_edit_auth($bo_table, $write)
|
||||
{
|
||||
$auth = get_session(g5_write_edit_auth_key($bo_table, $write['wr_id']));
|
||||
return empty($write['mb_id']) && is_array($auth) && isset($auth['expires'], $auth['password'])
|
||||
&& $auth['expires'] >= G5_SERVER_TIME
|
||||
&& slow_equals(hash('sha256', $write['wr_password']), $auth['password']);
|
||||
}
|
||||
|
||||
// 글쓰기 요청의 CSRF 토큰을 발급한다. 수정 권한은 별도로 확인한다.
|
||||
function get_write_token($bo_table)
|
||||
{
|
||||
$token = get_random_token_string(16);
|
||||
@@ -4581,7 +4743,8 @@ function check_write_token($bo_table)
|
||||
$token = get_session('ss_write_'.$bo_table.'_token');
|
||||
set_session('ss_write_'.$bo_table.'_token', '');
|
||||
|
||||
if(!$token || !$_REQUEST['token'] || $token != $_REQUEST['token'])
|
||||
if (!is_string($token) || !$token || !isset($_POST['token']) || !is_string($_POST['token'])
|
||||
|| !slow_equals($token, $_POST['token']))
|
||||
alert('올바른 방법으로 이용해 주십시오.', G5_URL);
|
||||
|
||||
return true;
|
||||
|
||||
@@ -0,0 +1,391 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
|
||||
// KVE-2026-2346: 테스트 통보도 발신지와 저장된 거래를 모두 검증한다.
|
||||
function kcp_noti_request($post, $server, $default)
|
||||
{
|
||||
if (!isset($server['REQUEST_METHOD']) || $server['REQUEST_METHOD'] !== 'POST') {
|
||||
return false;
|
||||
}
|
||||
// https://developer.kcp.co.kr/guide/webhook (2026-09-10 확인)
|
||||
$ips = !empty($default['de_card_test'])
|
||||
? array('210.122.176.144')
|
||||
: array('103.215.144.173', '103.215.144.174', '210.122.72.173');
|
||||
if (!isset($server['REMOTE_ADDR']) || !in_array($server['REMOTE_ADDR'], $ips, true)) {
|
||||
return false;
|
||||
}
|
||||
// 전달 헤더나 로그인 세션은 PG 발신 인증에 사용하지 않는다.
|
||||
$patterns = array(
|
||||
'site_cd' => '/\A[A-Z0-9]{5}\z/',
|
||||
'tno' => '/\A[0-9]{14}\z/',
|
||||
'order_no' => '/\A[1-9][0-9]{0,19}\z/',
|
||||
'tx_cd' => '/\ATX0[0-7]\z/',
|
||||
'tx_tm' => '/\A[0-9]{14}\z/'
|
||||
);
|
||||
if (isset($post['tx_cd']) && $post['tx_cd'] === 'TX00') {
|
||||
$patterns += array(
|
||||
'ipgm_mnyx' => '/\A[0-9]{1,12}\z/',
|
||||
'account' => '/\A[T]?[0-9]{1,19}\z/',
|
||||
'noti_id' => '/\A[0-9]{20}\z/',
|
||||
'op_cd' => '/\A(?:50|13)\z/'
|
||||
);
|
||||
}
|
||||
$data = array();
|
||||
foreach ($patterns as $name => $pattern) {
|
||||
if (!isset($post[$name]) || !is_string($post[$name]) || !preg_match($pattern, $post[$name])) {
|
||||
return false;
|
||||
}
|
||||
$data[$name] = $post[$name];
|
||||
}
|
||||
$time = $data['tx_tm'];
|
||||
if ((int) substr($time, 0, 4) < 1000 || !checkdate((int) substr($time, 4, 2), (int) substr($time, 6, 2), (int) substr($time, 0, 4))
|
||||
|| substr($time, 8, 2) > 23 || substr($time, 10, 2) > 59 || substr($time, 12, 2) > 59) {
|
||||
return false;
|
||||
}
|
||||
$test = in_array($data['site_cd'], array('T0000', 'T0007'), true);
|
||||
if ($test !== !empty($default['de_card_test'])) {
|
||||
return false;
|
||||
}
|
||||
if (!$test && substr($data['site_cd'], 0, 2) !== 'SR') {
|
||||
return false;
|
||||
}
|
||||
if ($data['tx_cd'] === 'TX00') {
|
||||
// 주문 금액 컬럼은 signed INT이다. 변환 전에 범위를 검사한다.
|
||||
if ((float) $data['ipgm_mnyx'] < 1 || (float) $data['ipgm_mnyx'] > 2147483647) {
|
||||
return false;
|
||||
}
|
||||
$data['ipgm_mnyx'] = (string) (int) $data['ipgm_mnyx'];
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
function kcp_noti_query($sql)
|
||||
{
|
||||
$result = sql_query($sql, false);
|
||||
if (!$result) {
|
||||
throw new Exception('KCP notification database failure');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function kcp_noti_rows($sql)
|
||||
{
|
||||
$result = kcp_noti_query($sql);
|
||||
$rows = array();
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function kcp_noti_quote($value)
|
||||
{
|
||||
return "'".sql_escape_string((string) $value)."'";
|
||||
}
|
||||
|
||||
function kcp_noti_tables()
|
||||
{
|
||||
global $g5;
|
||||
return array('order' => $g5['g5_shop_order_table'], 'personal' => $g5['g5_shop_personalpay_table'],
|
||||
'cart' => $g5['g5_shop_cart_table'],
|
||||
'event' => isset($g5['g5_shop_kcp_noti_table']) ? $g5['g5_shop_kcp_noti_table'] : G5_SHOP_TABLE_PREFIX.'kcp_noti');
|
||||
}
|
||||
|
||||
function kcp_noti_process($data)
|
||||
{
|
||||
$tables = kcp_noti_tables();
|
||||
$locked = false;
|
||||
try {
|
||||
// 기본 설치의 MyISAM에서도 다른 통보 및 관리자 UPDATE와 경쟁하지 않게 한다.
|
||||
// DDL은 관리자 DB 업그레이드에서만 실행하며 스키마/권한이 없으면 실패 응답한다.
|
||||
$locks = array();
|
||||
foreach ($tables as $table) {
|
||||
$locks[] = '`'.$table.'` WRITE';
|
||||
}
|
||||
kcp_noti_query('LOCK TABLES '.implode(', ', $locks));
|
||||
$locked = true;
|
||||
$success = kcp_noti_locked($data, $tables);
|
||||
} catch (Exception $e) {
|
||||
error_log('KCP notification: processing failed; reconciliation/retry required');
|
||||
$success = false;
|
||||
} catch (Throwable $e) {
|
||||
error_log('KCP notification: processing failed; reconciliation/retry required');
|
||||
$success = false;
|
||||
}
|
||||
if ($locked) {
|
||||
try {
|
||||
kcp_noti_query('UNLOCK TABLES');
|
||||
} catch (Exception $e) {
|
||||
$success = false;
|
||||
} catch (Throwable $e) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
// 변경 전/후 값을 함께 기록한다. 재시도는 가산 SQL 대신 목표값을 대입한다.
|
||||
function kcp_noti_step($table, $id_field, $row, $changes, $guards)
|
||||
{
|
||||
$before = array();
|
||||
foreach (array_unique(array_merge(array($id_field), array_keys($changes), $guards)) as $field) {
|
||||
if (!array_key_exists($field, $row)) {
|
||||
throw new Exception('Missing notification schema');
|
||||
}
|
||||
$before[$field] = (string) $row[$field];
|
||||
}
|
||||
$after = $before;
|
||||
foreach ($changes as $field => $value) {
|
||||
$after[$field] = (string) $value;
|
||||
}
|
||||
return array('table' => $table, 'id_field' => $id_field, 'id' => (string) $row[$id_field],
|
||||
'before' => $before, 'after' => $after);
|
||||
}
|
||||
|
||||
function kcp_noti_matches($row, $expected)
|
||||
{
|
||||
foreach ($expected as $field => $value) {
|
||||
if (!array_key_exists($field, $row) || (string) $row[$field] !== $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function kcp_noti_apply($plan, $tables)
|
||||
{
|
||||
// 먼저 모든 행을 검사한다. 실패 후 외부에서 수정한 행을 과거 값으로 덮어쓰지 않는다.
|
||||
foreach ($plan as $step) {
|
||||
$rows = kcp_noti_rows('SELECT * FROM `'.$tables[$step['table']].'` WHERE `'.$step['id_field'].'` = '.kcp_noti_quote($step['id']));
|
||||
if (count($rows) !== 1 || (!kcp_noti_matches($rows[0], $step['before']) && !kcp_noti_matches($rows[0], $step['after']))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach ($plan as $step) {
|
||||
$sets = array();
|
||||
foreach ($step['after'] as $field => $value) {
|
||||
if ($value !== $step['before'][$field]) {
|
||||
$sets[] = '`'.$field.'` = '.kcp_noti_quote($value);
|
||||
}
|
||||
}
|
||||
if ($sets) {
|
||||
$sql = 'UPDATE `'.$tables[$step['table']].'` SET '.implode(', ', $sets)
|
||||
.' WHERE `'.$step['id_field'].'` = '.kcp_noti_quote($step['id']);
|
||||
kcp_noti_query($sql);
|
||||
}
|
||||
}
|
||||
foreach ($plan as $step) {
|
||||
$rows = kcp_noti_rows('SELECT * FROM `'.$tables[$step['table']].'` WHERE `'.$step['id_field'].'` = '.kcp_noti_quote($step['id']));
|
||||
if (count($rows) !== 1 || !kcp_noti_matches($rows[0], $step['after'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function kcp_noti_locked($data, $tables)
|
||||
{
|
||||
$order_no = kcp_noti_quote($data['order_no']);
|
||||
$personal = kcp_noti_rows("SELECT * FROM `{$tables['personal']}` WHERE pp_id = $order_no");
|
||||
$orders = kcp_noti_rows("SELECT * FROM `{$tables['order']}` WHERE od_id = $order_no");
|
||||
// 동일 번호가 두 종류에 존재하면 임의로 한쪽을 선택하지 않는다.
|
||||
if ($personal && $orders) {
|
||||
return false;
|
||||
}
|
||||
$pp = $personal ? $personal[0] : null;
|
||||
if ($pp) {
|
||||
$prefix = 'pp';
|
||||
$payment = $pp;
|
||||
$orders = $pp['od_id'] ? kcp_noti_rows("SELECT * FROM `{$tables['order']}` WHERE od_id = ".kcp_noti_quote($pp['od_id'])) : array();
|
||||
if ($pp['od_id'] && !$orders) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!$orders) {
|
||||
return false;
|
||||
}
|
||||
$prefix = 'od';
|
||||
$payment = $orders[0];
|
||||
}
|
||||
$od = $orders ? $orders[0] : null;
|
||||
if ($payment[$prefix.'_pg'] !== 'kcp' || $payment[$prefix.'_tno'] !== $data['tno']
|
||||
|| empty($payment[$prefix.'_kcp_site_cd']) || $payment[$prefix.'_kcp_site_cd'] !== $data['site_cd']) {
|
||||
return false;
|
||||
}
|
||||
// 처리하지 않는 기존 에스크로 이벤트는 거래를 확인한 뒤 수신만 확인한다.
|
||||
if ($data['tx_cd'] !== 'TX00') {
|
||||
return true;
|
||||
}
|
||||
if ($payment[$prefix.'_settle_case'] !== '가상계좌') {
|
||||
return false;
|
||||
}
|
||||
if ($od && (bool) $od['od_test'] !== in_array($data['site_cd'], array('T0000', 'T0007'), true)) {
|
||||
return false;
|
||||
}
|
||||
$account_parts = preg_split('/\s+/', trim($payment[$prefix.'_bank_account']));
|
||||
if (end($account_parts) !== $data['account']) {
|
||||
return false;
|
||||
}
|
||||
return kcp_noti_deposit($data, $tables, $payment, $prefix, $pp, $od);
|
||||
}
|
||||
|
||||
// 거래 검증과 테이블 잠금이 끝난 가상계좌 통보의 이력·금액·상태를 처리한다.
|
||||
function kcp_noti_deposit($data, $tables, $payment, $prefix, $pp, $od)
|
||||
{
|
||||
// 기존 통보 이력과 미완료 처리를 먼저 확인한다.
|
||||
$trade = hash('sha256', $data['site_cd'].'|'.$data['tno'].'|'.$data['order_no']);
|
||||
$key = hash('sha256', $trade.'|'.$data['noti_id'].'|'.$data['op_cd']);
|
||||
$payload = hash('sha256', $data['ipgm_mnyx'].'|'.$data['account']);
|
||||
$events = kcp_noti_rows("SELECT * FROM `{$tables['event']}` WHERE kn_trade = '$trade'");
|
||||
$deposit = null;
|
||||
$cancel = null;
|
||||
$active = false;
|
||||
$by_id = array();
|
||||
foreach ($events as $event) {
|
||||
if ($event['kn_key'] === $key) {
|
||||
if ($event['kn_payload'] !== $payload) {
|
||||
return false;
|
||||
}
|
||||
if ($event['kn_done']) {
|
||||
return true;
|
||||
}
|
||||
$plan = json_decode($event['kn_plan'], true);
|
||||
if (!is_array($plan) || !kcp_noti_apply($plan, $tables)) {
|
||||
return false;
|
||||
}
|
||||
kcp_noti_query("UPDATE `{$tables['event']}` SET kn_done = 1 WHERE kn_key = '$key'");
|
||||
return true;
|
||||
}
|
||||
if (!$event['kn_done']) {
|
||||
return false;
|
||||
}
|
||||
$by_id[$event['kn_noti_id']][$event['kn_op_cd']] = $event;
|
||||
if ($event['kn_noti_id'] === $data['noti_id']) {
|
||||
if ($event['kn_payload'] !== $payload) {
|
||||
return false;
|
||||
}
|
||||
if ($event['kn_op_cd'] === '50') {
|
||||
$deposit = $event;
|
||||
} else {
|
||||
$cancel = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($by_id as $pair) {
|
||||
if (isset($pair['50']) && !isset($pair['13'])) {
|
||||
$active = true;
|
||||
}
|
||||
}
|
||||
// 망취소가 먼저 도착했거나 취소 뒤 입금 통보가 재전송되어도 재입금하지 않는다.
|
||||
if ($data['op_cd'] === '50' && $cancel) {
|
||||
return true;
|
||||
}
|
||||
$canceling = $data['op_cd'] === '13';
|
||||
if (!$canceling && $active) {
|
||||
return false;
|
||||
}
|
||||
if ($canceling && !$deposit && $active) {
|
||||
return false;
|
||||
}
|
||||
if ($pp && !$pp['pp_use']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 통보 금액과 현재 주문·개인결제의 잔액을 대조한다.
|
||||
$amount = (int) $data['ipgm_mnyx'];
|
||||
$receipt = (int) $payment[$prefix.'_receipt_price'];
|
||||
if ($pp && ((int) $pp['pp_price'] !== $amount || (int) $pp['pp_price'] <= 0)) {
|
||||
return false;
|
||||
}
|
||||
if (!$canceling && $receipt !== 0) {
|
||||
return false;
|
||||
}
|
||||
if ($canceling && $deposit && $receipt < $amount) {
|
||||
return false;
|
||||
}
|
||||
if ($canceling && !$deposit && $receipt !== 0) {
|
||||
return false;
|
||||
}
|
||||
$od_id = $od ? $od['od_id'] : '0';
|
||||
if ($od) {
|
||||
$pending = kcp_noti_rows("SELECT kn_key FROM `{$tables['event']}` WHERE od_id = ".kcp_noti_quote($od_id)." AND kn_done = 0");
|
||||
if ($pending) {
|
||||
return false;
|
||||
}
|
||||
$due = (int) $od['od_cart_price'] + (int) $od['od_send_cost'] + (int) $od['od_send_cost2']
|
||||
- (int) $od['od_cart_coupon'] - (int) $od['od_coupon'] - (int) $od['od_send_coupon']
|
||||
- (int) $od['od_cancel_price'] - (int) $od['od_receipt_point'] + (int) $od['od_refund_price'];
|
||||
$misu = $due - (int) $od['od_receipt_price'];
|
||||
if ($misu !== (int) $od['od_misu'] || $due <= 0) {
|
||||
return false;
|
||||
}
|
||||
if (!$pp && ($due !== $amount || (int) $od['od_cancel_price'] !== 0 || (int) $od['od_refund_price'] !== 0)) {
|
||||
return false;
|
||||
}
|
||||
if (!$canceling && ($od['od_status'] !== '주문' || $misu < $amount)) {
|
||||
return false;
|
||||
}
|
||||
if ($canceling && !in_array($od['od_status'], array('주문', '입금', '준비', '배송', '완료'), true)) {
|
||||
return false;
|
||||
}
|
||||
if ($canceling && $deposit && (int) $od['od_receipt_price'] < $amount) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
$time = substr($data['tx_tm'], 0, 4).'-'.substr($data['tx_tm'], 4, 2).'-'.substr($data['tx_tm'], 6, 2)
|
||||
.' '.substr($data['tx_tm'], 8, 2).':'.substr($data['tx_tm'], 10, 2).':'.substr($data['tx_tm'], 12, 2);
|
||||
|
||||
// 개인결제, 장바구니, 주문 순서로 변경 전/후 목표값을 구성한다.
|
||||
$delta = $canceling ? ($deposit ? -$amount : 0) : $amount;
|
||||
$plan = array();
|
||||
if ($pp && $delta) {
|
||||
$plan[] = kcp_noti_step('personal', 'pp_id', $pp,
|
||||
array('pp_receipt_price' => $receipt + $delta, 'pp_receipt_time' => $time),
|
||||
array('od_id', 'pp_pg', 'pp_tno', 'pp_kcp_site_cd', 'pp_settle_case', 'pp_price', 'pp_use', 'pp_bank_account'));
|
||||
}
|
||||
if ($od && $delta) {
|
||||
$new_receipt = (int) $od['od_receipt_price'] + $delta;
|
||||
$new_misu = $due - $new_receipt;
|
||||
if ($new_receipt < 0 || $new_receipt > 2147483647 || $new_misu > 2147483647) {
|
||||
return false;
|
||||
}
|
||||
$status = $od['od_status'];
|
||||
if (!$canceling && $new_misu === 0) {
|
||||
$status = '입금';
|
||||
}
|
||||
if ($canceling && $status === '입금') {
|
||||
$status = '주문';
|
||||
}
|
||||
$cart = kcp_noti_rows("SELECT ct_id, od_id, ct_status FROM `{$tables['cart']}` WHERE od_id = ".kcp_noti_quote($od_id));
|
||||
if (!$cart) {
|
||||
return false;
|
||||
}
|
||||
foreach ($cart as $item) {
|
||||
if (!$canceling && $item['ct_status'] !== '주문') {
|
||||
return false;
|
||||
}
|
||||
if ($status !== $od['od_status'] && $item['ct_status'] === $od['od_status']) {
|
||||
$plan[] = kcp_noti_step('cart', 'ct_id', $item, array('ct_status' => $status), array('od_id'));
|
||||
}
|
||||
}
|
||||
// 마지막에 주문 상태를 변경하여 중간 실패 시 출고 판정 노출을 줄인다.
|
||||
$plan[] = kcp_noti_step('order', 'od_id', $od,
|
||||
array('od_receipt_price' => $new_receipt, 'od_receipt_time' => $time, 'od_misu' => $new_misu, 'od_status' => $status),
|
||||
array('od_pg', 'od_tno', 'od_kcp_site_cd', 'od_settle_case', 'od_test', 'od_cart_price', 'od_send_cost',
|
||||
'od_send_cost2', 'od_cart_coupon', 'od_coupon', 'od_send_coupon', 'od_cancel_price', 'od_receipt_point', 'od_refund_price'));
|
||||
}
|
||||
|
||||
// 복구에 사용할 계획을 저장한 뒤 적용하고 완료 이력을 기록한다.
|
||||
$json = json_encode($plan);
|
||||
if ($json === false) {
|
||||
return false;
|
||||
}
|
||||
kcp_noti_query("INSERT INTO `{$tables['event']}` SET kn_key = '$key', kn_trade = '$trade', kn_noti_id = ".kcp_noti_quote($data['noti_id'])
|
||||
.", kn_op_cd = ".kcp_noti_quote($data['op_cd']).", kn_payload = '$payload', od_id = ".kcp_noti_quote($od_id)
|
||||
.", kn_plan = ".kcp_noti_quote($json).", kn_created_at = ".kcp_noti_quote($time));
|
||||
if (!kcp_noti_apply($plan, $tables)) {
|
||||
return false;
|
||||
}
|
||||
kcp_noti_query("UPDATE `{$tables['event']}` SET kn_done = 1 WHERE kn_key = '$key'");
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
<?php
|
||||
if (!defined('_GNUBOARD_')) exit;
|
||||
include_once(dirname(__FILE__).'/shop_order_compat.lib.php');
|
||||
|
||||
function lg_noti_query($sql)
|
||||
{
|
||||
$result = sql_query($sql, false);
|
||||
if (!$result) {
|
||||
throw new Exception('LG notification database failure');
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
function lg_noti_rows($sql)
|
||||
{
|
||||
$result = lg_noti_query($sql);
|
||||
$rows = array();
|
||||
while ($row = sql_fetch_array($result)) {
|
||||
$rows[] = $row;
|
||||
}
|
||||
return $rows;
|
||||
}
|
||||
|
||||
function lg_noti_quote($value)
|
||||
{
|
||||
return "'".sql_escape_string((string) $value)."'";
|
||||
}
|
||||
|
||||
function lg_noti_tables()
|
||||
{
|
||||
global $g5;
|
||||
return array('order' => $g5['g5_shop_order_table'], 'personal' => $g5['g5_shop_personalpay_table'],
|
||||
'cart' => $g5['g5_shop_cart_table'],
|
||||
'event' => isset($g5['g5_shop_lg_noti_table']) ? $g5['g5_shop_lg_noti_table'] : G5_SHOP_TABLE_PREFIX.'lg_noti');
|
||||
}
|
||||
|
||||
function lg_noti_process($data)
|
||||
{
|
||||
$tables = lg_noti_tables();
|
||||
$locked = false;
|
||||
try {
|
||||
// 기본 설치의 MyISAM에서도 다른 통보 및 관리자 UPDATE와 경쟁하지 않게 한다.
|
||||
// DDL은 관리자 DB 업그레이드에서만 실행하며 스키마/권한이 없으면 실패 응답한다.
|
||||
$locks = array();
|
||||
foreach ($tables as $table) {
|
||||
$locks[] = '`'.$table.'` WRITE';
|
||||
}
|
||||
lg_noti_query('LOCK TABLES '.implode(', ', $locks));
|
||||
$locked = true;
|
||||
$success = lg_noti_locked($data, $tables);
|
||||
} catch (Exception $e) {
|
||||
error_log('LG notification: processing failed; reconciliation/retry required');
|
||||
$success = false;
|
||||
} catch (Throwable $e) {
|
||||
error_log('LG notification: processing failed; reconciliation/retry required');
|
||||
$success = false;
|
||||
}
|
||||
if ($locked) {
|
||||
try {
|
||||
lg_noti_query('UNLOCK TABLES');
|
||||
} catch (Exception $e) {
|
||||
$success = false;
|
||||
} catch (Throwable $e) {
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
return $success;
|
||||
}
|
||||
|
||||
// 변경 전/후 값을 함께 기록한다. 재시도는 가산 SQL 대신 목표값을 대입한다.
|
||||
function lg_noti_step($table, $id_field, $row, $changes, $guards)
|
||||
{
|
||||
$before = array();
|
||||
foreach (array_unique(array_merge(array($id_field), array_keys($changes), $guards)) as $field) {
|
||||
if (!array_key_exists($field, $row)) {
|
||||
throw new Exception('Missing notification schema');
|
||||
}
|
||||
$before[$field] = (string) $row[$field];
|
||||
}
|
||||
$after = $before;
|
||||
foreach ($changes as $field => $value) {
|
||||
$after[$field] = (string) $value;
|
||||
}
|
||||
return array('table' => $table, 'id_field' => $id_field, 'id' => (string) $row[$id_field],
|
||||
'before' => $before, 'after' => $after);
|
||||
}
|
||||
|
||||
function lg_noti_matches($row, $expected)
|
||||
{
|
||||
foreach ($expected as $field => $value) {
|
||||
if (!array_key_exists($field, $row) || (string) $row[$field] !== $value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function lg_noti_apply($plan, $tables)
|
||||
{
|
||||
// 먼저 모든 행을 검사한다. 실패 후 외부에서 수정한 행을 과거 값으로 덮어쓰지 않는다.
|
||||
foreach ($plan as $step) {
|
||||
$rows = lg_noti_rows('SELECT * FROM `'.$tables[$step['table']].'` WHERE `'.$step['id_field'].'` = '.lg_noti_quote($step['id']));
|
||||
if (count($rows) !== 1 || (!lg_noti_matches($rows[0], $step['before']) && !lg_noti_matches($rows[0], $step['after']))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
foreach ($plan as $step) {
|
||||
$sets = array();
|
||||
foreach ($step['after'] as $field => $value) {
|
||||
if ($value !== $step['before'][$field]) {
|
||||
$sets[] = '`'.$field.'` = '.lg_noti_quote($value);
|
||||
}
|
||||
}
|
||||
if ($sets) {
|
||||
$sql = 'UPDATE `'.$tables[$step['table']].'` SET '.implode(', ', $sets)
|
||||
.' WHERE `'.$step['id_field'].'` = '.lg_noti_quote($step['id']);
|
||||
lg_noti_query($sql);
|
||||
}
|
||||
}
|
||||
foreach ($plan as $step) {
|
||||
$rows = lg_noti_rows('SELECT * FROM `'.$tables[$step['table']].'` WHERE `'.$step['id_field'].'` = '.lg_noti_quote($step['id']));
|
||||
if (count($rows) !== 1 || !lg_noti_matches($rows[0], $step['after'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// XPay 공통사항 6.2 및 가상계좌 가이드(2026-09-18 확인).
|
||||
// MD5에는 CASFLAG/TID/순번이 포함되지 않으므로 발신지 검사도 반드시 적용한다.
|
||||
function lg_noti_request($post, $server, $config)
|
||||
{
|
||||
$ips = array('13.124.18.147', '13.124.108.35', '3.36.173.151', '3.38.81.32',
|
||||
'115.92.221.121', '115.92.221.122', '115.92.221.123', '115.92.221.125', '115.92.221.126', '115.92.221.127');
|
||||
if (!isset($server['REQUEST_METHOD'], $server['REMOTE_ADDR']) || $server['REQUEST_METHOD'] !== 'POST'
|
||||
|| !in_array($server['REMOTE_ADDR'], $ips, true)) return false;
|
||||
// 프록시 전달 헤더, 현재 로그인/활성 PG 설정은 통보 인증에 사용하지 않는다.
|
||||
if (!isset($config['cf_lg_mid'], $config['cf_lg_mert_key'])
|
||||
|| !is_string($config['cf_lg_mid']) || !preg_match('/\A[A-Za-z0-9_-]{1,100}\z/', $config['cf_lg_mid'])
|
||||
|| !is_string($config['cf_lg_mert_key']) || trim($config['cf_lg_mert_key']) === '') return false;
|
||||
$patterns = array(
|
||||
'LGD_MID' => '/\A[A-Za-z0-9_-]{1,104}\z/', 'LGD_OID' => '/\A[1-9][0-9]{0,19}\z/',
|
||||
'LGD_TID' => '/\A[A-Za-z0-9_-]{1,64}\z/', 'LGD_RESPCODE' => '/\A0000\z/',
|
||||
'LGD_PAYTYPE' => '/\ASC0040\z/', 'LGD_HASHDATA' => '/\A[a-fA-F0-9]{32}\z/',
|
||||
'LGD_TIMESTAMP' => '/\A[0-9]{14}\z/', 'LGD_PAYDATE' => '/\A[0-9]{14}\z/',
|
||||
'LGD_AMOUNT' => '/\A[0-9]{1,12}\z/', 'LGD_CASTAMOUNT' => '/\A[0-9]{1,12}\z/',
|
||||
'LGD_CASCAMOUNT' => '/\A[0-9]{1,12}\z/', 'LGD_CASFLAG' => '/\A[RIC]\z/',
|
||||
'LGD_CASSEQNO' => '/\A[0-9]{1,6}\z/', 'LGD_ACCOUNTNUM' => '/\A[A-Za-z0-9-]{1,30}\z/'
|
||||
);
|
||||
$data = array();
|
||||
foreach ($patterns as $field => $pattern) {
|
||||
if (!isset($post[$field]) || !is_string($post[$field]) || !preg_match($pattern, $post[$field])) return false;
|
||||
$data[$field] = $post[$field];
|
||||
}
|
||||
$mid = 'si_'.$config['cf_lg_mid'];
|
||||
if (!shop_order_equals($mid, $data['LGD_MID']) && !shop_order_equals('t'.$mid, $data['LGD_MID'])) return false;
|
||||
$hash = md5($data['LGD_MID'].$data['LGD_OID'].$data['LGD_AMOUNT'].$data['LGD_RESPCODE'].$data['LGD_TIMESTAMP'].$config['cf_lg_mert_key']);
|
||||
if (!shop_order_equals($hash, strtolower($data['LGD_HASHDATA']))) return false;
|
||||
foreach (array('LGD_TIMESTAMP', 'LGD_PAYDATE') as $field) {
|
||||
$time = $data[$field];
|
||||
if ((int)substr($time, 0, 4) < 1000 || !checkdate((int)substr($time, 4, 2), (int)substr($time, 6, 2), (int)substr($time, 0, 4))
|
||||
|| substr($time, 8, 2) > 23 || substr($time, 10, 2) > 59 || substr($time, 12, 2) > 59) return false;
|
||||
}
|
||||
// 수동 재통보를 막는 임의의 timestamp 수명 제한 대신 거래별 처리 이력을 확인한다.
|
||||
foreach (array('LGD_AMOUNT', 'LGD_CASTAMOUNT', 'LGD_CASCAMOUNT') as $field) {
|
||||
if ((float)$data[$field] > 2147483647) return false;
|
||||
$data[$field] = (int)$data[$field];
|
||||
}
|
||||
if ($data['LGD_AMOUNT'] <= 0) return false;
|
||||
$flag = $data['LGD_CASFLAG'];
|
||||
if ($flag === 'R') {
|
||||
if ($data['LGD_CASTAMOUNT'] !== 0 || $data['LGD_CASCAMOUNT'] !== 0) return false;
|
||||
} else {
|
||||
if ((int)$data['LGD_CASSEQNO'] === 0) return false;
|
||||
// 기본 통합결제창의 일반형 가상계좌는 전액 일회성 입금이다.
|
||||
if ($flag === 'I' && ($data['LGD_CASTAMOUNT'] !== $data['LGD_AMOUNT'] || $data['LGD_CASCAMOUNT'] !== $data['LGD_AMOUNT'])) return false;
|
||||
if ($flag === 'C' && ($data['LGD_CASTAMOUNT'] !== 0 || !in_array($data['LGD_CASCAMOUNT'], array(0, $data['LGD_AMOUNT']), true))) return false;
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
||||
function lg_noti_locked($data, $tables)
|
||||
{
|
||||
$id = lg_noti_quote($data['LGD_OID']);
|
||||
$personal = lg_noti_rows("SELECT * FROM `{$tables['personal']}` WHERE pp_id = $id");
|
||||
$orders = lg_noti_rows("SELECT * FROM `{$tables['order']}` WHERE od_id = $id");
|
||||
if (count($personal) > 1 || count($orders) > 1 || ($personal && $orders)) return false;
|
||||
$pp = $personal ? $personal[0] : null;
|
||||
if ($pp) {
|
||||
$prefix = 'pp'; $payment = $pp;
|
||||
$orders = $pp['od_id'] ? lg_noti_rows("SELECT * FROM `{$tables['order']}` WHERE od_id = ".lg_noti_quote($pp['od_id'])) : array();
|
||||
if ($pp['od_id'] && count($orders) !== 1) return false;
|
||||
} else {
|
||||
if (!$orders) return false;
|
||||
$prefix = 'od'; $payment = $orders[0];
|
||||
}
|
||||
$od = $orders ? $orders[0] : null;
|
||||
if ($payment[$prefix.'_pg'] !== 'lg' || $payment[$prefix.'_settle_case'] !== '가상계좌'
|
||||
|| $payment[$prefix.'_tno'] !== $data['LGD_TID'] || empty($payment[$prefix.'_lg_mid'])
|
||||
|| !shop_order_equals($payment[$prefix.'_lg_mid'], $data['LGD_MID'])) return false;
|
||||
if ($od && (bool)$od['od_test'] !== (substr($data['LGD_MID'], 0, 1) === 't')) return false;
|
||||
// 저장 형식: 은행명 계좌번호 예금주명. 부분 문자열 일치는 허용하지 않는다.
|
||||
if (!in_array($data['LGD_ACCOUNTNUM'], preg_split('/\s+/', trim($payment[$prefix.'_bank_account'])), true)) return false;
|
||||
if ($data['LGD_CASFLAG'] === 'R') return true; // 발급 통보로 입금 처리하지 않는다.
|
||||
return lg_noti_deposit($data, $tables, $payment, $prefix, $pp, $od);
|
||||
}
|
||||
|
||||
function lg_noti_deposit($data, $tables, $payment, $prefix, $pp, $od)
|
||||
{
|
||||
$trade = hash('sha256', $data['LGD_MID'].'|'.$data['LGD_TID'].'|'.$data['LGD_OID']);
|
||||
$seq = (int)$data['LGD_CASSEQNO'];
|
||||
$flag = $data['LGD_CASFLAG'];
|
||||
$key = hash('sha256', $trade.'|'.$seq.'|'.$flag);
|
||||
// 재전송 timestamp는 달라질 수 있다. 업무 값을 바꾼 동일 이벤트는 거부한다.
|
||||
$payload = hash('sha256', $data['LGD_AMOUNT'].'|'.$data['LGD_CASTAMOUNT'].'|'.$data['LGD_CASCAMOUNT'].'|'.$data['LGD_ACCOUNTNUM']);
|
||||
$events = lg_noti_rows("SELECT * FROM `{$tables['event']}` WHERE ln_trade = '$trade'");
|
||||
$same = null; $latest = null;
|
||||
foreach ($events as $event) {
|
||||
if ($event['ln_key'] === $key) $same = $event;
|
||||
if (!$latest || (int)$event['ln_seq'] > (int)$latest['ln_seq']
|
||||
|| ((int)$event['ln_seq'] === (int)$latest['ln_seq'] && $event['ln_flag'] === 'C')) $latest = $event;
|
||||
if (!$event['ln_done'] && $event['ln_key'] !== $key) return false;
|
||||
}
|
||||
if ($same) {
|
||||
if ($same['ln_payload'] !== $payload) return false;
|
||||
if ($same['ln_done']) return true;
|
||||
$plan = json_decode($same['ln_plan'], true);
|
||||
if (!is_array($plan) || !lg_noti_apply($plan, $tables)) return false;
|
||||
lg_noti_query("UPDATE `{$tables['event']}` SET ln_done = 1 WHERE ln_key = '$key'");
|
||||
return true;
|
||||
}
|
||||
// 은행 취소가 먼저 와도 같은/과거 입금 순번으로 다시 입금시키지 않는다.
|
||||
if ($latest && ($seq < (int)$latest['ln_seq'] || ($seq === (int)$latest['ln_seq'] && $latest['ln_flag'] === 'C'))) return true;
|
||||
$canceling = $flag === 'C';
|
||||
// 사용 중지는 신규 입금만 막는다. 이미 수납한 거래의 은행 취소는 반영해야 한다.
|
||||
if ($pp && !$pp['pp_use'] && !$canceling) return false;
|
||||
$amount = $data['LGD_AMOUNT'];
|
||||
$receipt = (int)$payment[$prefix.'_receipt_price'];
|
||||
$active = $latest && $latest['ln_flag'] === 'I';
|
||||
if ($pp && (int)$pp['pp_price'] !== $amount) return false;
|
||||
if (!$canceling && ($active || $receipt !== 0)) return false;
|
||||
// 이력 없는 기존 입금은 자동으로 취소하거나 기처리로 추정하지 않는다.
|
||||
if ($canceling && $receipt !== ($active ? $amount : 0)) return false;
|
||||
$delta = $canceling ? ($active ? -$amount : 0) : $amount;
|
||||
$od_id = $od ? $od['od_id'] : '0';
|
||||
// 관리자가 연결 주문을 바꿔도 이전 입금의 취소액을 새 주문에서 차감하지 않는다.
|
||||
// 연결 해제/추가도 충돌이며 원장 대조 후 원래 연결을 복원하고 재통보한다.
|
||||
if ($pp && $canceling && $active && (string)$latest['od_id'] !== (string)$od_id) {
|
||||
error_log('LG notification: personal payment order binding changed; reconciliation required');
|
||||
return false;
|
||||
}
|
||||
if ($od) {
|
||||
$pending = lg_noti_rows("SELECT ln_key FROM `{$tables['event']}` WHERE od_id = ".lg_noti_quote($od_id)." AND ln_done = 0");
|
||||
if ($pending) return false;
|
||||
$due = (int)$od['od_cart_price'] + (int)$od['od_send_cost'] + (int)$od['od_send_cost2']
|
||||
- (int)$od['od_cart_coupon'] - (int)$od['od_coupon'] - (int)$od['od_send_coupon']
|
||||
- (int)$od['od_cancel_price'] - (int)$od['od_receipt_point'] + (int)$od['od_refund_price'];
|
||||
$misu = $due - (int)$od['od_receipt_price'];
|
||||
if ($due <= 0 || $due > 2147483647 || $misu !== (int)$od['od_misu']) return false;
|
||||
if (!$pp && ($due !== $amount || (int)$od['od_cancel_price'] !== 0 || (int)$od['od_refund_price'] !== 0)) return false;
|
||||
if (!$canceling && ($od['od_status'] !== '주문' || $misu < $amount)) return false;
|
||||
if ($canceling && (!in_array($od['od_status'], array('주문', '입금', '준비', '배송', '완료'), true)
|
||||
|| (int)$od['od_receipt_price'] + $delta < 0)) return false;
|
||||
}
|
||||
$raw_time = $data['LGD_PAYDATE'];
|
||||
$time = substr($raw_time, 0, 4).'-'.substr($raw_time, 4, 2).'-'.substr($raw_time, 6, 2)
|
||||
.' '.substr($raw_time, 8, 2).':'.substr($raw_time, 10, 2).':'.substr($raw_time, 12, 2);
|
||||
$plan = array();
|
||||
if ($pp && $delta) {
|
||||
$plan[] = lg_noti_step('personal', 'pp_id', $pp,
|
||||
array('pp_receipt_price'=>$receipt + $delta, 'pp_receipt_time'=>$time, 'pp_casseqno'=>$data['LGD_CASSEQNO']),
|
||||
array('od_id', 'pp_pg', 'pp_tno', 'pp_lg_mid', 'pp_settle_case', 'pp_price', 'pp_use', 'pp_bank_account'));
|
||||
}
|
||||
if ($od && $delta) {
|
||||
$new_receipt = (int)$od['od_receipt_price'] + $delta;
|
||||
$new_misu = $due - $new_receipt;
|
||||
if ($new_receipt < 0 || $new_receipt > 2147483647 || $new_misu < 0 || $new_misu > 2147483647) return false;
|
||||
$status = $od['od_status'];
|
||||
if (!$canceling && $new_misu === 0) $status = '입금';
|
||||
if ($canceling && in_array($status, array('입금', '준비'), true)) $status = '주문';
|
||||
$cart = lg_noti_rows("SELECT ct_id, od_id, ct_status FROM `{$tables['cart']}` WHERE od_id = ".lg_noti_quote($od_id));
|
||||
if (!$cart) return false;
|
||||
foreach ($cart as $item) {
|
||||
if (!$canceling && $item['ct_status'] !== '주문') return false;
|
||||
if ($status !== $od['od_status'] && ($item['ct_status'] === $od['od_status']
|
||||
|| ($canceling && in_array($item['ct_status'], array('입금', '준비'), true)))) {
|
||||
$plan[] = lg_noti_step('cart', 'ct_id', $item, array('ct_status'=>$status), array('od_id'));
|
||||
}
|
||||
}
|
||||
// 출고에 사용하는 주문 상태는 장바구니 반영 뒤 마지막에 바꾼다.
|
||||
$plan[] = lg_noti_step('order', 'od_id', $od,
|
||||
array('od_receipt_price'=>$new_receipt, 'od_receipt_time'=>$time, 'od_misu'=>$new_misu,
|
||||
'od_status'=>$status, 'od_casseqno'=>$data['LGD_CASSEQNO']),
|
||||
array('od_pg', 'od_tno', 'od_lg_mid', 'od_settle_case', 'od_test', 'od_bank_account', 'od_cart_price',
|
||||
'od_send_cost', 'od_send_cost2', 'od_cart_coupon', 'od_coupon', 'od_send_coupon', 'od_cancel_price', 'od_receipt_point', 'od_refund_price'));
|
||||
}
|
||||
$json = json_encode($plan);
|
||||
if ($json === false) return false;
|
||||
lg_noti_query("INSERT INTO `{$tables['event']}` SET ln_key='$key', ln_trade='$trade', ln_seq='$seq', ln_flag='$flag', ln_payload='$payload', od_id=".lg_noti_quote($od_id)
|
||||
.", ln_plan=".lg_noti_quote($json).", ln_created_at=".lg_noti_quote($time));
|
||||
if (!lg_noti_apply($plan, $tables)) return false;
|
||||
lg_noti_query("UPDATE `{$tables['event']}` SET ln_done = 1 WHERE ln_key = '$key'");
|
||||
if ($canceling && $od && in_array($od['od_status'], array('배송', '완료'), true)) {
|
||||
error_log('LG notification: bank cancellation after shipment; operator reconciliation required');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user