본문으로 건너뛰기

소스 변경 전체 보기

PHP 8 · GODO26
변경 파일 수: 162개
변경 파일 목록
총 162개
주문 적용 쿠폰 회원 다운로드 발급 방식 추가
layer_kakao_friendtalk_content_setting.phpAdmin/crm/mobile_send/coupon_list.phpAdmin/promotion/coupon_manage.phpAdmin/promotion/basic_info.phpAdmin/promotion/coupon_regist/coupon_regist.phpAdmin/promotion/CouponAdmin.phpComponent/Coupon/CouponDAO.phpComponent/Coupon/Coupon.phpComponent/Coupon/Goods.phpComponent/Goods/NaverDbUrl3.phpComponent/Worker/CouponListController.phpController/Admin/Promotion/CouponManageController.phpController/Admin/Promotion/CouponPsController.phpController/Admin/Promotion/CouponRegistController.phpController/Admin/Promotion/CouponzoneController.phpController/Front/Event/LayerCouponDownController.phpController/Front/Goods/LayerCouponPsController.phpController/Front/Goods/CouponPsController.phpController/Front/Mypage/LayerCouponApplyOrderController.phpController/Front/Order/LayerCouponDownController.phpController/Mobile/Goods/LayerCouponPsController.phpController/Mobile/Goods/CouponPsController.phpController/Mobile/Mypage/LayerCouponApplyOrderController.phpController/Mobile/Order/coupon.cssdata/skin/front/moment/css/event/couponzone.htmldata/skin/front/moment/event/layer_coupon_down.htmldata/skin/front/moment/goods/coupon.cssdata/skin/front/mygreek/css/event/couponzone.htmldata/skin/front/mygreek/event/layer_coupon_down.htmldata/skin/front/mygreek/goods/gd_contents.cssdata/skin/front/story_g/css/couponzone.htmldata/skin/front/story_g/event/layer_coupon_down.htmldata/skin/front/story_g/goods/coupon.cssdata/skin/mobile/moment/css/event/event.cssdata/skin/mobile/moment/css/event/couponzone.htmldata/skin/mobile/moment/event/layer_coupon_down.htmldata/skin/mobile/moment/goods/coupon.cssdata/skin/mobile/mygreek/css/event/couponzone.htmldata/skin/mobile/mygreek/event/layer_coupon_down.htmldata/skin/mobile/mygreek/goods/gd_contents.cssdata/skin/mobile/story_g/css/couponzone.htmldata/skin/mobile/story_g/event/layer_coupon_down.htmldata/skin/mobile/story_g/goods/icon-check-black.pngdata/skin/mobile/story_g/img/icon/
SMS 발송 세팅 현황 안내 추가
message_config.phpAdmin/crm/
카카오 회원 가입 팝업 처리 개선
PageAccess.phpComponent/Member/
최신 iOS Safari 환경 네이버페이(주문형) 이동 안정성 개선
NaverPayController.phpController/Front/Goods/
예치금·마일리지 포함 주문 환불 처리 안정성 개선
refund_do_new.phpAdmin/order/ReOrderCalculation.phpComponent/Order/
배송지 수정 기능 보안 강화
inc_order_view.phpAdmin/order/layer_regular_order_shipping_address.phpAdmin/order/layout_order_goods_list.phpAdmin/order/layout_order_goods_simple_list.phpAdmin/order/layout_order_list.phpAdmin/order/logistics_layout_order_goods_list.phpAdmin/order/popup_order_packet.phpAdmin/order/regular_order_view.phpAdmin/order/ReplaceCode.phpComponent/Design/OrderNew.phpComponent/Order/Order.phpComponent/Order/RegularOrderShippingAddress.phpComponent/RegularDelivery/RegularOrder/LayerReceiverInfoController.phpController/Admin/Order/OrderViewController.phpController/Admin/Order/RegularOrderViewController.phpController/Admin/Order/LayerShippingAddressController.phpController/Admin/Share/OrderViewController.phpController/Front/Mypage/RegularDeliveryShippingAddressChangeController.phpController/Front/Mypage/ShippingController.phpController/Front/Mypage/LayerRegularOrderShippingAddressController.phpController/Front/Order/LayerRegularOrderShippingAddressRegistController.phpController/Front/Order/LayerShippingAddressController.phpController/Front/Order/LayerShippingAddressRegistController.phpController/Front/Order/OrderEndController.phpController/Front/Order/RegularOrderController.phpController/Front/Order/RegularOrderEndController.phpController/Front/Order/LayerShippingAddressRegistController.phpController/Mobile/Mypage/OrderViewController.phpController/Mobile/Mypage/ShippingController.phpController/Mobile/Mypage/LayerRegularOrderShippingAddressController.phpController/Mobile/Order/LayerRegularOrderShippingAddressRegistController.phpController/Mobile/Order/OrderController.phpController/Mobile/Order/OrderEndController.phpController/Mobile/Order/OrderPsController.phpController/Mobile/Order/RegularOrderController.phpController/Mobile/Order/order.htmldata/skin/mobile/moment/order/order.htmldata/skin/mobile/mygreek/order/order.htmldata/skin/mobile/story_g/order/
디자인에디터 스킨 모바일 환경에서 네이버페이 버튼 표시 개선
Policy.phpComponent/Policy/
네이버 판매지수 집계 정확도 개선
NaverDbUrl.phpComponent/Worker/
상품 수정시 정가/매입가/판매가 입력 안정성 개선
GoodsAdmin.phpComponent/Goods/GoodsBenefit.phpComponent/Goods/
비즈엠 알림톡 웹링크 버튼 PC 링크 미전송으로 발송 실패(K108)하는 문제 수정
SmsAuto.phpComponent/Sms/
상품 엑셀 업로드 시 단위가격 반영 개선
ExcelGoodsConvert.phpComponent/Excel/
엑셀 다운로드 시 자체상품코드 텍스트 서식 적용
ExcelDataConvert.phpComponent/Excel/ExcelRequest.phpComponent/Excel/ExcelSample.phpComponent/Excel/
모바일 메시지 대체 발송 미리보기 숏링크 치환 개선
layer_preview_mobile_send_alternative.phpAdmin/crm/preview_mobile_send/
마이앱 단독 발송 시 미리보기 인증번호 입력·수신거부회원 안내 생략
layer_preview_mobile_send.phpAdmin/crm/
회원정보 팝업 SMS 발송 안정성 개선
member_crm_sms.phpAdmin/share/ncds/
보안서버 HTTPS 리다이렉트 처리 개선
SiteLink.phpComponent/SiteLink/
변경 파일 코드
추가삭제
주문 적용 쿠폰 회원 다운로드 발급 방식 추가
Admin/crm/mobile_send/layer_kakao_friendtalk_content_setting.php+1-1
변경 전layer_kakao_friendtalk_content_setting.php
$.post('./layer_select_coupon.php', null, function (data) {
ncds_layer_popup({
message: data,
title: '쿠폰 선택<div class="modal-title-description">발급방식이 수동 발급인 쿠폰 중 발급상태가 발급 중인 쿠폰만 조회됩니다.</div>',
size: 'wide'
});
});
 
변경 후layer_kakao_friendtalk_content_setting.php
$.post('./layer_select_coupon.php', null, function (data) {
ncds_layer_popup({
message: data,
title: '쿠폰 선택<div class="modal-title-description">발급방식이 수동 발급 또는 회원 다운로드인 쿠폰 중 발급상태가 발급 중인 쿠폰만 조회됩니다.</div>',
size: 'wide'
});
});
 
Admin/promotion/coupon_list.php+2-1
변경 전coupon_list.php
 
<form id="frmCouponList" action="../promotion/coupon_ps.php" method="post">
<input type="hidden" name="mode" value="deleteCouponList"/>
 
<table class="table table-rows promotion-coupon-list">
<thead>
<tr>
method: "POST",
cache: false,
url: "../promotion/coupon_ps.php",
data: "mode=modifyCouponList&couponNo=" + couponNo + "&couponType=" + couponType,
dataType: 'json'
}).success(function (data) {
BootstrapDialog.alert({
 
변경 후coupon_list.php
 
<form id="frmCouponList" action="../promotion/coupon_ps.php" method="post">
<input type="hidden" name="mode" value="deleteCouponList"/>
<input type="hidden" name="couponListToken" value="<?= $couponListToken ?>"/>
<table class="table table-rows promotion-coupon-list">
<thead>
<tr>
method: "POST",
cache: false,
url: "../promotion/coupon_ps.php",
data: "mode=modifyCouponList&couponNo=" + couponNo + "&couponType=" + couponType + "&couponListToken=" + $('input[name=couponListToken]').val(),
dataType: 'json'
}).success(function (data) {
BootstrapDialog.alert({
 
Admin/promotion/coupon_manage.php+3-2
변경 전coupon_manage.php
<tbody>
<tr>
<th>쿠폰명</th>
<td><?= $getData['couponNm']; ?></td>
</tr>
<tr>
<th>쿠폰설명</th>
<td><?= $getData['couponDescribed']; ?></td>
</tr>
<tr>
<th>사용기간</th>
 
<form id="frmMemberCouponList" action="../promotion/coupon_ps.php" method="post">
<input type="hidden" name="mode" value="deleteCouponManage" />
 
<input type="hidden" name="couponNo" value="<?= $getData['couponNo']; ?>" />
<table class="table table-rows">
<colgroup>
 
변경 후coupon_manage.php
<tbody>
<tr>
<th>쿠폰명</th>
<td><?= gd_htmlspecialchars($getData['couponNm']); ?></td>
</tr>
<tr>
<th>쿠폰설명</th>
<td><?= gd_htmlspecialchars($getData['couponDescribed']); ?></td>
</tr>
<tr>
<th>사용기간</th>
 
<form id="frmMemberCouponList" action="../promotion/coupon_ps.php" method="post">
<input type="hidden" name="mode" value="deleteCouponManage" />
<input type="hidden" name="couponManageToken" value="<?= $couponManageToken ?>" />
<input type="hidden" name="couponNo" value="<?= $getData['couponNo']; ?>" />
<table class="table table-rows">
<colgroup>
 
Admin/promotion/coupon_regist/basic_info.php+2-2
변경 전basic_info.php
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input placeholder="쿠폰명을 입력하세요." name="couponNm" data-charcount-key="couponNm" value="<?= $couponData['couponNm'] ?>" class="ncua-coupon__name" type="text" maxlength="30" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="couponNm">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input placeholder="쿠폰 설명을 입력하세요." name="couponDescribed" data-charcount-key="couponDescribed" value="<?= $couponData['couponDescribed'] ?>" class="ncua-coupon__description" type="text" maxlength="50" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="couponDescribed">
 
변경 후basic_info.php
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input placeholder="쿠폰명을 입력하세요." name="couponNm" data-charcount-key="couponNm" value="<?= gd_htmlspecialchars($couponData['couponNm']) ?>" class="ncua-coupon__name" type="text" maxlength="30" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="couponNm">
<div class="ncua-input__content-wrap">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs ncua-input-width-520">
<input placeholder="쿠폰 설명을 입력하세요." name="couponDescribed" data-charcount-key="couponDescribed" value="<?= gd_htmlspecialchars($couponData['couponDescribed']) ?>" class="ncua-coupon__description" type="text" maxlength="50" />
</div>
</div>
<div class="ncua-input__field-text-count" data-charcount-text="couponDescribed">
 
Admin/promotion/coupon_regist.php+3-6
변경 전coupon_regist.php
<input type="hidden" name="ypage" value="<?= $ypage; ?>"/>
<input type="hidden" name="couponNo" value="<?= $couponData['couponNo']; ?>"/>
<input type="hidden" name="couponKind" value="<?= $couponData['couponKind']; ?>"/>
 
<input type="hidden" name="couponType" value="<?= $couponData['couponType']; ?>"/>
 
<header class="page-header js-affix ncua-page-header">
// 발급방식
$('input:radio[name="couponSaveType"]:eq(0)').prop("disabled", false);
$('input:radio[name="couponSaveType"]:eq(1)').prop("disabled", false);
$('input:radio[name="couponSaveType"]:eq(2)').prop("disabled", true);
if ($('input:radio[name="couponSaveType"]:checked').val() == 'down') {
$('input:radio[name="couponSaveType"]:eq(2)').prop("checked", false);
$('input:radio[name="couponSaveType"]:eq(0)').prop("checked", true);
}
$('#labelCouponSaveTypeManual').show();
$('#labelCouponSaveTypeAuto').show();
$('#labelCouponSaveTypeDown').hide();
// 혜택구분
$('input:radio[name="couponKindType"]:eq(0)').prop("disabled", false);
if ($('input:radio[name="couponKindType"]:checked').val() == 'delivery') {
 
변경 후coupon_regist.php
<input type="hidden" name="ypage" value="<?= $ypage; ?>"/>
<input type="hidden" name="couponNo" value="<?= $couponData['couponNo']; ?>"/>
<input type="hidden" name="couponKind" value="<?= $couponData['couponKind']; ?>"/>
<input type="hidden" name="couponRegistToken" value="<?= $couponRegistToken ?>"/>
<input type="hidden" name="couponType" value="<?= $couponData['couponType']; ?>"/>
 
<header class="page-header js-affix ncua-page-header">
// 발급방식
$('input:radio[name="couponSaveType"]:eq(0)').prop("disabled", false);
$('input:radio[name="couponSaveType"]:eq(1)').prop("disabled", false);
$('input:radio[name="couponSaveType"]:eq(2)').prop("disabled", false);
 
 
 
 
$('#labelCouponSaveTypeManual').show();
$('#labelCouponSaveTypeAuto').show();
$('#labelCouponSaveTypeDown').show();
// 혜택구분
$('input:radio[name="couponKindType"]:eq(0)').prop("disabled", false);
if ($('input:radio[name="couponKindType"]:checked').val() == 'delivery') {
 
Component/Coupon/CouponAdmin.php+185-141
변경 전CouponAdmin.php
use Component\Sms\SmsAdmin;
use Component\Sms\SmsAutoCode;
use Component\Validator\Validator;
 
use Framework\File\FileHandler;
use Framework\ObjectStorage\Service\ImageUploadService;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ProducerUtils;
 
use Globals;
use Origin\Enum\ApiClient\Notification\SmsPersonalInfoAccessLogEventType;
use Origin\Service\Coupon\CopyCouponService;
/**
* 쿠폰 리스트 가져오기
*
* @author su
 
 
 
*/
public function getCouponAdminList($mode = null, $addSaveTypeWhere = '', $addUseTypeWhereIn = [], $addDeviceTypeWhereIn = [])
{
$getValue = Request::get()->toArray();
$this->setCouponAdminSearch($getValue);
 
$sort['fieldName'] = gd_isset($getValue['sort']['name']);
$sort['sortMode'] = gd_isset($getValue['sort']['mode']);
if (empty($sort['fieldName']) || empty($sort['sortMode'])) {
$sort['fieldName'] = 'c.regDt';
$sort['sortMode'] = 'desc';
} else {
$sort['fieldName'] = 'c' . $sort['fieldName'];
}
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($sort['fieldName'], self::ALLOWED_SORT_FIELDS_COUPON, true)) {
$sort['fieldName'] = 'c.regDt';
}
if (!in_array($sort['sortMode'], self::ALLOWED_SORT_MODES, true)) {
$sort['sortMode'] = 'desc';
}
 
 
// 레이어에서 자바스크립트 페이징 처리시 사용되는 구문
if (gd_isset($getValue['pagelink'])) {
$getValue['page'] = (int) str_replace('page=', '', preg_replace('/^{page=[0-9]+}/', '', gd_isset($getValue['pagelink'])));
}
gd_isset($getValue['page'], 1);
gd_isset($getValue['pageNum'], 10);
 
$page = \App::load('\\Component\\Page\\Page', $getValue['page']);
$page->page['list'] = $getValue['pageNum'];
 
if (!$mode) {
$mode = 'online';
}
 
if ($addSaveTypeWhere != '') {
$this->arrWhere[] = 'c.couponSaveType = ?';
$this->db->bind_param_push($this->arrBind, $this->fieldTypes['coupon']['couponSaveType'], $addSaveTypeWhere);
$addSaveTypeWhere = " AND couponSaveType = '".$addSaveTypeWhere."'";
}
 
 
 
// couponUseType 필터 (배열: product, order, delivery, gift)
$addUseTypeWhereSql = '';
if (!empty($addUseTypeWhereIn) && is_array($addUseTypeWhereIn)) {
$placeholders = implode(',', array_fill(0, count($addUseTypeWhereIn), '?'));
$this->arrWhere[] = 'c.couponUseType IN (' . $placeholders . ')';
foreach ($addUseTypeWhereIn as $type) {
$this->db->bind_param_push($this->arrBind, 's', $type);
}
$addUseTypeWhereSql = " AND couponUseType IN ('" . implode("','", $addUseTypeWhereIn) . "')";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
// couponDeviceType 필터 (배열: all, pc, mobile)
$addDeviceTypeWhereSql = '';
if (!empty($addDeviceTypeWhereIn) && is_array($addDeviceTypeWhereIn)) {
$placeholders = implode(',', array_fill(0, count($addDeviceTypeWhereIn), '?'));
$this->arrWhere[] = 'c.couponDeviceType IN (' . $placeholders . ')';
foreach ($addDeviceTypeWhereIn as $type) {
$this->db->bind_param_push($this->arrBind, 's', $type);
}
$addDeviceTypeWhereSql = " AND couponDeviceType IN ('" . implode("','", $addDeviceTypeWhereIn) . "')";
}
 
 
 
 
 
if ($mode == 'all') {
$this->arrWhere[] = '1';
$amountSql = 'SELECT count(couponNo) FROM ' . DB_COUPON . ' WHERE 1' . $addSaveTypeWhere . $addUseTypeWhereSql . $addDeviceTypeWhereSql;
} else {
$this->arrWhere[] = 'c.couponKind = ?';
$this->db->bind_param_push($this->arrBind, $this->fieldTypes['coupon']['couponKind'], $mode);
$amountSql = 'SELECT count(couponNo) FROM ' . DB_COUPON . ' WHERE couponKind = "' . $mode . '"' . $addSaveTypeWhere . $addUseTypeWhereSql . $addDeviceTypeWhereSql;
}
list($page->recode['amount']) = $this->db->fetch($amountSql, 'row');
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$this->db->strField = "c.*";
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = $sort['fieldName'] . ' ' . $sort['sortMode'];
if($getValue['couponUseLayer'] != 'attendance_detail') { // 출석체크 쿠폰 혜택지급 레이어에서는 전체 쿠폰리스트 필요하여 limit 삭제
$this->db->strLimit = $page->recode['start'] . ',' . $getValue['pageNum'];
}
// 검색 카운트
$strSQL = ' SELECT COUNT(c.couponNo) AS cnt FROM ' . DB_COUPON .' as c ' . ' WHERE ' . $this->db->strWhere;
$res = $this->db->query_fetch($strSQL, $this->arrBind, false);
$page->recode['total'] = $res['cnt']; // 검색 레코드 수
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ',m.isDelete FROM ' . DB_COUPON . ' as c LEFT OUTER JOIN ' . DB_MANAGER . " as m ON c.managerNo = m.sno" . gd_implode(' ', $query);
// \Logger::debug($strSQL, $this->arrBind);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
Manager::displayListData($data);
 
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
 
 
 
 
 
 
 
 
 
 
$getData['sort'] = $sort;
$getData['search'] = gd_htmlspecialchars($this->search);
$getData['checked'] = $this->checked;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
return $getData;
}
 
/**
*/
public function getMemberCouponAdminList()
{
$getValue = Request::get()->toArray();
$this->setMemberCouponAdminSearch($getValue);
 
$sort['fieldName'] = gd_isset($getValue['sort']['name']);
$sort['sortMode'] = gd_isset($getValue['sort']['mode']);
if (empty($sort['fieldName']) || empty($sort['sortMode'])) {
$sort['fieldName'] = 'mc.regDt';
$sort['sortMode'] = 'desc';
} else {
$sort['fieldName'] = 'mc.' . $sort['fieldName'];
}
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($sort['fieldName'], self::ALLOWED_SORT_FIELDS_MEMBER_COUPON, true)) {
$sort['fieldName'] = 'mc.regDt';
}
if (!in_array($sort['sortMode'], self::ALLOWED_SORT_MODES, true)) {
$sort['sortMode'] = 'desc';
}
 
 
gd_isset($getValue['page'], 1);
gd_isset($getValue['pageNum'], 10);
 
$page = \App::load('\\Component\\Page\\Page', $getValue['page']);
$page->page['list'] = $getValue['pageNum'];
$arrBindTotal = [];
$this->db->bind_param_push($arrBindTotal, 'i', $getValue['couponNo']);
$total = $this->db->query_fetch('SELECT count(mc.memNo) as total FROM ' . DB_MEMBER_COUPON . ' as mc WHERE mc.couponNo = ?', $arrBindTotal, false);
$page->recode['amount'] = $total['total'];
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$this->db->strField = "mc.*, c.couponNm, c.couponDescribed, c.couponSaveType, ";
$this->db->strField .= " m.memId, m.memNm, m.groupSno, IF(ms.connectFl='y', ms.snsTypeFl, '') AS snsTypeFl";
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strJoin = 'LEFT JOIN ' . DB_MEMBER . ' as m ON m.memNo = mc.memNo ';
$this->db->strJoin .= 'LEFT JOIN ' . DB_MEMBER_SNS . ' as ms ON mc.memNo = ms.memNo ';
$this->db->strJoin .= ' LEFT JOIN ' . DB_COUPON . ' as c ON c.couponNo = mc.couponNo';
$this->db->strOrder = $sort['fieldName'] . ' ' . $sort['sortMode'];
$this->db->strLimit = $page->recode['start'] . ',' . $getValue['pageNum'];
 
// 검색 카운트
$strSQL = ' SELECT COUNT(mc.memNo) AS cnt FROM ' . DB_MEMBER_COUPON .' as mc ' . $this->db->strJoin . ' WHERE ' . $this->db->strWhere;
$res = $this->db->query_fetch($strSQL, $this->arrBind, false);
$page->recode['total'] = $res['cnt']; // 검색 레코드 수
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MEMBER_COUPON . ' as mc ' . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
$getData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
$getData['sort'] = $sort;
$getData['search'] = gd_htmlspecialchars($this->search);
$getData['checked'] = $this->checked;
return $getData;
 
 
 
 
}
 
/**
// 발급방식에 따른 체크
// 회원다운로드 일 경우
if ($arrData['couponSaveType'] == 'down') {
if ($arrData['couponUseType'] == 'order' || $arrData['couponUseType'] == 'delivery') { // 쿠폰유형–상품쿠폰(‘product’),주문쿠폰(‘order’),배송비쿠폰('delivery')
throw new \Exception(__('회원다운로드 발급은 상품 쿠폰만 생성할 수 있습니다.'));
}
$validator->add('couponDisplayType', 'yn', true); // 상세노출기간종류–즉시(‘n’),예약(‘y’)
if ($arrData['couponDisplayType'] == 'y') {
}
}
 
 
$validator->add('couponNm', null, true); // 쿠폰명
// $validator->add('couponNm', 'maxlen', true, null, 30); // 쿠폰명 최대 30자
$validator->add('couponDescribed', '', null); // 쿠폰설명
}
 
// 발급 가능 회원등급 선택
$arrData['couponApplyMemberGroupDisplayType'] = 'n';
$validator->add('couponApplyMemberGroupDisplayType', null, null); // 발급/사용 가능한 회원등급에게만 쿠폰노출
 
 
 
if ($arrData['couponSaveType'] == 'manual') { // 발급방식이 '수동발급' 경우
$arrData['couponApplyMemberGroup'] = '';
$validator->add('couponApplyMemberGroup', null, null); // 선택된 회원등급
// 키워드 검색
if ($this->search['key'] && $this->search['keyword']) {
if(empty($getValue['couponUseType']) === true) { // 출석체크 상세보기 레이어에서는 쿠폰검색이 아닌 회원검색이므로 해당 where절 포함안함
 
 
 
 
if ($this->search['key'] == 'all') {
$tmpWhere = ['couponNm', 'couponDescribed', 'couponInsertAdminId'];
$arrWhereAll = [];
}
// 키워드 검색
if ($this->search['key'] && $this->search['keyword']) {
 
 
 
 
if ($this->search['key'] == 'all') {
$tmpWhere = ['memId', 'memNm'];
$arrWhereAll = [];
 
변경 후CouponAdmin.php
use Component\Sms\SmsAdmin;
use Component\Sms\SmsAutoCode;
use Component\Validator\Validator;
use Framework\Debug\Exception\DatabaseException;
use Framework\File\FileHandler;
use Framework\ObjectStorage\Service\ImageUploadService;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Globals;
use Origin\Enum\ApiClient\Notification\SmsPersonalInfoAccessLogEventType;
use Origin\Service\Coupon\CopyCouponService;
/**
* 쿠폰 리스트 가져오기
*
* @param string|null $mode 조회 모드 (미지정 시 'online', 'all'이면 카운트 포함)
* @param string|array $addSaveTypeWhere 발급방식(couponSaveType) 필터 (manual·down·auto)
* @param array $addUseTypeWhereIn 사용유형(couponUseType) 필터 (product·order·delivery·gift)
* @param array $addDeviceTypeWhereIn 디바이스유형(couponDeviceType) 필터 (all·pc·mobile)
*/
public function getCouponAdminList($mode = null, $addSaveTypeWhere = '', $addUseTypeWhereIn = [], $addDeviceTypeWhereIn = [])
{
try {
$getValue = Request::get()->toArray();
$this->setCouponAdminSearch($getValue);
 
$sort['fieldName'] = gd_isset($getValue['sort']['name']);
$sort['sortMode'] = gd_isset($getValue['sort']['mode']);
if (empty($sort['fieldName']) || empty($sort['sortMode'])) {
$sort['fieldName'] = 'c.regDt';
$sort['sortMode'] = 'desc';
} else {
$sort['fieldName'] = 'c' . $sort['fieldName'];
}
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($sort['fieldName'], self::ALLOWED_SORT_FIELDS_COUPON, true)) {
$sort['fieldName'] = 'c.regDt';
}
if (!in_array($sort['sortMode'], self::ALLOWED_SORT_MODES, true)) {
$sort['sortMode'] = 'desc';
}
 
// 레이어에서 자바스크립트 페이징 처리시 사용되는 구문
if (gd_isset($getValue['pagelink'])) {
$getValue['page'] = (int) str_replace('page=', '', preg_replace('/^{page=[0-9]+}/', '', gd_isset($getValue['pagelink'])));
}
gd_isset($getValue['page'], 1);
gd_isset($getValue['pageNum'], 10);
 
$page = \App::load('\\Component\\Page\\Page', $getValue['page']);
$page->page['list'] = $getValue['pageNum'];
 
if (!$mode) {
$mode = 'online';
}
 
$addSaveTypeWhereIn = !empty($addSaveTypeWhere) ? (array) $addSaveTypeWhere : [];
// 발급방식 필터 (manual·down·auto, 문자열·배열 모두 허용)
[$this->arrWhere, $this->arrBind] = $this->bindParameterByIn($this->arrWhere, $this->arrBind, 'couponSaveType', $addSaveTypeWhereIn, $this->fieldTypes['coupon']['couponSaveType'], 'c');
// 사용유형 필터 (product·order·delivery·gift)
[$this->arrWhere, $this->arrBind] = $this->bindParameterByIn($this->arrWhere, $this->arrBind, 'couponUseType', $addUseTypeWhereIn, 's', 'c');
// 디바이스유형 필터 (all·pc·mobile)
[$this->arrWhere, $this->arrBind] = $this->bindParameterByIn($this->arrWhere, $this->arrBind, 'couponDeviceType', $addDeviceTypeWhereIn, 's', 'c');
 
// 페이징 총계 쿼리 — SQL Injection 방지: 문자열 보간 대신 바인딩 재사용(SEC-POL-001, Dooray 1308)
$amountBind = [];
if ($mode == 'all') {
$this->arrWhere[] = '1';
$amountWhereParts = ['1'];
} else {
$this->arrWhere[] = 'c.couponKind = ?';
$this->db->bind_param_push($this->arrBind, $this->fieldTypes['coupon']['couponKind'], $mode);
$amountWhereParts = ['couponKind = ?'];
$this->db->bind_param_push($amountBind, $this->fieldTypes['coupon']['couponKind'], $mode);
}
// 발급방식 필터 (manual·down·auto)
[$amountWhereParts, $amountBind] = $this->bindParameterByIn($amountWhereParts, $amountBind, 'couponSaveType', $addSaveTypeWhereIn, $this->fieldTypes['coupon']['couponSaveType']);
// 사용유형 필터 (product·order·delivery·gift)
[$amountWhereParts, $amountBind] = $this->bindParameterByIn($amountWhereParts, $amountBind, 'couponUseType', $addUseTypeWhereIn, 's');
// 디바이스유형 필터 (all·pc·mobile)
[$amountWhereParts, $amountBind] = $this->bindParameterByIn($amountWhereParts, $amountBind, 'couponDeviceType', $addDeviceTypeWhereIn, 's');
$amountSql = 'SELECT count(couponNo) as cnt FROM ' . DB_COUPON . ' WHERE ' . implode(' AND ', $amountWhereParts);
$amountRow = $this->db->query_fetch($amountSql, $amountBind, false);
$page->recode['amount'] = $amountRow['cnt'];
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$this->db->strField = "c.*";
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strOrder = $sort['fieldName'] . ' ' . $sort['sortMode'];
if($getValue['couponUseLayer'] != 'attendance_detail') { // 출석체크 쿠폰 혜택지급 레이어에서는 전체 쿠폰리스트 필요하여 limit 삭제
$this->db->strLimit = $page->recode['start'] . ',' . $getValue['pageNum'];
 
 
}
// 검색 카운트
$strSQL = ' SELECT COUNT(c.couponNo) AS cnt FROM ' . DB_COUPON .' as c ' . ' WHERE ' . $this->db->strWhere;
$res = $this->db->query_fetch($strSQL, $this->arrBind, false);
$page->recode['total'] = $res['cnt']; // 검색 레코드 수
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ',m.isDelete FROM ' . DB_COUPON . ' as c LEFT OUTER JOIN ' . DB_MANAGER . " as m ON c.managerNo = m.sno" . gd_implode(' ', $query);
// \Logger::debug($strSQL, $this->arrBind);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
Manager::displayListData($data);
 
 
 
 
 
 
 
$getData['data'] = gd_htmlspecialchars(gd_isset($data));
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$getData['sort'] = $sort;
$getData['search'] = gd_htmlspecialchars($this->search);
$getData['checked'] = $this->checked;
 
return $getData;
} catch (DatabaseException $e) {
\App::getInstance('logger')->channel('coupon')->error('쿠폰 목록 조회 실패', ['exception' => $e]);
throw new \Exception(__('쿠폰 목록 조회에 실패하였습니다.'));
}
}
 
/**
* IN 조건절·바인딩 값 추가 (getCouponAdminList의 arrWhere/카운트 쿼리 공통)
*
* @param array $whereParams 조건절 배열
* @param array $bindParam 바인딩 배열
* @param string $column 컬럼명
* @param array $values 필터 값 목록
* @param string $bindType 바인딩 타입 문자
* @param string $prefix 컬럼에 붙을 테이블 별칭
*
* @return array [조건절 배열, 바인딩 배열]
*/
protected function bindParameterByIn($whereParams, $bindParam, $column, $values, $bindType, $prefix = '')
{
if (!is_array($values) || empty($values)) {
return [$whereParams, $bindParam];
}
$column = $prefix !== '' ? $prefix . '.' . $column : $column;
$whereParams[] = $column . ' IN (' . implode(',', array_fill(0, count($values), '?')) . ')';
foreach ($values as $value) {
$this->db->bind_param_push($bindParam, $bindType, $value);
}
 
return [$whereParams, $bindParam];
}
 
/**
*/
public function getMemberCouponAdminList()
{
try {
$getValue = Request::get()->toArray();
$this->setMemberCouponAdminSearch($getValue);
 
$sort['fieldName'] = gd_isset($getValue['sort']['name']);
$sort['sortMode'] = gd_isset($getValue['sort']['mode']);
if (empty($sort['fieldName']) || empty($sort['sortMode'])) {
$sort['fieldName'] = 'mc.regDt';
$sort['sortMode'] = 'desc';
} else {
$sort['fieldName'] = 'mc.' . $sort['fieldName'];
}
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($sort['fieldName'], self::ALLOWED_SORT_FIELDS_MEMBER_COUPON, true)) {
$sort['fieldName'] = 'mc.regDt';
}
if (!in_array($sort['sortMode'], self::ALLOWED_SORT_MODES, true)) {
$sort['sortMode'] = 'desc';
}
 
gd_isset($getValue['page'], 1);
gd_isset($getValue['pageNum'], 10);
 
$page = \App::load('\\Component\\Page\\Page', $getValue['page']);
$page->page['list'] = $getValue['pageNum'];
$arrBindTotal = [];
$this->db->bind_param_push($arrBindTotal, 'i', $getValue['couponNo']);
$total = $this->db->query_fetch('SELECT count(mc.memNo) as total FROM ' . DB_MEMBER_COUPON . ' as mc WHERE mc.couponNo = ?', $arrBindTotal, false);
$page->recode['amount'] = $total['total'];
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$this->db->strField = "mc.*, c.couponNm, c.couponDescribed, c.couponSaveType, ";
$this->db->strField .= " m.memId, m.memNm, m.groupSno, IF(ms.connectFl='y', ms.snsTypeFl, '') AS snsTypeFl";
$this->db->strWhere = gd_implode(' AND ', gd_isset($this->arrWhere));
$this->db->strJoin = 'LEFT JOIN ' . DB_MEMBER . ' as m ON m.memNo = mc.memNo ';
$this->db->strJoin .= 'LEFT JOIN ' . DB_MEMBER_SNS . ' as ms ON mc.memNo = ms.memNo ';
$this->db->strJoin .= ' LEFT JOIN ' . DB_COUPON . ' as c ON c.couponNo = mc.couponNo';
$this->db->strOrder = $sort['fieldName'] . ' ' . $sort['sortMode'];
$this->db->strLimit = $page->recode['start'] . ',' . $getValue['pageNum'];
 
// 검색 카운트
$strSQL = ' SELECT COUNT(mc.memNo) AS cnt FROM ' . DB_MEMBER_COUPON .' as mc ' . $this->db->strJoin . ' WHERE ' . $this->db->strWhere;
$res = $this->db->query_fetch($strSQL, $this->arrBind, false);
$page->recode['total'] = $res['cnt']; // 검색 레코드 수
$page->setPage();
$page->setUrl(\Request::getQueryString());
 
$query = $this->db->query_complete();
$strSQL = 'SELECT ' . array_shift($query) . ' FROM ' . DB_MEMBER_COUPON . ' as mc ' . gd_implode(' ', $query);
$data = $this->db->query_fetch($strSQL, $this->arrBind);
 
$getData['data'] = gd_htmlspecialchars(gd_isset($data));
$getData['sort'] = $sort;
$getData['search'] = gd_htmlspecialchars($this->search);
$getData['checked'] = $this->checked;
return $getData;
} catch (DatabaseException $e) {
\App::getInstance('logger')->channel('coupon')->error('쿠폰별 회원 목록 조회 실패', ['exception' => $e]);
throw new \Exception(__('쿠폰별 회원 목록 조회에 실패하였습니다.'));
}
}
 
/**
// 발급방식에 따른 체크
// 회원다운로드 일 경우
if ($arrData['couponSaveType'] == 'down') {
if ($arrData['couponUseType'] === 'delivery' || $arrData['couponUseType'] === 'gift') { // 쿠폰유형–상품쿠폰('product'),주문쿠폰('order'),배송비쿠폰('delivery'),기프트쿠폰('gift') → delivery·gift는 회원다운로드 불가
throw new \Exception(__('회원다운로드 발급은 상품·주문 쿠폰만 생성할 수 있습니다.'));
}
$validator->add('couponDisplayType', 'yn', true); // 상세노출기간종류–즉시(‘n’),예약(‘y’)
if ($arrData['couponDisplayType'] == 'y') {
}
}
 
$arrData['couponNm'] = StringUtils::xssClean($arrData['couponNm']);
$validator->add('couponNm', null, true); // 쿠폰명
// $validator->add('couponNm', 'maxlen', true, null, 30); // 쿠폰명 최대 30자
$validator->add('couponDescribed', '', null); // 쿠폰설명
}
 
// 발급 가능 회원등급 선택
// 회원다운로드는 다운로드 목록 노출에 사용하므로 전송값 유지함
if ($arrData['couponSaveType'] !== 'down') {
$arrData['couponApplyMemberGroupDisplayType'] = 'n';
$validator->add('couponApplyMemberGroupDisplayType', null, null); // 발급/사용 가능한 회원등급에게만 쿠폰노출
}
if ($arrData['couponSaveType'] == 'manual') { // 발급방식이 '수동발급' 경우
$arrData['couponApplyMemberGroup'] = '';
$validator->add('couponApplyMemberGroup', null, null); // 선택된 회원등급
// 키워드 검색
if ($this->search['key'] && $this->search['keyword']) {
if(empty($getValue['couponUseType']) === true) { // 출석체크 상세보기 레이어에서는 쿠폰검색이 아닌 회원검색이므로 해당 where절 포함안함
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($this->search['key'], array_keys($this->search['combineSearch']), true)) {
throw new \Exception(__('올바르지 않은 검색 조건입니다.'));
}
if ($this->search['key'] == 'all') {
$tmpWhere = ['couponNm', 'couponDescribed', 'couponInsertAdminId'];
$arrWhereAll = [];
}
// 키워드 검색
if ($this->search['key'] && $this->search['keyword']) {
// 화이트리스트 검증 — SQL Injection 방지
if (!in_array($this->search['key'], array_keys($this->search['combineSearch']), true)) {
throw new \Exception(__('올바르지 않은 검색 조건입니다.'));
}
if ($this->search['key'] == 'all') {
$tmpWhere = ['memId', 'memNm'];
$arrWhereAll = [];
 
Component/Coupon/CouponDAO.php+80
변경 전CouponDAO.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후CouponDAO.php
<?php
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Component\Coupon;
 
use App;
use Component\Database\DBTableField;
use Framework\Database\DBTool;
use Exception;
 
class CouponDAO
{
/** @var DBTool $db */
protected $db;
protected $fieldTypes;
protected $logger;
 
public function __construct()
{
$this->db = App::load('DB');
$this->fieldTypes['coupon'] = DBTableField::getFieldTypes('tableCoupon');
$this->logger = App::getInstance('logger');
}
 
/**
* 설정수량 검증과 회원쿠폰 등록을 하나의 트랜잭션으로 처리
*
* @param array $arrData 회원쿠폰 등록 데이터
*
* @throws Exception
*/
public function insertMemberCoupon(array $arrData): void
{
$arrBind = [];
try {
$this->db->begin_tran();
if (!$this->updateCouponSaveCount($arrData['couponNo'])) {
throw new Exception();
}
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
$this->db->commit();
} catch (\Throwable $e) {
$this->db->rollback();
$this->logger->channel('coupon')->warning('회원쿠폰 발급 실패', ['couponNo' => $arrData['couponNo'], 'exception' => $e]);
throw new Exception('발급 조건이 맞지 않습니다.');
} finally {
unset($arrBind);
}
}
 
/**
* 조건부 UPDATE로 설정수량 초과 여부를 원자적으로 검증
*
* @param int $couponNo 쿠폰 고유 번호
*
* @return bool 설정수량 이내에서 발급 수 증가 성공 여부
*/
protected function updateCouponSaveCount($couponNo): bool
{
$arrBind = [];
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponNo'], $couponNo);
$affectedRows = $this->db->set_update_db(
DB_COUPON,
'couponSaveCount = couponSaveCount+1',
"couponNo = ? AND (couponAmountType != 'y' OR couponSaveCount < couponAmount)",
$arrBind,
false,
false
);
unset($arrBind);
 
return $affectedRows >= 1;
}
}
 
Component/Coupon/Coupon.php+379-179
변경 전Coupon.php
use Component\Storage\Storage;
use Component\Cart\CartAdmin;
use Framework\Object\SimpleStorage;
 
use Framework\Utility\ArrayUtils;
 
use Globals;
use Request;
use Session;
{
const ERROR_VIEW = 'ERROR_VIEW';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
const ECT_ALREADY_GIVE = 'Coupon.ECT_ALREADY_GIVE';
const ECT_NOTMATCH_SERIAL = 'Coupon.ECT_NOTMATCH_SERIAL';
const ECT_WRONG_EXPIREDATE = 'Coupon.ECT_WRONG_EXPIREDATE';
protected $goodsCouponDownList;
protected $changeStatusAuto;
 
 
 
/**
* 생성자
*/
// 발급여부 쿠폰(발급중)
$arrWhere[] = 'c.couponType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponType'], 'y');
// 상품 쿠폰
$arrWhere[] = 'c.couponUseType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponUseType'], 'product');
 
 
// 회원다운로드 쿠폰
$arrWhere[] = 'c.couponSaveType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponSaveType'], 'down');
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 공급사
if ($val['couponExceptProviderType'] == 'y') {
$exceptProviderGroupArr = explode(INT_DIVISION, $val['couponExceptProvider']);
if (gd_array_search($goodsData['scmNo'], $exceptProviderGroupArr) !== false) {//공급사 존재
$removeKey[] = $key;
continue;
} else {//공급사 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 카테고리
if ($val['couponExceptCategoryType'] == 'y') {
$exceptCategoryGroupArr = explode(INT_DIVISION, $val['couponExceptCategory']);
$matchCateData = 0;
foreach ($cateCdArr as $cateKey => $cateVal) {
if (gd_array_search($cateVal, $exceptCategoryGroupArr) !== false) {//카테고리 존재
$matchCateData++;
} else {//카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
if ($matchCateData > 0) {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재
$removeKey[] = $key;
continue;
} else {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
 
 
 
 
 
 
 
 
 
 
 
 
}
}
// 발급 제외 브랜드
if ($val['couponExceptBrandType'] == 'y') {
$exceptBrandGroupArr = explode(INT_DIVISION, $val['couponExceptBrand']);
if (gd_array_search($goodsData['brandCd'], $exceptBrandGroupArr) !== false) {//브랜드 존재
$removeKey[] = $key;
continue;
} else {//브랜드 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 상품
if ($val['couponExceptGoodsType'] == 'y') {
$exceptGoodsGroupArr = explode(INT_DIVISION, $val['couponExceptGoods']);
if (gd_array_search($goodsNo, $exceptGoodsGroupArr) !== false) {//상품 존재
$removeKey[] = $key;
continue;
} else {//상품 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 허용 체크
if ($val['couponApplyProductType'] == 'all') {//전체 발급
 
} else if ($val['couponApplyProductType'] == 'provider') {//공급사 발급
$applyProviderGroupArr = explode(INT_DIVISION, $val['couponApplyProvider']);
if (gd_array_search($goodsData['scmNo'], $applyProviderGroupArr) !== false) {//공급사 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//공급사 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'category') {//카테고리 발급
$applyCategoryGroupArr = explode(INT_DIVISION, $val['couponApplyCategory']);
$matchCateData = 0;
foreach ($cateCdArr as $cateKey => $cateVal) {
if (gd_array_search($cateVal, $applyCategoryGroupArr) !== false) {//카테고리 존재
$matchCateData++;
} else {//카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
}
if ($matchCateData > 0) {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'brand') {//브랜드 발급
$applyBrandGroupArr = explode(INT_DIVISION, $val['couponApplyBrand']);
if (gd_array_search($goodsData['brandCd'], $applyBrandGroupArr) !== false) {//브랜드 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//브랜드 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'goods') {//상품 발급
$applyGoodsGroupArr = explode(INT_DIVISION, $val['couponApplyGoods']);
if (gd_array_search($goodsNo, $applyGoodsGroupArr) !== false) {//상품 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//상품 존재안함
$removeKey[] = $key;
continue;
}
}
if ($memNo) {
}
unset($removeKey);
 
 
 
 
 
 
 
return $couponListData;
}
 
}
}
// 쿠폰 사용 회원등급
$data = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = " . $couponData['couponNo'] . " ORDER BY couponUseMemberGroup");
 
 
if ($data) {
foreach ($data as $key => $val) {
$groupNm = GroupUtil::getGroupName('sno=' . $val['couponUseMemberGroup']);
}
} else {
// 쿠폰 사용 회원등급
$data = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = " . $couponVal['couponNo'] . " ORDER BY couponUseMemberGroup");
 
 
if ($data) {
unset($couponArrData[$couponKey]['couponApplyMemberGroup']);
foreach ($data as $key => $val) {
unset($this->arrBind);
unset($this->arrWhere);
 
$memberCouponData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
$memberCouponData['sort'] = $sort;
$memberCouponData['search'] = gd_htmlspecialchars($this->search);
$memberCouponData['selected'] = $this->selected;
unset($this->arrBind);
unset($this->arrWhere);
 
$memberCouponData['data'] = gd_htmlspecialchars_stripslashes(gd_isset($data));
 
return $memberCouponData;
}
} else if ($val['couponSaveType'] == 'manual') {
$convertCouponArrData[$key]['couponSaveType'] = '수동발급';
}
 
 
if ($val['couponUseType'] == 'product') {
$convertCouponArrData[$key]['couponUseType'] = '상품적용쿠폰';
} else if ($val['couponUseType'] == 'order') {
unset($arrBind);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 상품에 적용된 회원다운로드 쿠폰을 발급 받기
*
public function setGoodsCouponMemberSave($couponNo, $goodsNo, $memNo, $memGroupNo, $scmNo = null, $brandCd = null)
{
$checkCoupon = false;
 
$goodsMemberCouponArrData = $this->getGoodsCouponDownList($goodsNo, $memNo, $memGroupNo, null, null, $scmNo, $brandCd);
 
 
foreach ($goodsMemberCouponArrData as $val) {
 
if ($val['chkMemberCoupon'] == 'YES') {
$couponNoData[] = $val['couponNo'];
}
}
 
if (is_array($couponNo)) {
 
 
 
 
 
 
foreach ($couponNo as $val) {
$checkCoupon = gd_in_array($val, $couponNoData);
 
if ($checkCoupon == true) {
$arrData = [];
$arrData['couponNo'] = $val;
$arrData['couponSaveAdminId'] = $goodsNo . '의 상품쿠폰';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($val);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($val);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
$this->setCouponMemberSaveCount($val);
 
 
 
 
 
 
 
 
 
 
 
 
 
} else {
 
throw new \Exception('발급 조건이 맞지 않는 쿠폰이있습니다. 쿠폰다운창을 다시 열고 받아주세요.');
}
}
$checkCoupon = gd_in_array($couponNo, $couponNoData);
 
if ($checkCoupon == true) {
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = $goodsNo . '의 상품쿠폰';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = Session::get('member.memNo');
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
$this->setCouponMemberSaveCount($couponNo);
 
 
 
 
 
 
 
 
 
 
 
 
 
} else {
 
throw new \Exception('발급 조건이 맞지 않습니다.');
}
}
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 회원다운로드 쿠폰의 링크클릭으로 쿠폰 발급
*
*/
public function setCouponLinkDown($couponNo, $memNo, $memGroupNo)
{
$couponLinkDownData = $this->getCouponLinkDownUsable($couponNo, $memNo, $memGroupNo);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = '회원다운로드쿠폰 링크 다운';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
 
unset($arrData);
unset($arrBind);
// 쿠폰 발급 카운트 증가
$this->setCouponMemberSaveCount($couponNo);
} else {
throw new \Exception($couponLinkDownData);
 
 
 
 
 
 
 
}
}
 
return gd_htmlspecialchars_stripslashes($getData);
}
 
 
 
 
 
 
 
 
 
 
 
/**
* 상품의 해당 쿠폰 리스트용 전체 쿠폰 리스트 추출
*
// 발급여부 쿠폰(발급중)
$arrWhere[] = 'c.couponType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponType'], 'y');
// 상품 쿠폰
$arrWhere[] = 'c.couponUseType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponUseType'], 'product');
 
 
 
 
 
// 회원다운로드 쿠폰
$arrWhere[] = 'c.couponSaveType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponSaveType'], 'down');
$memNo = gd_isset(Session::get('member.memNo'), 0);
$groupSno = gd_isset(Session::get('member.groupSno'), 0);
 
 
 
 
if($couponConfig['autoDisplayFl'] == 'y') { // 자동진열
 
$this->db->strField = "*";
// 쿠폰 노출 기간
$arrWhere[] = ' ( couponDisplayType = \'n\' OR ( couponDisplayType = \'y\' AND couponDisplayStartDate < now() AND couponDisplayEndDate > now()) )';
// 쿠폰 사용 기간 만료 체크
$arrWhere[] = ' ((couponUsePeriodType = \'period\' and couponUsePeriodEndDate <> \'0000-00-00 00:00:00\' and couponUsePeriodEndDate > now()) OR (couponUsePeriodType = \'day\' and (couponUseDateLimit is not null or couponUseDateLimit = \'0000-00-00 00:00:00\' or couponUseDateLimit > now())))';
 
$this->db->strLimit = 20;
if($couponConfig['couponzoneSort'] != 'custom') $this->db->strOrder = $couponConfig['couponzoneSort'];
}
$i++;
}
 
$list[0]['list'] = $this->convertCouponArrData($data);
} else { // 수동진열
 
$data[] = $tmp;
}
$list[$key]['title'] = $val;
 
$list[$key]['list'] = $this->convertCouponArrData($data);
}
 
}
}
 
 
 
 
 
 
 
 
 
return $list;
}
/**
}
 
if (is_array($couponNo)) {
 
 
 
 
 
 
$success = 0;
foreach ($couponNo as $val) {
$couponLinkDownData = $this->getCouponLinkDownUsable($val, $memNo, $memGroupNo, true);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $val;
$arrData['couponSaveAdminId'] = '쿠폰존';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($val);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($val);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
 
if ($barcodeDisplayFl === 'y') {
//바코드 발급. - 2018.11.07 parkjs
$memCouponNo = $this->db->insert_id();
$barcodeResult = $barcodeCoupon->setCouponNo([$arrData['couponNo']])->setMemberCouponNo($memCouponNo)->couponBarcodeGenarator('give');
}
 
 
 
 
 
 
 
unset($arrData);
unset($arrBind);
// 쿠폰 발급 카운트 증가
$this->setCouponMemberSaveCount($val);
$success++;
 
 
 
}
}
$return['return'] = true;
$return['success'] = $success;
return $return;
} else {
$couponLinkDownData = $this->getCouponLinkDownUsable($couponNo, $memNo, $memGroupNo);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = '쿠폰존';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$arrBind = $this->db->get_binding(DBTableField::tableMemberCoupon(), $arrData, 'insert', gd_array_keys($arrData), ['memberCouponNo']);
$this->db->set_insert_db(DB_MEMBER_COUPON, $arrBind['param'], $arrBind['bind'], 'y');
 
if ($barcodeDisplayFl === 'y') {
//바코드 발급. - 2018.11.07 parkjs
$memCouponNo = $this->db->insert_id();
$barcodeResult = $barcodeCoupon->setCouponNo([$arrData['couponNo']])->setMemberCouponNo($memCouponNo)->couponBarcodeGenarator('give');
}
 
 
 
 
 
 
unset($arrData);
unset($arrBind);
// 쿠폰 발급 카운트 증가
$this->setCouponMemberSaveCount($couponNo);
$return['return'] = true;
$return['success'] = 1;
return $return;
} else {
throw new \Exception($couponLinkDownData);
 
 
 
}
}
}
public function getCouponUseMemberGroup($couponNo)
{
if ($couponNo) {
$memberGroup = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = " . $couponNo);
 
 
if ($memberGroup) {
$resultMemberGroup = array();
foreach ($memberGroup as $val) {
 
변경 후Coupon.php
use Component\Storage\Storage;
use Component\Cart\CartAdmin;
use Framework\Object\SimpleStorage;
use Framework\SimpleCache\SimpleCache;
use Framework\Utility\ArrayUtils;
use Origin\Service\Coupon\UpdateCouponService;
use Globals;
use Request;
use Session;
{
const ERROR_VIEW = 'ERROR_VIEW';
 
/** 발급 잠금 키 접두사 */
const MEMBER_SAVE_LOCK_KEY_PREFIX = 'coupon_member_save_';
 
/** 반복 발급 카운트 키 접두사 */
const ISSUE_COUNT_CACHE_KEY_PREFIX = 'coupon_issue_count_';
 
/** 반복 발급 요청 수를 세는 기준 시간(초) */
const ISSUE_RATE_LIMIT_WINDOW_SECONDS = 60;
 
/** 기준 시간 안에 같은 회원·쿠폰으로 허용하는 최대 발급 요청 횟수 (초과 시 거부) */
const ISSUE_RATE_LIMIT_MAX_COUNT = 30;
 
/** [전체 다운받기]로 한 번에 요청할 수 있는 쿠폰 개수의 상한 */
const COUPON_DOWN_ALL_MAX = 300;
 
/** 한 번에 요청한 쿠폰 수가 상한을 넘었을 때의 안내 문구 */
const COUPON_DOWN_ALL_EXCEED_MESSAGE = '쿠폰 발급 개수가 초과되어 발급할 수 없습니다.';
 
const ECT_ALREADY_GIVE = 'Coupon.ECT_ALREADY_GIVE';
const ECT_NOTMATCH_SERIAL = 'Coupon.ECT_NOTMATCH_SERIAL';
const ECT_WRONG_EXPIREDATE = 'Coupon.ECT_WRONG_EXPIREDATE';
protected $goodsCouponDownList;
protected $changeStatusAuto;
 
protected array $couponUseTypes = ['product'];
 
/**
* 생성자
*/
// 발급여부 쿠폰(발급중)
$arrWhere[] = 'c.couponType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponType'], 'y');
$arrPlaceHolder = implode(',', array_fill(0, count($this->couponUseTypes), '?'));
$arrWhere[] = 'c.couponUseType IN (' . $arrPlaceHolder . ')';
foreach ($this->couponUseTypes as $couponUseType) {
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponUseType'], $couponUseType);
}
// 회원다운로드 쿠폰
$arrWhere[] = 'c.couponSaveType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponSaveType'], 'down');
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
if ($val['couponUseType'] !== 'order') { // 주문쿠폰은 상품 무관 — 상품범위 제외·허용 판정 비적용
// 발급 제외 공급사
if ($val['couponExceptProviderType'] == 'y') {
$exceptProviderGroupArr = explode(INT_DIVISION, $val['couponExceptProvider']);
if (gd_array_search($goodsData['scmNo'], $exceptProviderGroupArr) !== false) {//공급사 존재
$removeKey[] = $key;
continue;
} else {//공급사 존재안함
 
 
 
 
 
 
 
 
 
 
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 카테고리
if ($val['couponExceptCategoryType'] == 'y') {
$exceptCategoryGroupArr = explode(INT_DIVISION, $val['couponExceptCategory']);
$matchCateData = 0;
foreach ($cateCdArr as $cateKey => $cateVal) {
if (gd_array_search($cateVal, $exceptCategoryGroupArr) !== false) {//카테고리 존재
$matchCateData++;
} else {//카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
if ($matchCateData > 0) {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재
$removeKey[] = $key;
continue;
} else {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 브랜드
if ($val['couponExceptBrandType'] == 'y') {
$exceptBrandGroupArr = explode(INT_DIVISION, $val['couponExceptBrand']);
if (gd_array_search($goodsData['brandCd'], $exceptBrandGroupArr) !== false) {//브랜드 존재
$removeKey[] = $key;
continue;
} else {//브랜드 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 제외 상품
if ($val['couponExceptGoodsType'] == 'y') {
$exceptGoodsGroupArr = explode(INT_DIVISION, $val['couponExceptGoods']);
if (gd_array_search($goodsNo, $exceptGoodsGroupArr) !== false) {//상품 존재
$removeKey[] = $key;
continue;
} else {//상품 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
// 발급 허용 체크
if ($val['couponApplyProductType'] == 'all') {//전체 발급
 
 
} else if ($val['couponApplyProductType'] == 'provider') {//공급사 발급
$applyProviderGroupArr = explode(INT_DIVISION, $val['couponApplyProvider']);
if (gd_array_search($goodsData['scmNo'], $applyProviderGroupArr) !== false) {//공급사 존재
 
 
 
 
 
 
 
 
 
 
 
 
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//공급사 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'category') {//카테고리 발급
$applyCategoryGroupArr = explode(INT_DIVISION, $val['couponApplyCategory']);
$matchCateData = 0;
foreach ($cateCdArr as $cateKey => $cateVal) {
if (gd_array_search($cateVal, $applyCategoryGroupArr) !== false) {//카테고리 존재
$matchCateData++;
} else {//카테고리 존재안함
$couponListData[$key]['chkMemberCoupon'] = 'YES';
}
}
if ($matchCateData > 0) {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {// 상품의 적용된 카테고리와 쿠폰 허용 카테고리 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'brand') {//브랜드 발급
$applyBrandGroupArr = explode(INT_DIVISION, $val['couponApplyBrand']);
if (gd_array_search($goodsData['brandCd'], $applyBrandGroupArr) !== false) {//브랜드 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//브랜드 존재안함
$removeKey[] = $key;
continue;
}
} else if ($val['couponApplyProductType'] == 'goods') {//상품 발급
$applyGoodsGroupArr = explode(INT_DIVISION, $val['couponApplyGoods']);
if (gd_array_search($goodsNo, $applyGoodsGroupArr) !== false) {//상품 존재
$couponListData[$key]['chkMemberCoupon'] = 'YES';
} else {//상품 존재안함
$removeKey[] = $key;
continue;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
}
if ($memNo) {
}
unset($removeKey);
 
foreach ($couponListData as $key => $couponRow) {
if (isset($couponRow['couponNm'])) {
$couponListData[$key]['couponNm'] = gd_htmlspecialchars($couponRow['couponNm']);
}
}
 
return $couponListData;
}
 
}
}
// 쿠폰 사용 회원등급
$useMemberGroupBind = [];
$this->db->bind_param_push($useMemberGroupBind, 'i', $couponData['couponNo']);
$data = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = ? ORDER BY couponUseMemberGroup", $useMemberGroupBind);
if ($data) {
foreach ($data as $key => $val) {
$groupNm = GroupUtil::getGroupName('sno=' . $val['couponUseMemberGroup']);
}
} else {
// 쿠폰 사용 회원등급
$useMemberGroupBind = [];
$this->db->bind_param_push($useMemberGroupBind, 'i', $couponVal['couponNo']);
$data = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = ? ORDER BY couponUseMemberGroup", $useMemberGroupBind);
if ($data) {
unset($couponArrData[$couponKey]['couponApplyMemberGroup']);
foreach ($data as $key => $val) {
unset($this->arrBind);
unset($this->arrWhere);
 
$memberCouponData['data'] = gd_htmlspecialchars(gd_isset($data));
$memberCouponData['sort'] = $sort;
$memberCouponData['search'] = gd_htmlspecialchars($this->search);
$memberCouponData['selected'] = $this->selected;
unset($this->arrBind);
unset($this->arrWhere);
 
$memberCouponData['data'] = gd_htmlspecialchars(gd_isset($data));
 
return $memberCouponData;
}
} else if ($val['couponSaveType'] == 'manual') {
$convertCouponArrData[$key]['couponSaveType'] = '수동발급';
}
// 배지 유형 판별용 원값 보존(아래 한글 라벨 변환 전)
$convertCouponArrData[$key]['couponUseTypeCode'] = $val['couponUseType'];
if ($val['couponUseType'] == 'product') {
$convertCouponArrData[$key]['couponUseType'] = '상품적용쿠폰';
} else if ($val['couponUseType'] == 'order') {
unset($arrBind);
}
 
/**
* 회원 다운로드 발급 거부 감사로그 (실패 시도만 기록 / 성공 발급은 es_memberCoupon 적재)
*
* @param mixed $couponNo 쿠폰고유번호(단건 또는 배열)
* @param int $memNo 회원고유번호
* @param string $reason 거부 사유
*/
protected function logCouponDownFail($couponNo, $memNo, $reason)
{
\App::getInstance('logger')->channel('coupon')->warning('쿠폰 발급 실패', array_merge(
$this->couponDownLogContext($couponNo, $memNo),
['reason' => $reason]
));
}
 
/**
* 회원 다운로드 발급 성공 감사로그
*
* @param mixed $couponNo 쿠폰고유번호(단건 또는 배열)
* @param integer $memNo 회원고유번호
*/
protected function logCouponDownSuccess($couponNo, $memNo)
{
\App::getInstance('logger')->channel('coupon')->info('쿠폰 발급 성공', $this->couponDownLogContext($couponNo, $memNo));
}
 
/**
* 발급 감사로그 공통 컨텍스트(행위자·쿠폰번호·접속지 정보)
*
* @param mixed $couponNo
* @param integer $memNo
* @return array
*/
protected function couponDownLogContext($couponNo, $memNo)
{
$request = \App::getInstance('request');
return [
'couponNo' => $couponNo,
'memNo' => $memNo,
'ip' => $request->getRemoteAddress(),
'userAgent' => $request->getUserAgent(),
'uri' => $request->getRequestUri(),
];
}
 
/**
* 상품에 적용된 회원다운로드 쿠폰을 발급 받기
*
public function setGoodsCouponMemberSave($couponNo, $goodsNo, $memNo, $memGroupNo, $scmNo = null, $brandCd = null)
{
$checkCoupon = false;
$this->setCouponUseTypes(['product', 'order']);
$goodsMemberCouponArrData = $this->getGoodsCouponDownList($goodsNo, $memNo, $memGroupNo, null, null, $scmNo, $brandCd);
$this->setCouponUseTypes(['product']);
$couponUseTypeMap = [];
foreach ($goodsMemberCouponArrData as $val) {
$couponUseTypeMap[$val['couponNo']] = $val['couponUseType'];
if ($val['chkMemberCoupon'] == 'YES') {
$couponNoData[] = $val['couponNo'];
}
}
 
if (is_array($couponNo)) {
// 동일 쿠폰번호 중복 삽입을 1건으로 축약
$couponNo = array_values(array_unique($couponNo));
if (count($couponNo) > self::COUPON_DOWN_ALL_MAX) {
$this->logCouponDownFail($couponNo, $memNo, self::COUPON_DOWN_ALL_EXCEED_MESSAGE);
throw new \Exception(self::COUPON_DOWN_ALL_EXCEED_MESSAGE);
}
foreach ($couponNo as $val) {
$checkCoupon = gd_in_array($val, $couponNoData);
 
if ($checkCoupon == true) {
$redisKey = sprintf(self::MEMBER_SAVE_LOCK_KEY_PREFIX . '%s_%s', $val, $memNo);
try {
SimpleCache::init(SimpleCache::REDIS)->executeWithLock($redisKey, 'coupon', function () use ($val, $memNo, $memGroupNo, $goodsNo, $couponUseTypeMap) {
$this->checkRapidReissueRequest($val, $memNo);
// 중복 발급 방지 재확인
$this->getCouponLinkDownUsable($val, $memNo, $memGroupNo);
$arrData = [];
$arrData['couponNo'] = $val;
$couponUseType = $couponUseTypeMap[$val] ?? 'product';
$arrData['couponSaveAdminId'] = $goodsNo . ($couponUseType === 'order' ? '의 주문쿠폰' : '의 상품쿠폰');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($val);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($val);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$couponDao = \App::load('\\Component\\Coupon\\CouponDAO');
$couponDao->insertMemberCoupon($arrData);
$this->checkCouponType($arrData['couponNo']); // 발급 수 증가로 발급종료 상태로 변경 될 수 있어 실행
});
$this->logCouponDownSuccess($val, $memNo);
} catch (\Exception $e) {
$this->logCouponDownFail($val, $memNo, $e->getMessage());
throw $e;
}
} else {
$this->logCouponDownFail($val, $memNo, '발급 조건이 맞지 않는 쿠폰이있습니다. 쿠폰다운창을 다시 열고 받아주세요.');
throw new \Exception('발급 조건이 맞지 않는 쿠폰이있습니다. 쿠폰다운창을 다시 열고 받아주세요.');
}
}
$checkCoupon = gd_in_array($couponNo, $couponNoData);
 
if ($checkCoupon == true) {
$couponUseType = $couponUseTypeMap[$couponNo] ?? 'product';
$saveMemNo = Session::get('member.memNo');
$redisKey = sprintf(self::MEMBER_SAVE_LOCK_KEY_PREFIX . '%s_%s', $couponNo, $saveMemNo);
try {
SimpleCache::init(SimpleCache::REDIS)->executeWithLock($redisKey, 'coupon', function () use ($couponNo, $saveMemNo, $memGroupNo, $goodsNo, $couponUseType) {
$this->checkRapidReissueRequest($couponNo, $saveMemNo);
$this->getCouponLinkDownUsable($couponNo, $saveMemNo, $memGroupNo);
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = $goodsNo . ($couponUseType === 'order' ? '의 주문쿠폰' : '의 상품쿠폰');
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $saveMemNo;
$couponDao = \App::load('\\Component\\Coupon\\CouponDAO');
$couponDao->insertMemberCoupon($arrData);
$this->checkCouponType($arrData['couponNo']); // 발급 수 증가로 발급종료 상태로 변경 될 수 있어 실행
});
$this->logCouponDownSuccess($couponNo, $saveMemNo);
} catch (\Exception $e) {
$this->logCouponDownFail($couponNo, $saveMemNo, $e->getMessage());
throw $e;
}
} else {
$this->logCouponDownFail($couponNo, $saveMemNo, '발급 조건이 맞지 않습니다.');
throw new \Exception('발급 조건이 맞지 않습니다.');
}
}
}
}
 
/**
* 같은 회원·쿠폰의 짧은 시간 내 반복 발급 요청 차단
*
* @param int $couponNo 쿠폰고유번호
* @param int $memNo 회원고유번호
*
* @throws \Exception 기준 시간 안에 허용 횟수를 초과한 반복 요청일 때
*/
protected function checkRapidReissueRequest($couponNo, $memNo)
{
$cache = SimpleCache::init(SimpleCache::REDIS);
$countKey = self::ISSUE_COUNT_CACHE_KEY_PREFIX . $couponNo . '_' . $memNo;
$count = (int) $cache->get($countKey, fn() => 0);
if ($count >= self::ISSUE_RATE_LIMIT_MAX_COUNT) {
throw new \Exception('잠시 후 다시 시도해주십시오.');
}
$cache->set($countKey, $count + 1, self::ISSUE_RATE_LIMIT_WINDOW_SECONDS);
}
 
/**
* 회원다운로드 쿠폰의 링크클릭으로 쿠폰 발급
*
*/
public function setCouponLinkDown($couponNo, $memNo, $memGroupNo)
{
$redisKey = sprintf(self::MEMBER_SAVE_LOCK_KEY_PREFIX . '%s_%s', $couponNo, $memNo);
try {
SimpleCache::init(SimpleCache::REDIS)->executeWithLock($redisKey, 'coupon', function () use ($couponNo, $memNo, $memGroupNo) {
$this->checkRapidReissueRequest($couponNo, $memNo);
$couponLinkDownData = $this->getCouponLinkDownUsable($couponNo, $memNo, $memGroupNo);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = '회원다운로드쿠폰 링크 다운';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$couponDao = \App::load('\\Component\\Coupon\\CouponDAO');
$couponDao->insertMemberCoupon($arrData);
$this->checkCouponType($arrData['couponNo']); // 발급 수 증가로 발급종료 상태로 변경 될 수 있어 실행
unset($arrData);
} else {
throw new \Exception($couponLinkDownData ?: '발급 조건이 맞지 않습니다.');
}
});
$this->logCouponDownSuccess($couponNo, $memNo);
} catch (\Exception $e) {
$this->logCouponDownFail($couponNo, $memNo, $e->getMessage());
throw $e;
}
}
 
return gd_htmlspecialchars_stripslashes($getData);
}
 
/**
* 쿠폰 사용 유형 설정
*
* @param array $types 쿠폰 사용 유형 배열 (ex: ['product'], ['product', 'order'])
*/
public function setCouponUseTypes(array $types)
{
$this->couponUseTypes = $types;
}
 
/**
* 상품의 해당 쿠폰 리스트용 전체 쿠폰 리스트 추출
*
// 발급여부 쿠폰(발급중)
$arrWhere[] = 'c.couponType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponType'], 'y');
 
// 쿠폰 적용 대상 유형(product=상품, order=주문 등)
$arrPlaceHolder = implode(',', array_fill(0, count($this->couponUseTypes), '?'));
$arrWhere[] = 'c.couponUseType IN (' . $arrPlaceHolder . ')';
foreach ($this->couponUseTypes as $couponUseType) {
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponUseType'], $couponUseType);
}
 
// 회원다운로드 쿠폰
$arrWhere[] = 'c.couponSaveType=?';
$this->db->bind_param_push($arrBind, $this->fieldTypes['coupon']['couponSaveType'], 'down');
$memNo = gd_isset(Session::get('member.memNo'), 0);
$groupSno = gd_isset(Session::get('member.groupSno'), 0);
 
/** @var UpdateCouponService $updateCouponService */
$updateCouponService = \App::getInstance(UpdateCouponService::class);
 
if($couponConfig['autoDisplayFl'] == 'y') { // 자동진열
 
$this->db->strField = "*";
// 쿠폰 노출 기간
$arrWhere[] = ' ( couponDisplayType = \'n\' OR ( couponDisplayType = \'y\' AND couponDisplayStartDate < now() AND couponDisplayEndDate > now()) )';
// 쿠폰 사용 기간 만료 체크
$arrWhere[] = ' ((couponUsePeriodType = \'period\' and couponUsePeriodEndDate <> \'0000-00-00 00:00:00\' and couponUsePeriodEndDate > now()) OR (couponUsePeriodType = \'day\' and (couponUseDateLimit is null or couponUseDateLimit = \'0000-00-00 00:00:00\' or couponUseDateLimit > now())))';
 
$this->db->strLimit = 20;
if($couponConfig['couponzoneSort'] != 'custom') $this->db->strOrder = $couponConfig['couponzoneSort'];
}
$i++;
}
$updateCouponService->convertOrderBenefitsToDisplay($data);
$list[0]['list'] = $this->convertCouponArrData($data);
} else { // 수동진열
 
$data[] = $tmp;
}
$list[$key]['title'] = $val;
$updateCouponService->convertOrderBenefitsToDisplay($data);
$list[$key]['list'] = $this->convertCouponArrData($data);
}
 
}
}
 
foreach ($list as $key => $zoneData) {
foreach ($zoneData['list'] as $cKey => $couponRow) {
if (isset($couponRow['couponNm'])) {
$list[$key]['list'][$cKey]['couponNm'] = gd_htmlspecialchars($couponRow['couponNm']);
}
}
}
 
return $list;
}
/**
}
 
if (is_array($couponNo)) {
// 동일 쿠폰번호 중복 삽입을 1건으로 축약
$couponNo = array_values(array_unique($couponNo));
if (count($couponNo) > self::COUPON_DOWN_ALL_MAX) {
$this->logCouponDownFail($couponNo, $memNo, self::COUPON_DOWN_ALL_EXCEED_MESSAGE);
throw new \Exception(self::COUPON_DOWN_ALL_EXCEED_MESSAGE);
}
$success = 0;
foreach ($couponNo as $val) {
$redisKey = sprintf(self::MEMBER_SAVE_LOCK_KEY_PREFIX . '%s_%s', $val, $memNo);
// 실패 항목은 건너뜀
try {
SimpleCache::init(SimpleCache::REDIS)->executeWithLock($redisKey, 'coupon', function () use ($val, $memNo, $memGroupNo, $barcodeDisplayFl, &$barcodeCoupon, &$success) {
$this->checkRapidReissueRequest($val, $memNo);
$couponLinkDownData = $this->getCouponLinkDownUsable($val, $memNo, $memGroupNo, true);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $val;
$arrData['couponSaveAdminId'] = '쿠폰존';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($val);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($val);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$couponDao = \App::load('\\Component\\Coupon\\CouponDAO');
$couponDao->insertMemberCoupon($arrData);
$this->checkCouponType($arrData['couponNo']); // 발급 수 증가로 발급종료 상태로 변경 될 수 있어 실행
 
if ($barcodeDisplayFl === 'y') {
//바코드 발급
$memCouponNo = $this->db->insert_id();
$barcodeResult = $barcodeCoupon->setCouponNo([$arrData['couponNo']])->setMemberCouponNo($memCouponNo)->couponBarcodeGenarator('give');
}
 
unset($arrData);
$success++;
$this->logCouponDownSuccess($val, $memNo);
}
});
} catch (\Exception $e) {
$this->logCouponDownFail($val, $memNo, $e->getMessage());
continue;
}
}
$return['return'] = true;
$return['success'] = $success;
return $return;
} else {
$redisKey = sprintf(self::MEMBER_SAVE_LOCK_KEY_PREFIX . '%s_%s', $couponNo, $memNo);
try {
return SimpleCache::init(SimpleCache::REDIS)->executeWithLock($redisKey, 'coupon', function () use ($couponNo, $memNo, $memGroupNo, $barcodeDisplayFl, &$barcodeCoupon) {
$this->checkRapidReissueRequest($couponNo, $memNo);
$couponLinkDownData = $this->getCouponLinkDownUsable($couponNo, $memNo, $memGroupNo);
if ($couponLinkDownData) {
$arrData = [];
$arrData['couponNo'] = $couponNo;
$arrData['couponSaveAdminId'] = '쿠폰존';
$arrData['memberCouponStartDate'] = $this->getMemberCouponStartDate($couponNo);
$arrData['memberCouponEndDate'] = $this->getMemberCouponEndDate($couponNo);
$arrData['memberCouponState'] = 'y';
$arrData['memNo'] = $memNo;
$couponDao = \App::load('\\Component\\Coupon\\CouponDAO');
$couponDao->insertMemberCoupon($arrData);
$this->checkCouponType($arrData['couponNo']); // 발급 수 증가로 발급종료 상태로 변경 될 수 있어 실행
 
if ($barcodeDisplayFl === 'y') {
//바코드 발급
$memCouponNo = $this->db->insert_id();
$barcodeResult = $barcodeCoupon->setCouponNo([$arrData['couponNo']])->setMemberCouponNo($memCouponNo)->couponBarcodeGenarator('give');
}
 
unset($arrData);
$return['return'] = true;
$return['success'] = 1;
$this->logCouponDownSuccess($couponNo, $memNo);
return $return;
} else {
throw new \Exception($couponLinkDownData ?: '발급 조건이 맞지 않습니다.');
}
});
} catch (\Exception $e) {
$this->logCouponDownFail($couponNo, $memNo, $e->getMessage());
throw $e;
}
}
}
public function getCouponUseMemberGroup($couponNo)
{
if ($couponNo) {
$useMemberGroupBind = [];
$this->db->bind_param_push($useMemberGroupBind, 'i', $couponNo);
$memberGroup = $this->db->query_fetch("SELECT couponUseMemberGroup FROM " . DB_COUPON_USE_MEMBER_GROUP . " WHERE couponNo = ?", $useMemberGroupBind);
if ($memberGroup) {
$resultMemberGroup = array();
foreach ($memberGroup as $val) {
 
Component/Goods/Goods.php+12-1
변경 전Goods.php
// 쿠폰 할인 금액
if ($couponConfig['couponUseType'] == 'y' && $couponPriceYN && $getData['goodsPrice'] > 0 && empty($getData['goodsPriceString']) === true) {
// 해당 상품의 모든 쿠폰
 
 
 
 
$couponArrData = $this->coupon->getGoodsCouponDownList($getData['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'), null, null, $getData['scmNo'], $getData['brandCd']);
 
 
 
 
// 상품의 해당 쿠폰 리스트 저장 (2022.06 상품리스트 및 상세 성능개선)
if (gd_count($couponArrData) > 0) {
$this->coupon->setGoodsCouponDownInfo($couponArrData);
}
 
 
 
 
 
// 해당 상품의 쿠폰가
$getData['couponDcPrice'] = $couponSalePrice = $this->coupon->getGoodsCouponDisplaySalePrice($couponArrData, $getData['goodsPrice']);
if ($couponSalePrice) {
$getData['couponPrice'] = $getData['goodsPrice'] - $couponSalePrice;
$getData['couponSalePrice'] = $couponSalePrice;
 
변경 후Goods.php
// 쿠폰 할인 금액
if ($couponConfig['couponUseType'] == 'y' && $couponPriceYN && $getData['goodsPrice'] > 0 && empty($getData['goodsPriceString']) === true) {
// 해당 상품의 모든 쿠폰
$supportsCouponUseTypeFilter = method_exists($this->coupon, 'setCouponUseTypes');
if ($supportsCouponUseTypeFilter) {
$this->coupon->setCouponUseTypes(['product', 'order']);
}
$couponArrData = $this->coupon->getGoodsCouponDownList($getData['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'), null, null, $getData['scmNo'], $getData['brandCd']);
if ($supportsCouponUseTypeFilter) {
$this->coupon->setCouponUseTypes(['product']);
}
 
// 상품의 해당 쿠폰 리스트 저장 (2022.06 상품리스트 및 상세 성능개선)
if (gd_count($couponArrData) > 0) {
$this->coupon->setGoodsCouponDownInfo($couponArrData);
}
 
$goodsCouponArrData = array_filter($couponArrData ?: [], function ($downCoupon) {
return ($downCoupon['couponUseType'] ?? 'product') !== 'order';
});
 
// 해당 상품의 쿠폰가
$getData['couponDcPrice'] = $couponSalePrice = $this->coupon->getGoodsCouponDisplaySalePrice($goodsCouponArrData, $getData['goodsPrice']);
if ($couponSalePrice) {
$getData['couponPrice'] = $getData['goodsPrice'] - $couponSalePrice;
$getData['couponSalePrice'] = $couponSalePrice;
 
Component/Worker/NaverDbUrl3.php+28
변경 전NaverDbUrl3.php
/** @var null|\Bundle\Component\Goods\DefineGoods */
private $_defineGoods = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* init
*
 
변경 후NaverDbUrl3.php
/** @var null|\Bundle\Component\Goods\DefineGoods */
private $_defineGoods = null;
 
/**
* 사용가능한 네이버 EP 쿠폰 목록 조회
*
* 네이버 EP에서 사용 가능한 상품·주문 다운로드 쿠폰을 조회
* PC·모바일 각각의 쿠폰 목록을 별도 조회하여 저장
*
* @override
*/
protected function loadCoupon()
{
$where[] = "couponMinOrderPrice = 0 ";
$where[] = "couponAmountType = 'n' ";
$where[] = "(couponApplyMemberGroup = '' or couponApplyMemberGroup is null) ";
 
$canSetCouponUseTypes = method_exists($this->componentCoupon, 'setCouponUseTypes');
// 네이버 EP는 상품·주문 다운로드 쿠폰을 조회 대상에 포함
if ($canSetCouponUseTypes) {
$this->componentCoupon->setCouponUseTypes(['product', 'order']);
}
$this->couponForEpPcList = $this->componentCoupon->getGoodsCouponDownListAll($where, "pc");
$this->couponForEpMobileList = $this->componentCoupon->getGoodsCouponDownListAll($where, "mobile");
 
if ($canSetCouponUseTypes) {
$this->componentCoupon->setCouponUseTypes(['product']);
}
unset($where);
}
 
/**
* init
*
 
Controller/Admin/Promotion/CouponListController.php+2
변경 전CouponListController.php
 
use Exception;
use Framework\Debug\Exception\LayerException;
 
use Origin\Service\Coupon\UpdateCouponService;
use Request;
 
$this->setData('searchKindASelectBox', \Component\Member\Member::getSearchKindASelectBox());
$this->setData('checked', $getData['checked']);
$this->setData('page', $page);
 
}
}
 
변경 후CouponListController.php
 
use Exception;
use Framework\Debug\Exception\LayerException;
use Framework\Security\Token;
use Origin\Service\Coupon\UpdateCouponService;
use Request;
 
$this->setData('searchKindASelectBox', \Component\Member\Member::getSearchKindASelectBox());
$this->setData('checked', $getData['checked']);
$this->setData('page', $page);
$this->setData('couponListToken', Token::generate('couponListToken'));
}
}
 
Controller/Admin/Promotion/CouponManageController.php+2
변경 전CouponManageController.php
use Component\Database\DBTableField;
use Exception;
use Framework\Debug\Exception\LayerException;
 
use Origin\Service\Coupon\UpdateCouponService;
use Origin\Service\Privacy\Masking\CouponListMaskingService;
use Request;
$this->setData('search', gd_isset($getMemberData['search']));
$this->setData('searchKindASelectBox', \Component\Member\Member::getSearchKindASelectBox());
$this->setData('page', $page);
 
}
}
 
변경 후CouponManageController.php
use Component\Database\DBTableField;
use Exception;
use Framework\Debug\Exception\LayerException;
use Framework\Security\Token;
use Origin\Service\Coupon\UpdateCouponService;
use Origin\Service\Privacy\Masking\CouponListMaskingService;
use Request;
$this->setData('search', gd_isset($getMemberData['search']));
$this->setData('searchKindASelectBox', \Component\Member\Member::getSearchKindASelectBox());
$this->setData('page', $page);
$this->setData('couponManageToken', Token::generate('couponManageToken'));
}
}
 
Controller/Admin/Promotion/CouponPsController.php+45-5
변경 전CouponPsController.php
use Component\Sms\SmsSender;
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\File\FileValidator;
 
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleLatestResultsRequestDTO;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
use Origin\Service\Sms\OccupancyAuth\SendGateService;
$postValue = Request::post()->toArray();
$filesValue = Request::files()->toArray();
if (Request::post()->get('couponKind') == 'online') {
 
 
 
 
$this->validateSaveCouponRequest($postValue, $filesValue);
$couponAdmin->saveCoupon($postValue, $filesValue);
$this->layer(__('저장이 완료되었습니다.'), 'parent.unload_callback("' . $postValue['couponEventType'] . '");top.location.href="coupon_list.php?page=' . $ypage . '";');
}
break;
case 'modifyCouponList':
 
 
 
 
 
 
case 'modifyCouponOfflineList':
$couponAdmin->changeCouponType();
break;
case 'deleteCouponList':
 
 
 
 
 
 
 
case 'deleteCouponOfflineList':
$couponAdmin->deleteCoupon();
if (Request::post()->get('mode') == 'deleteCouponList') {
$this->layer(__('삭제 되었습니다.'), 'top.location.reload()');
} else if (Request::post()->get('mode') == 'deleteCouponOfflineList') {
$this->layer(__('삭제 되었습니다.'), 'top.location.href="coupon_offline_list.php";');
}
break;
case 'deleteCouponManage':
 
 
 
 
 
 
 
case 'deleteCouponOfflineManage':
$couponAdmin->deleteMemberCoupon();
$this->layer(__('삭제 되었습니다.'), 'top.location.reload()');
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* @param array $postValue 쿠폰 등록 데이터
* @param array $files 쿠폰 이미지
 
변경 후CouponPsController.php
use Component\Sms\SmsSender;
use Framework\Debug\Exception\LayerNotReloadException;
use Framework\File\FileValidator;
use Framework\Security\Token;
use Origin\DTO\ApiClient\Commerce\Crm\TargetsRepeatRuleLatestResultsRequestDTO;
use Origin\Service\ApiClient\Commerce\Crm\CrmAdminApiClientService;
use Origin\Service\Sms\OccupancyAuth\SendGateService;
$postValue = Request::post()->toArray();
$filesValue = Request::files()->toArray();
if (Request::post()->get('couponKind') == 'online') {
// CSRF 토큰 판별 - 미전송·위조 시 로그 수집
$couponRegistToken = Request::post()->get('couponRegistToken');
Token::check('couponRegistToken', ['couponRegistToken' => $couponRegistToken], false, null, true);
$this->validateRequestOrigin();
$this->validateSaveCouponRequest($postValue, $filesValue);
$couponAdmin->saveCoupon($postValue, $filesValue);
$this->layer(__('저장이 완료되었습니다.'), 'parent.unload_callback("' . $postValue['couponEventType'] . '");top.location.href="coupon_list.php?page=' . $ypage . '";');
}
break;
case 'modifyCouponList':
// CSRF 토큰 판별 - 미전송·위조 시 로그 수집
$couponListToken = Request::post()->get('couponListToken');
Token::check('couponListToken', ['couponListToken' => $couponListToken], false, null, true);
$this->validateRequestOrigin();
$couponAdmin->changeCouponType();
break;
case 'modifyCouponOfflineList':
$couponAdmin->changeCouponType();
break;
case 'deleteCouponList':
// CSRF 토큰 판별 - 미전송·위조 시 로그 수집
$couponListToken = Request::post()->get('couponListToken');
Token::check('couponListToken', ['couponListToken' => $couponListToken], false, null, true);
$this->validateRequestOrigin();
$couponAdmin->deleteCoupon();
$this->layer(__('삭제 되었습니다.'), 'top.location.reload()');
break;
case 'deleteCouponOfflineList':
$couponAdmin->deleteCoupon();
$this->layer(__('삭제 되었습니다.'), 'top.location.href="coupon_offline_list.php";');
 
 
 
 
break;
case 'deleteCouponManage':
// CSRF 토큰 판별 - 미전송·위조 시 로그 수집
$couponManageToken = Request::post()->get('couponManageToken');
Token::check('couponManageToken', ['couponManageToken' => $couponManageToken], false, null, true);
$this->validateRequestOrigin();
$couponAdmin->deleteMemberCoupon();
$this->layer(__('삭제 되었습니다.'), 'top.location.reload()');
break;
case 'deleteCouponOfflineManage':
$couponAdmin->deleteMemberCoupon();
$this->layer(__('삭제 되었습니다.'), 'top.location.reload()');
}
}
 
/**
* @return void
* @throws LayerNotReloadException
*/
protected function validateRequestOrigin()
{
$httpOrigin = Request::server()->get('HTTP_ORIGIN');
if (empty($httpOrigin)) {
return;
}
 
$originHost = strtolower((string) parse_url($httpOrigin, PHP_URL_HOST));
$allowedHosts = [strtolower((string) Request::getHostNoPort()), strtolower((string) Request::getServerName())];
if (empty($originHost) || !in_array($originHost, $allowedHosts, true)) {
\App::getInstance('logger')->channel('coupon')->warning('허용되지 않은 요청 도메인', ['origin' => $httpOrigin, 'host' => $allowedHosts[0]]);
throw new LayerNotReloadException(__('허용되지 않은 도메인에서의 접근입니다.'));
}
}
 
/**
* @param array $postValue 쿠폰 등록 데이터
* @param array $files 쿠폰 이미지
 
Controller/Admin/Promotion/CouponRegistController.php+4
변경 전CouponRegistController.php
use Component\Database\DBTableField;
use Exception;
use Framework\Debug\Exception\LayerException;
 
use Origin\Service\Coupon\UpdateCouponService;
use Request;
 
'promotion/gd_promotion_common.js',
]);
$this->setData('couponData', gd_isset($couponData));
 
 
 
$this->setData('checked', gd_isset($checked));
$this->setData('selected', gd_isset($selected));
$this->setData('ypage', gd_isset($ypage,1));
 
변경 후CouponRegistController.php
use Component\Database\DBTableField;
use Exception;
use Framework\Debug\Exception\LayerException;
use Framework\Security\Token;
use Origin\Service\Coupon\UpdateCouponService;
use Request;
 
'promotion/gd_promotion_common.js',
]);
$this->setData('couponData', gd_isset($couponData));
if (isset($couponData['couponKind']) && $couponData['couponKind'] == 'online') {
$this->setData('couponRegistToken', Token::generate('couponRegistToken'));
}
$this->setData('checked', gd_isset($checked));
$this->setData('selected', gd_isset($selected));
$this->setData('ypage', gd_isset($ypage,1));
 
Controller/Front/Event/CouponzoneController.php+12
변경 전CouponzoneController.php
use Framework\Debug\Exception\AlertRedirectException;
use Component\Coupon\Coupon;
use Framework\ObjectStorage\Service\ImageUploadService;
 
 
/**
* Class AttendStampController
*/
class CouponzoneController extends \Controller\Front\Controller
{
 
 
 
/**
* @inheritdoc
*/
$couponList = $coupon->getCouponzoneList();
 
$this->setData('couponList', $couponList);
 
 
 
if($couponConfig['couponImageType'] == 'basic') {
$couponConfig['pcCouponImagePath'] = PATH_ADMIN_GD_SHARE . 'img/defaultCouponzonePc.png';
}
$couponConfig['pcContents'] = gd_htmlspecialchars_decode($couponConfig['pcContents']);
$couponConfig['pcContents'] = str_replace('<p>&nbsp;</p>', '', $couponConfig['pcContents']);
 
 
 
 
// 웹취약점 개선사항 상단영역 에디터 업로드 이미지 alt 추가
if ($couponConfig['pcContents']) {
} else {
$couponConfig['mobileContents'] = gd_htmlspecialchars_decode($couponConfig['mobileContents']);
$couponConfig['mobileContents'] = str_replace('<p>&nbsp;</p>', '', $couponConfig['mobileContents']);
 
 
 
}
 
$this->setData('couponConfig', $couponConfig);
 
변경 후CouponzoneController.php
use Framework\Debug\Exception\AlertRedirectException;
use Component\Coupon\Coupon;
use Framework\ObjectStorage\Service\ImageUploadService;
use Framework\Security\Token;
 
/**
* Class AttendStampController
*/
class CouponzoneController extends \Controller\Front\Controller
{
// 에디터를 비워 저장하면 남는 값
protected const EMPTY_EDITOR_CONTENTS = '<p><br></p>';
 
/**
* @inheritdoc
*/
$couponList = $coupon->getCouponzoneList();
 
$this->setData('couponList', $couponList);
// 발급 검증용 쿠폰 전용 토큰
$this->setData('couponToken', Token::generate('couponToken'));
 
if($couponConfig['couponImageType'] == 'basic') {
$couponConfig['pcCouponImagePath'] = PATH_ADMIN_GD_SHARE . 'img/defaultCouponzonePc.png';
}
$couponConfig['pcContents'] = gd_htmlspecialchars_decode($couponConfig['pcContents']);
$couponConfig['pcContents'] = str_replace('<p>&nbsp;</p>', '', $couponConfig['pcContents']);
$couponConfig['pcContents'] = trim(str_replace(self::EMPTY_EDITOR_CONTENTS, '', $couponConfig['pcContents'])) === ''
? ''
: $couponConfig['pcContents'];
 
// 웹취약점 개선사항 상단영역 에디터 업로드 이미지 alt 추가
if ($couponConfig['pcContents']) {
} else {
$couponConfig['mobileContents'] = gd_htmlspecialchars_decode($couponConfig['mobileContents']);
$couponConfig['mobileContents'] = str_replace('<p>&nbsp;</p>', '', $couponConfig['mobileContents']);
$couponConfig['mobileContents'] = trim(str_replace(self::EMPTY_EDITOR_CONTENTS, '', $couponConfig['mobileContents'])) === ''
? ''
: $couponConfig['mobileContents'];
}
 
$this->setData('couponConfig', $couponConfig);
 
Controller/Front/Goods/LayerCouponDownController.php+25-6
변경 전LayerCouponDownController.php
use App;
use Framework\Debug\Exception\WarningException;
 
 
use Session;
use Request;
use Exception;
$coupon = App::load(\Component\Coupon\Coupon::class);
$post = Request::post()->toArray();
// 상품의 쿠폰리스트
$goodsCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'],Session::get('member.memNo'),Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
// 해당 상품의 모든 쿠폰을 보기용으로 변환
$convertCouponArrData = $coupon->convertCouponArrData($goodsCouponArrData);
// 상품의 다운받을 수 있는 쿠폰의 개수
$goodsCouponCnt = $coupon->getGoodsCouponDownListCount($goodsCouponArrData);
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->setData('goodsCouponArrData', $goodsCouponArrData);
 
$this->setData('convertCouponArrData', $convertCouponArrData);
$this->setData('goodsNo', $post['goodsNo']);
$this->setData('scmNo', $post['scmNo']);
$this->setData('brandCd', $post['brandCd']);
 
$this->setData('goodsCouponCnt', $goodsCouponCnt);
 
 
} else {
$this->json([
'error' => 10,
 
변경 후LayerCouponDownController.php
use App;
use Framework\Debug\Exception\WarningException;
use Framework\Security\Token;
use Origin\Service\Coupon\UpdateCouponService;
use Session;
use Request;
use Exception;
$coupon = App::load(\Component\Coupon\Coupon::class);
$post = Request::post()->toArray();
$supportsCouponUseTypeFilter = method_exists($coupon, 'setCouponUseTypes');
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product', 'order']);
}
$downCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'],Session::get('member.memNo'),Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product']);
}
/** @var UpdateCouponService $updateCouponService */
$updateCouponService = \App::getInstance(UpdateCouponService::class);
$updateCouponService->convertOrderBenefitsToDisplay($downCouponArrData);
$convertCouponArrData = $coupon->convertCouponArrData($downCouponArrData);
$orderCouponArrData = array_filter($downCouponArrData, function ($downCoupon) {
return ($downCoupon['couponUseType'] ?? '') === 'order';
});
$goodsCouponArrData = array_filter($downCouponArrData, function ($downCoupon) {
return ($downCoupon['couponUseType'] ?? '') === 'product';
});
$this->setData('goodsCouponArrData', $goodsCouponArrData);
$this->setData('orderCouponArrData', $orderCouponArrData);
$this->setData('convertCouponArrData', $convertCouponArrData);
$this->setData('goodsNo', $post['goodsNo']);
$this->setData('scmNo', $post['scmNo']);
$this->setData('brandCd', $post['brandCd']);
$goodsCouponCnt = empty($downCouponArrData) ? 0 : $coupon->getGoodsCouponDownListCount($downCouponArrData);
$this->setData('goodsCouponCnt', $goodsCouponCnt);
// 발급 검증용 쿠폰 전용 토큰
$this->setData('couponToken', Token::generate('couponToken'));
} else {
$this->json([
'error' => 10,
 
Controller/Front/Goods/LayerCouponPsController.php+16-1
변경 전LayerCouponPsController.php
use App;
use Framework\Debug\Exception\WarningException;
 
use Session;
use Request;
use Exception;
// 쿠폰 다운
case 'coupon_down':
case 'coupon_down_all':
 
 
 
 
 
 
// --- 쿠폰 class
$coupon = \App::load('\\Component\\Coupon\\Coupon');
$coupon->setGoodsCouponMemberSave(Request::post()->get('couponNo'), Request::post()->get('goodsNo'),Session::get('member.memNo'),Session::get('member.groupSno'), $post['scmNo'], $post['brandCd']);
// 상품의 다운받을 수 있는 쿠폰의 개수
$goodsCouponCnt = $coupon->getGoodsCouponDownListCount(null, $post['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'));
 
 
 
 
 
 
 
 
$this->json([
'code' => 200,
 
변경 후LayerCouponPsController.php
use App;
use Framework\Debug\Exception\WarningException;
use Framework\Security\Token;
use Session;
use Request;
use Exception;
// 쿠폰 다운
case 'coupon_down':
case 'coupon_down_all':
if (Request::getMethod() !== 'POST') {
throw new Exception(__('잘못된 요청입니다.'));
}
// 쿠폰 전용 토큰 판별 - 미전송·위조 시 로그 수집
$couponToken = Request::post()->get('couponToken');
Token::check('couponToken', ['couponToken' => $couponToken], false, null, true);
// --- 쿠폰 class
$coupon = \App::load('\\Component\\Coupon\\Coupon');
$coupon->setGoodsCouponMemberSave(Request::post()->get('couponNo'), Request::post()->get('goodsNo'),Session::get('member.memNo'),Session::get('member.groupSno'), $post['scmNo'], $post['brandCd']);
// 상품의 다운받을 수 있는 쿠폰의 개수
$supportsCouponUseTypeFilter = method_exists($coupon, 'setCouponUseTypes');
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product', 'order']);
}
$downCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product']);
}
$goodsCouponCnt = empty($downCouponArrData) ? 0 : $coupon->getGoodsCouponDownListCount($downCouponArrData);
$this->json([
'code' => 200,
 
Controller/Front/Mypage/CouponPsController.php+7
변경 전CouponPsController.php
use Exception;
use Framework\Debug\Exception\AlertOnlyException;
use Framework\Debug\Exception\AlertReloadException;
 
use App;
use Session;
use Request;
case 'couponDown' :
case 'couponDownAll' :
try {
 
 
 
 
 
 
$result = $coupon->setCouponzoneDown(Request::post()->get('couponNo'), Session::get('member.memNo'), Session::get('member.groupSno'));
 
if ($result['success'] == 0) {
 
변경 후CouponPsController.php
use Exception;
use Framework\Debug\Exception\AlertOnlyException;
use Framework\Debug\Exception\AlertReloadException;
use Framework\Security\Token;
use App;
use Session;
use Request;
case 'couponDown' :
case 'couponDownAll' :
try {
if (Request::getMethod() !== 'POST' || !Request::isAjax()) {
throw new Exception(__('잘못된 요청입니다.'));
}
// 쿠폰 전용 토큰 판별 - 미전송·위조 시 로그 수집
$couponToken = Request::post()->get('couponToken');
Token::check('couponToken', ['couponToken' => $couponToken], false, null, true);
$result = $coupon->setCouponzoneDown(Request::post()->get('couponNo'), Session::get('member.memNo'), Session::get('member.groupSno'));
 
if ($result['success'] == 0) {
 
Controller/Front/Order/LayerCouponApplyOrderController.php+18
변경 전LayerCouponApplyOrderController.php
}
}
 
 
 
 
 
 
 
 
 
$this->setData('memberCouponArrData', $memberCouponArrData);
$this->setData('convertMemberCouponArrData', $convertMemberCouponArrData);
$this->setData('convertMemberCouponPriceArrData', $convertMemberCouponPriceArrData);
}
}
}
 
 
 
 
 
 
 
 
 
$this->setData('goodsCouponArrData', $goodsCouponData['goodsCouponArrData']); // 쿠폰 DB 데이터
$this->setData('convertGoodsCouponArrData', $goodsCouponData['convertGoodsCouponArrData']); // 변환 쿠폰 데이터
$this->setData('convertGoodsCouponPriceArrData', $goodsCouponData['convertGoodsCouponPriceArrData']); // 쿠폰 가격 데이터
 
변경 후LayerCouponApplyOrderController.php
}
}
// 쿠폰명은 원문 저장되어 화면 출력 전 인코딩함
foreach (['order', 'delivery'] as $couponUseType) {
if (is_array($memberCouponArrData[$couponUseType])) {
foreach ($memberCouponArrData[$couponUseType] as &$memberCoupon) {
$memberCoupon['couponNm'] = gd_htmlspecialchars($memberCoupon['couponNm']);
}
unset($memberCoupon);
}
}
$this->setData('memberCouponArrData', $memberCouponArrData);
$this->setData('convertMemberCouponArrData', $convertMemberCouponArrData);
$this->setData('convertMemberCouponPriceArrData', $convertMemberCouponPriceArrData);
}
}
}
if (is_array($goodsCouponData['goodsCouponArrData'])) {
foreach ($goodsCouponData['goodsCouponArrData'] as &$goodsCouponList) {
foreach ($goodsCouponList as &$goodsCoupon) {
$goodsCoupon['couponNm'] = gd_htmlspecialchars($goodsCoupon['couponNm']);
}
unset($goodsCoupon);
}
unset($goodsCouponList);
}
$this->setData('goodsCouponArrData', $goodsCouponData['goodsCouponArrData']); // 쿠폰 DB 데이터
$this->setData('convertGoodsCouponArrData', $goodsCouponData['convertGoodsCouponArrData']); // 변환 쿠폰 데이터
$this->setData('convertGoodsCouponPriceArrData', $goodsCouponData['convertGoodsCouponPriceArrData']); // 쿠폰 가격 데이터
 
Controller/Mobile/Goods/LayerCouponDownController.php+25-6
변경 전LayerCouponDownController.php
use App;
use Framework\Debug\Exception\WarningException;
 
 
use Session;
use Request;
use Exception;
$coupon = App::load(\Component\Coupon\Coupon::class);
$post = Request::request()->toArray();
// 상품의 쿠폰리스트
$goodsCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'],Session::get('member.memNo'),Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
// 해당 상품의 모든 쿠폰을 보기용으로 변환
$convertCouponArrData = $coupon->convertCouponArrData($goodsCouponArrData);
// 상품의 다운받을 수 있는 쿠폰의 개수
$goodsCouponCnt = $coupon->getGoodsCouponDownListCount($goodsCouponArrData);
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->setData('goodsCouponArrData', $goodsCouponArrData);
 
$this->setData('convertCouponArrData', $convertCouponArrData);
$this->setData('goodsNo', $post['goodsNo']);
$this->setData('scmNo', $post['scmNo']);
$this->setData('brandCd', $post['brandCd']);
 
$this->setData('goodsCouponCnt', $goodsCouponCnt);
 
 
} else {
$this->json([
'error' => 10,
 
변경 후LayerCouponDownController.php
use App;
use Framework\Debug\Exception\WarningException;
use Framework\Security\Token;
use Origin\Service\Coupon\UpdateCouponService;
use Session;
use Request;
use Exception;
$coupon = App::load(\Component\Coupon\Coupon::class);
$post = Request::request()->toArray();
$supportsCouponUseTypeFilter = method_exists($coupon, 'setCouponUseTypes');
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product', 'order']);
}
$downCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'],Session::get('member.memNo'),Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product']);
}
/** @var UpdateCouponService $updateCouponService */
$updateCouponService = \App::getInstance(UpdateCouponService::class);
$updateCouponService->convertOrderBenefitsToDisplay($downCouponArrData);
$convertCouponArrData = $coupon->convertCouponArrData($downCouponArrData);
$orderCouponArrData = array_filter($downCouponArrData, function ($downCoupon) {
return ($downCoupon['couponUseType'] ?? '') === 'order';
});
$goodsCouponArrData = array_filter($downCouponArrData, function ($downCoupon) {
return ($downCoupon['couponUseType'] ?? '') === 'product';
});
$this->setData('goodsCouponArrData', $goodsCouponArrData);
$this->setData('orderCouponArrData', $orderCouponArrData);
$this->setData('convertCouponArrData', $convertCouponArrData);
$this->setData('goodsNo', $post['goodsNo']);
$this->setData('scmNo', $post['scmNo']);
$this->setData('brandCd', $post['brandCd']);
$goodsCouponCnt = empty($downCouponArrData) ? 0 : $coupon->getGoodsCouponDownListCount($downCouponArrData);
$this->setData('goodsCouponCnt', $goodsCouponCnt);
// 발급 검증용 쿠폰 전용 토큰
$this->setData('couponToken', Token::generate('couponToken'));
} else {
$this->json([
'error' => 10,
 
Controller/Mobile/Goods/LayerCouponPsController.php+16-1
변경 전LayerCouponPsController.php
namespace Bundle\Controller\Mobile\Goods;
use App;
 
use Session;
use Request;
use Exception;
// 쿠폰 다운
case 'coupon_down':
case 'coupon_down_all':
 
 
 
 
 
 
// --- 쿠폰 class
$coupon = \App::load('\\Component\\Coupon\\Coupon');
$coupon->setGoodsCouponMemberSave(Request::post()->get('couponNo'), Request::post()->get('goodsNo'),Session::get('member.memNo'),Session::get('member.groupSno'), $post['scmNo'], $post['brandCd']);
// 상품의 다운받을 수 있는 쿠폰의 개수
$goodsCouponCnt = $coupon->getGoodsCouponDownListCount(null, $post['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'));
 
 
 
 
 
 
 
 
$this->json([
'code' => 200,
 
변경 후LayerCouponPsController.php
namespace Bundle\Controller\Mobile\Goods;
use App;
use Framework\Security\Token;
use Session;
use Request;
use Exception;
// 쿠폰 다운
case 'coupon_down':
case 'coupon_down_all':
if (Request::getMethod() !== 'POST') {
throw new Exception(__('잘못된 요청입니다.'));
}
// 쿠폰 전용 토큰 판별 - 미전송·위조 시 로그 수집
$couponToken = Request::post()->get('couponToken');
Token::check('couponToken', ['couponToken' => $couponToken], false, null, true);
// --- 쿠폰 class
$coupon = \App::load('\\Component\\Coupon\\Coupon');
$coupon->setGoodsCouponMemberSave(Request::post()->get('couponNo'), Request::post()->get('goodsNo'),Session::get('member.memNo'),Session::get('member.groupSno'), $post['scmNo'], $post['brandCd']);
// 상품의 다운받을 수 있는 쿠폰의 개수
$supportsCouponUseTypeFilter = method_exists($coupon, 'setCouponUseTypes');
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product', 'order']);
}
$downCouponArrData = $coupon->getGoodsCouponDownList($post['goodsNo'], Session::get('member.memNo'), Session::get('member.groupSno'), null, null, $post['scmNo'], $post['brandCd']);
if ($supportsCouponUseTypeFilter) {
$coupon->setCouponUseTypes(['product']);
}
$goodsCouponCnt = empty($downCouponArrData) ? 0 : $coupon->getGoodsCouponDownListCount($downCouponArrData);
$this->json([
'code' => 200,
 
Controller/Mobile/Mypage/CouponPsController.php+7
변경 전CouponPsController.php
use Exception;
use Framework\Debug\Exception\AlertOnlyException;
use Framework\Debug\Exception\AlertReloadException;
 
use Request;
use Session;
 
case 'couponDown' :
case 'couponDownAll' :
try {
 
 
 
 
 
 
$result = $coupon->setCouponzoneDown(Request::post()->get('couponNo'), Session::get('member.memNo'), Session::get('member.groupSno'));
 
if ($result['success'] == 0) {
 
변경 후CouponPsController.php
use Exception;
use Framework\Debug\Exception\AlertOnlyException;
use Framework\Debug\Exception\AlertReloadException;
use Framework\Security\Token;
use Request;
use Session;
 
case 'couponDown' :
case 'couponDownAll' :
try {
if (Request::getMethod() !== 'POST' || !Request::isAjax()) {
throw new Exception(__('잘못된 요청입니다.'));
}
// 쿠폰 전용 토큰 판별 - 미전송·위조 시 로그 수집
$couponToken = Request::post()->get('couponToken');
Token::check('couponToken', ['couponToken' => $couponToken], false, null, true);
$result = $coupon->setCouponzoneDown(Request::post()->get('couponNo'), Session::get('member.memNo'), Session::get('member.groupSno'));
 
if ($result['success'] == 0) {
 
Controller/Mobile/Order/LayerCouponApplyOrderController.php+18
변경 전LayerCouponApplyOrderController.php
}
}
 
 
 
 
 
 
 
 
 
$this->setData('memberCouponArrData', $memberCouponArrData);
$this->setData('convertMemberCouponArrData', $convertMemberCouponArrData);
$this->setData('convertMemberCouponPriceArrData', $convertMemberCouponPriceArrData);
}
}
}
 
 
 
 
 
 
 
 
 
$this->setData('goodsCouponArrData', $goodsCouponData['goodsCouponArrData']); // 쿠폰 DB 데이터
$this->setData('convertGoodsCouponArrData', $goodsCouponData['convertGoodsCouponArrData']); // 변환 쿠폰 데이터
$this->setData('convertGoodsCouponPriceArrData', $goodsCouponData['convertGoodsCouponPriceArrData']); // 쿠폰 가격 데이터
 
변경 후LayerCouponApplyOrderController.php
}
}
// 쿠폰명은 원문 저장되어 화면 출력 전 인코딩함
foreach (['order', 'delivery'] as $couponUseType) {
if (is_array($memberCouponArrData[$couponUseType])) {
foreach ($memberCouponArrData[$couponUseType] as &$memberCoupon) {
$memberCoupon['couponNm'] = gd_htmlspecialchars($memberCoupon['couponNm']);
}
unset($memberCoupon);
}
}
$this->setData('memberCouponArrData', $memberCouponArrData);
$this->setData('convertMemberCouponArrData', $convertMemberCouponArrData);
$this->setData('convertMemberCouponPriceArrData', $convertMemberCouponPriceArrData);
}
}
}
if (is_array($goodsCouponData['goodsCouponArrData'])) {
foreach ($goodsCouponData['goodsCouponArrData'] as &$goodsCouponList) {
foreach ($goodsCouponList as &$goodsCoupon) {
$goodsCoupon['couponNm'] = gd_htmlspecialchars($goodsCoupon['couponNm']);
}
unset($goodsCoupon);
}
unset($goodsCouponList);
}
$this->setData('goodsCouponArrData', $goodsCouponData['goodsCouponArrData']); // 쿠폰 DB 데이터
$this->setData('convertGoodsCouponArrData', $goodsCouponData['convertGoodsCouponArrData']); // 변환 쿠폰 데이터
$this->setData('convertGoodsCouponPriceArrData', $goodsCouponData['convertGoodsCouponPriceArrData']); // 쿠폰 가격 데이터
 
data/skin/front/moment/css/event/coupon.css+15
변경 전coupon.css
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후coupon.css
@charset "utf-8";
 
/* 쿠폰존 — 사용처 배지 */
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:22px;padding:0 5px;border-radius:2px;margin-bottom:6px;font-size:12px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .date{margin-top:14px;vertical-align:top;}
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.coupon_down_layer .coupon_section_tit{margin:18px 0 0; padding-bottom:6px; color:#222; font-size:15px; font-weight:700; border-bottom:1px solid #333;}
 
/* 쿠폰 다운받기 레이어 */
.coupon_down_layer .coupon_completed.coupon-grade-limit{padding:0; color:#838383; background:none;}
.coupon_down_layer .top_table_type table{table-layout:fixed;}
.coupon_down_layer .top_table_type td:first-child{word-break:keep-all; word-wrap:break-word;}
 
data/skin/front/moment/event/couponzone.html+9-2
변경 전couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<div class="content">
 
<div class="mypage_cont">
<div class="coupon_content">
<h2>쿠폰존</h2>
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="{=__('쿠폰이미지')}" /></div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<div class="content">
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="mypage_cont">
<div class="coupon_content">
<h2>쿠폰존</h2>
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="{=__('쿠폰이미지')}" /></div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/front/moment/goods/layer_coupon_down.html+62-4
변경 전layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
 
<div class="layer_wrap_cont">
<div class="ly_tit">
<h4>
<div class="ly_cont">
<div class="scroll_box">
<!--{ ? gd_is_login() }-->
 
 
<div class="top_table_type">
<table>
<colgroup>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{ : }-->
<div class="coupon_down_txt"><p>{=__('로그인하셔야 본 서비스를 이용하실 수 있습니다.')}</p></div>
<!--{ / }-->
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
});
//-->
</script>
<!--{ / }-->
 
변경 후layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="layer_wrap_cont">
<div class="ly_tit">
<h4>
<div class="ly_cont">
<div class="scroll_box">
<!--{ ? gd_is_login() }-->
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<div class="top_table_type">
<table>
<colgroup>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<div class="top_table_type">
<table>
<colgroup>
<col style="width:150px" />
<col style="width:300px" />
<col>
</colgroup>
<thead>
<tr>
<th>{=__('쿠폰')}</th>
<th>{=__('혜택')}</th>
<th>{=__('다운받기')}</th>
</tr>
</thead>
<tbody>
<!--{ @ orderCouponArrData }-->
<tr>
<td>
<strong>{.couponNm}</strong>
</td>
<td class="td_left">
<strong class="coupon_price"><b>{convertCouponArrData[.key_].couponBenefit}</b> { convertCouponArrData[.key_].couponKindType }</strong>
<span class="text_info">
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
- {convertCouponArrData[.key_].couponMaxBenefit}<br />
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
- {convertCouponArrData[.key_].couponMinOrderPrice}<br />
<!--{ / }-->
- {convertCouponArrData[.key_].couponApplyDuplicateType}
</span>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
<div class="btn_down_box">
<span class="btn_gray_list btn_coupon_down" data-coupon-no="{.couponNo}"><a href="#" class="btn_gray_small"><em class="icon_download">{=__('쿠폰 다운받기')}</em></a></span>
<em class="text_info">({convertCouponArrData[.key_].useEndDate})</em>
</div>
<!--{ / }-->
</td>
</tr>
<!--{ / }-->
</tbody>
</table>
</div>
<!--{ / }-->
<!--{ : }-->
<div class="coupon_down_txt"><p>{=__('로그인하셔야 본 서비스를 이용하실 수 있습니다.')}</p></div>
<!--{ / }-->
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
});
//-->
</script>
<!--{ / }-->
 
data/skin/front/mygreek/css/event/coupon.css+19
변경 전coupon.css
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후coupon.css
@charset "utf-8";
 
/* 쿠폰존 — 사용처 배지 */
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:22px;padding:0 5px;border-radius:2px;margin-bottom:6px;font-size:12px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .date{margin-top:14px;vertical-align:top;}
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.coupon_down_layer .coupon_section_tit{margin:18px 0 0; padding-bottom:6px; color:#222; font-size:15px; font-weight:700; border-bottom:1px solid #333;}
 
/* 쿠폰 다운받기 레이어 */
.coupon_down_layer .coupon_completed:not(.coupon-grade-limit){display:inline-block; min-height:17px; padding-left:23px; line-height:17px; background:url('../../img/common/layer/icon_completed.png') no-repeat left center;}
.coupon_down_layer .coupon_completed.coupon-grade-limit{color:#838383;}
.coupon_down_layer .top_table_type table{table-layout:fixed;}
.coupon_down_layer .top_table_type td:first-child{word-break:keep-all; word-wrap:break-word;}
/* 쿠폰존 */
.coupon_content .cp_list_wrap .cp_list{width:920px;}
.coupon_content .cp_list_wrap .cp_list li{margin-right:10px;}
 
data/skin/front/mygreek/event/couponzone.html+9-2
변경 전couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<div class="content">
 
<div class="mypage_cont">
<div class="coupon_content">
<h2>쿠폰존</h2>
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="{=__('쿠폰이미지')}" /></div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<div class="content">
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="mypage_cont">
<div class="coupon_content">
<h2>쿠폰존</h2>
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="{=__('쿠폰이미지')}" /></div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/front/mygreek/goods/layer_coupon_down.html+61-3
변경 전layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
 
<div class="layer_wrap_cont">
<div class="ly_tit">
<h4>
<div class="ly_cont">
<div class="scroll_box">
<!--{ ? gd_is_login() }-->
 
 
<div class="top_table_type">
<table>
<colgroup>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{ : }-->
<div class="coupon_down_txt"><p>{=__('로그인하셔야 본 서비스를 이용하실 수 있습니다.')}</p></div>
<!--{ / }-->
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
 
변경 후layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="layer_wrap_cont">
<div class="ly_tit">
<h4>
<div class="ly_cont">
<div class="scroll_box">
<!--{ ? gd_is_login() }-->
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<div class="top_table_type">
<table>
<colgroup>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<div class="top_table_type">
<table>
<colgroup>
<col style="width:150px" />
<col style="width:300px" />
<col>
</colgroup>
<thead>
<tr>
<th>{=__('쿠폰')}</th>
<th>{=__('혜택')}</th>
<th>{=__('다운받기')}</th>
</tr>
</thead>
<tbody>
<!--{ @ orderCouponArrData }-->
<tr>
<td>
<strong>{.couponNm}</strong>
</td>
<td class="td_left">
<strong class="coupon_price"><b>{convertCouponArrData[.key_].couponBenefit}</b> { convertCouponArrData[.key_].couponKindType }</strong>
<span class="text_info">
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
- {convertCouponArrData[.key_].couponMaxBenefit}<br />
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
- {convertCouponArrData[.key_].couponMinOrderPrice}<br />
<!--{ / }-->
- {convertCouponArrData[.key_].couponApplyDuplicateType}
</span>
</td>
<td>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="coupon_completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="coupon_completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
<div class="btn_down_box">
<span class="btn_gray_list btn_coupon_down" data-coupon-no="{.couponNo}"><a href="#" class="btn_gray_small"><em class="icon_download">{=__('쿠폰 다운받기')}</em></a></span>
<em class="text_info">({convertCouponArrData[.key_].useEndDate})</em>
</div>
<!--{ / }-->
</td>
</tr>
<!--{ / }-->
</tbody>
</table>
</div>
<!--{ / }-->
<!--{ : }-->
<div class="coupon_down_txt"><p>{=__('로그인하셔야 본 서비스를 이용하실 수 있습니다.')}</p></div>
<!--{ / }-->
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/front/story_g/css/gd_contents.css+12
변경 전gd_contents.css
/*** 회원가입 사업자 정보 | member/_join_view_business.html ***/
.upload_btn_box {position: relative; display: inline-block;}
.upload_btn_box .file {position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;}
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후gd_contents.css
/*** 회원가입 사업자 정보 | member/_join_view_business.html ***/
.upload_btn_box {position: relative; display: inline-block;}
.upload_btn_box .file {position: absolute; top: 0; left: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer;}
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.coupon-down-layer .coupon_section_tit{margin:16px 0 0;padding-bottom:6px;color:#222;font-size:15px;font-weight:700;border-bottom:1px solid #333;text-align:left;}
/* 쿠폰존 — 사용처 배지 */
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:22px;padding:0 5px;border-radius:2px;margin-bottom:8px;font-size:12px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.coupon_content .cp_list_wrap .cp_list li .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.coupon_content .cp_list_wrap .cp_list li .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
.coupon_content .cp_list_wrap .cp_list li .box_coupon .text_wrap .text_info .date{margin-top:14px;vertical-align:top;}
/* 쿠폰 다운받기 레이어 */
.coupon-down-layer .view > p .goods-coupon-cnt{color:#fa2828;}
.coupon-down-layer .view .table1 .guide .coupon-benefit{color:#fa2828;}
.coupon-down-layer .table1 > table tbody td .completed.coupon-grade-limit{padding:0; background:none; color:#838383;}
 
data/skin/front/story_g/event/couponzone.html+8-2
변경 전couponzone.html
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="쿠폰이미지" /></div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
<div class="img_cp"><img src="{=couponConfig.pcCouponImagePath}" alt="쿠폰이미지" /></div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="dis">{..couponBenefit}</span>
<span class="txt">{..couponKindType}</span>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/front/story_g/goods/layer_coupon_down.html+63-4
변경 전layer_coupon_down.html
<p>{=__('다운로드 가능한 총')} <strong class="goods-coupon-cnt"><!--{ goodsCouponCnt }-->{=__('개%s의 쿠폰이 있습니다.', '</strong>')}</p>
 
<div class="scroll-box">
 
 
<div class="table1">
<table>
<colgroup>
<!--<span class="coupon-icon">55,000<b>원</b></span>-->
</td>
<td class="guide">
<strong>{ convertCouponArrData[.key_].couponBenefit } { convertCouponArrData[.key_].couponKindType }</strong>
<div class="msg">
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
<span>- { convertCouponArrData[.key_].couponMaxBenefit }</span>
</td>
<td class="ta-c">
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="completed">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
<div class="btn">
<button class="skinbtn point1 layer-close btn-close"><em>{=__('취소')}</em></button>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
 
변경 후layer_coupon_down.html
<p>{=__('다운로드 가능한 총')} <strong class="goods-coupon-cnt"><!--{ goodsCouponCnt }-->{=__('개%s의 쿠폰이 있습니다.', '</strong>')}</p>
 
<div class="scroll-box">
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<div class="table1">
<table>
<colgroup>
<!--<span class="coupon-icon">55,000<b>원</b></span>-->
</td>
<td class="guide">
<strong><span class="coupon-benefit">{ convertCouponArrData[.key_].couponBenefit }</span> { convertCouponArrData[.key_].couponKindType }</strong>
<div class="msg">
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
<span>- { convertCouponArrData[.key_].couponMaxBenefit }</span>
</td>
<td class="ta-c">
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
</tbody>
</table>
</div>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<div class="table1">
<table>
<colgroup>
<col style="width:151px"/>
<col/>
<col/>
</colgroup>
<thead>
<tr>
<th>{=__('쿠폰')}</th>
<th>{=__('혜택')}</th>
<th>{=__('다운받기')}</th>
</tr>
</thead>
<tbody>
<!--{ @ orderCouponArrData }-->
<tr>
<td class="ta-c">
<strong>{ .couponNm }</strong>
<!--<span class="coupon-icon">55,000<b>원</b></span>-->
</td>
<td class="guide">
<strong><span class="coupon-benefit">{ convertCouponArrData[.key_].couponBenefit }</span> { convertCouponArrData[.key_].couponKindType }</strong>
<div class="msg">
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
<span>- { convertCouponArrData[.key_].couponMaxBenefit }</span>
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<span>- { convertCouponArrData[.key_].couponMinOrderPrice }</span>
<!--{ / }-->
<span>- <!--{ convertCouponArrData[.key_].couponApplyDuplicateType }--></span>
</div>
</td>
<td class="ta-c">
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<strong class="completed coupon-grade-limit">{=__('해당 회원등급이 아닙니다.')}</strong>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<strong class="completed">{=__('발급완료')}</strong>
<!--{ : .chkMemberCoupon == 'YES' }-->
<button class="normal-btn small1 btn-coupon-down" data-coupon-no="{ .couponNo }">
<em>{=__('쿠폰 다운받기')}<img class="down" src="../img/etc/down.png" alt="{=__('쿠폰 다운받기')}"/></em></button>
<div class="msg">
<span>(<!--{ convertCouponArrData[.key_].useEndDate }-->)</span>
</div>
<!--{ / }-->
</td>
</tr>
<!--{ / }-->
</tbody>
</table>
</div>
<!--{ / }-->
</div>
<div class="btn">
<button class="skinbtn point1 layer-close btn-close"><em>{=__('취소')}</em></button>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/moment/css/event/coupon.css+16
변경 전coupon.css
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후coupon.css
@charset "utf-8";
 
/* 쿠폰존 — 사용처 배지 */
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:20px;padding:0 4px;border-radius:2px;margin-bottom:3px;font-size:11px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.layer_coupon_down .coupon_down_list .coupon_section_tit {margin:14px 0 0; padding-bottom:5px; color:#222; font-size:14px; font-weight:700; border-bottom:1px solid #333;}
 
/* 쿠폰 다운받기 레이어 */
.layer_coupon_down .ly_content .coupondown .my_info ul li:nth-child(1) strong span{color:var(--primary-default);}
.layer_coupon_down .ly_content .coupon_down_list ul li .coupon_left dl dt strong{color:var(--primary-default);}
.layer_coupon_down .coupon_down_list .coupon_left dd .c_red{color:inherit;}
/* 쿠폰존 */
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .tit{height:auto; max-height:38px;}
 
data/skin/mobile/moment/css/event/event.css+1-1
변경 전event.css
.coupon_content .cp_list_wrap .btn_all{padding:0 15px;margin-top:7px;}
.coupon_content .cp_list_none{display:none; text-align:center;padding:65px 0 100px;}
.coupon_content .cp_list_none p{padding-top:102px;font-weight:700;font-size:15px;letter-spacing:-0.05em;color:#222;background:url('../../img/icon/bg_notice.png') no-repeat 50% 0;background-size:80px 80px;}
/* //쿠폰존 페이지 */
 
변경 후event.css
.coupon_content .cp_list_wrap .btn_all{padding:0 15px;margin-top:7px;}
.coupon_content .cp_list_none{display:none; text-align:center;padding:65px 0 100px;}
.coupon_content .cp_list_none p{padding-top:102px;font-weight:700;font-size:15px;letter-spacing:-0.05em;color:#222;background:url('../../img/icon/bg_notice.png') no-repeat 50% 0;background-size:80px 80px;}
/* //쿠폰존 페이지 */
 
data/skin/mobile/moment/event/couponzone.html+9-2
변경 전couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
 
<div class="coupon">
<!-- 20190522 추가 -->
<!-- 쿠폰존 -->
</div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="coupon">
<!-- 20190522 추가 -->
<!-- 쿠폰존 -->
</div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/moment/goods/layer_coupon_down.html+45-3
변경 전layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
 
<div class="layer_coupon_down">
<div class="ly_head">
<h2 class="h_tit">{=__('쿠폰 다운받기')}</h2>
</ul>
</div>
<div class="coupon_down_list">
 
 
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<!--{ convertCouponArrData[.key_].couponApplyDuplicateType }-->
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
</li>
<!--{ / }-->
</ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
</div>
</div>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
 
변경 후layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="layer_coupon_down">
<div class="ly_head">
<h2 class="h_tit">{=__('쿠폰 다운받기')}</h2>
</ul>
</div>
<div class="coupon_down_list">
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<dd><!--{ convertCouponArrData[.key_].couponApplyDuplicateType }--></dd>
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<ul>
<!--{ @ orderCouponArrData }-->
<li>
<div class="coupon_left">
<dl>
<dt>{ .couponNm } <strong>{ convertCouponArrData[.key_].couponBenefit }</strong> { convertCouponArrData[.key_].couponKindType }</dt>
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
<dd>{ convertCouponArrData[.key_].couponMaxBenefit }</dd>
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<dd><!--{ convertCouponArrData[.key_].couponApplyDuplicateType }--></dd>
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<dd>{=__('해당 회원등급이 아닙니다.')}</dd>
<!--{ / }-->
</dl>
</div>
<div class="coupon_right">
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<span class="coupon_download_no_btn">{=__('발급불가')}</span>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<span class="coupon_download_ok_btn">{=__('발급완료')}</span>
<!--{ : .chkMemberCoupon == 'YES' }-->
<span><a class="coupon_download_btn btn_coupon_down" data-coupon-no="{ .couponNo }">{=__('다운로드')}</a></span>
<!--{ / }-->
</div>
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
</div>
</div>
</div>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/mygreek/css/event/coupon.css+18
변경 전coupon.css
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후coupon.css
@charset "utf-8";
 
/* 쿠폰존 — 사용처 배지 */
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:20px;padding:0 4px;border-radius:2px;margin-bottom:3px;font-size:11px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.layer_coupon_down .coupon_down_list .coupon_section_tit {margin:14px 0 0; padding-bottom:5px; color:#222; font-size:14px; font-weight:700; border-bottom:1px solid #333;}
 
/* 쿠폰 다운받기 레이어 */
.layer_coupon_down .ly_content .coupondown .my_info ul li{vertical-align:middle;}
.layer_coupon_down .ly_content .coupondown .my_info ul li:nth-child(2){padding-bottom:0;}
.layer_coupon_down .ly_content .coupondown .my_info ul li:nth-child(1) strong span{color:var(--primary-default);}
.layer_coupon_down .ly_content .coupon_down_list ul li .coupon_left dl dt strong{color:var(--primary-default);}
.layer_coupon_down .ly_content .coupon_down_list ul li .coupon_left dl dd .chk_img{vertical-align:middle;}
/* 쿠폰존 */
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .tit{height:auto; max-height:38px;}
 
data/skin/mobile/mygreek/event/couponzone.html+9-2
변경 전couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
 
<div class="coupon">
<!-- 20190522 추가 -->
<!-- 쿠폰존 -->
</div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
{*** 쿠폰존 | event/couponzone.php ***}
{ # header }
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="coupon">
<!-- 20190522 추가 -->
<!-- 쿠폰존 -->
</div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/mygreek/goods/layer_coupon_down.html+45-3
변경 전layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
 
<div class="layer_coupon_down">
<div class="ly_head">
<h2 class="h_tit">{=__('쿠폰 다운받기')}</h2>
</ul>
</div>
<div class="coupon_down_list">
 
 
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<!--{ convertCouponArrData[.key_].couponApplyDuplicateType }-->
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
</li>
<!--{ / }-->
</ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
</div>
</div>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
 
변경 후layer_coupon_down.html
{*** 쿠폰 다운받기 레이어 | goods/layer_coupon_down.php ***}
<link type="text/css" rel="stylesheet" href="{=setBrowserCache('../css/event/coupon.css')}" />
<div class="layer_coupon_down">
<div class="ly_head">
<h2 class="h_tit">{=__('쿠폰 다운받기')}</h2>
</ul>
</div>
<div class="coupon_down_list">
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<dd><!--{ convertCouponArrData[.key_].couponApplyDuplicateType }--></dd>
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<ul>
<!--{ @ orderCouponArrData }-->
<li>
<div class="coupon_left">
<dl>
<dt>{ .couponNm } <strong>{ convertCouponArrData[.key_].couponBenefit }</strong> { convertCouponArrData[.key_].couponKindType }</dt>
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
<dd>{ convertCouponArrData[.key_].couponMaxBenefit }</dd>
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
<dd>{ convertCouponArrData[.key_].couponMinOrderPrice }</dd>
<!--{ / }-->
<dd><!--{ convertCouponArrData[.key_].couponApplyDuplicateType }--></dd>
<!--{ ? .chkMemberCoupon == 'YES' }-->
<dd><img src="../img/icon/icon_chk_red.png" alt="{=__('체크')}" class="chk_img"><span class="c_red"><!--{ convertCouponArrData[.key_].useEndDate }--></span></dd>
<!--{ / }-->
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<dd>{=__('해당 회원등급이 아닙니다.')}</dd>
<!--{ / }-->
</dl>
</div>
<div class="coupon_right">
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<span class="coupon_download_no_btn">{=__('발급불가')}</span>
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<span class="coupon_download_ok_btn">{=__('발급완료')}</span>
<!--{ : .chkMemberCoupon == 'YES' }-->
<span><a class="coupon_download_btn btn_coupon_down" data-coupon-no="{ .couponNo }">{=__('다운로드')}</a></span>
<!--{ / }-->
</div>
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
</div>
</div>
</div>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/story_g/css/gd_contents.css+14
변경 전gd_contents.css
label.check_s[for="certificationDeleteSno"],
label.check_s[for^="comAddiCertDeleteSno"] { min-height: 23px; line-height: 23px;}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후gd_contents.css
label.check_s[for="certificationDeleteSno"],
label.check_s[for^="comAddiCertDeleteSno"] { min-height: 23px; line-height: 23px;}
 
 
/* 쿠폰 다운받기 레이어 — 「상품쿠폰」/「주문쿠폰」 섹션 제목 */
.coupondown .cp_bx .coupon_section_tit{margin:0 12px;padding:14px 0 7px;color:#222;font-size:14px;font-weight:700;background:#fff;border-bottom:1px solid #333;}
/* 쿠폰존 — 사용처 배지 */
.cp_list_wrap .cp_list li .text_info .cp_use_badge{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;height:20px;padding:0 4px;border-radius:2px;margin-bottom:3px;font-size:11px;font-weight:700;line-height:1;letter-spacing:-0.02em;}
.cp_list_wrap .cp_list li .text_info .cp_use_badge.order{background:#555d87;color:#fff;}
.cp_list_wrap .cp_list li .text_info .cp_use_badge.prod{background:#eef2f6;color:#555;border:1px solid #d4dce4;}
/* 쿠폰 다운받기 레이어 */
.coupondown .cp_bx label .tit .c-red{color:#999999 !important;}
.coupondown .cp_bx label .sale:not(.disabled):not(.complete){background:var(--primary-default);}
.coupondown .cp_bx label .sale.complete{border-color:var(--primary-default); color:var(--primary-default);}
.coupondown .cp_bx li{margin:0 12px;}
/* 쿠폰존 */
.coupon_content .cp_list_wrap .cp_list li .text_wrap .text_info .tit{height:auto; max-height:38px;}
 
data/skin/mobile/story_g/event/couponzone.html+8-2
변경 전couponzone.html
</div>
<div class="text_wrap">
<div class="text_info">
 
 
 
 
 
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo,
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll'
 
};
$.ajax({
method: "POST",
 
변경 후couponzone.html
</div>
<div class="text_wrap">
<div class="text_info">
<!--{ ? ..couponUseTypeCode == 'order' }-->
<span class="cp_use_badge order">{=__('주문 적용')}</span>
<!--{ : ..couponUseTypeCode == 'product' }-->
<span class="cp_use_badge prod">{=__('상품 적용')}</span>
<!--{ / }-->
<span class="tit">{..couponNm}</span>
<span class="date">{..useEndDate}</span>
</div>
method: "POST",
cache: false,
url: "../mypage/coupon_ps.php",
data: "mode=couponDown&couponNo=" + couponNo + "&couponToken=" + encodeURIComponent('{=couponToken}'),
dataType: 'json',
async: false,
success: function (data) {
var params = {
total: total,
couponNo: couponArrData,
mode: 'couponDownAll',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/story_g/goods/layer_coupon_down.html+55-3
변경 전layer_coupon_down.html
<a href="#" class="bn_logout" id="btnAllCouponDown">{=__('전체다운로드')}</a>
</div>
<div class="cp_bx">
 
 
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ convertCouponArrData[.key_].couponApplyDuplicateType }-->
 
<!--{ ? .chkMemberCoupon == 'YES' }-->
<br/> <img src="../img/icon/icon-check-red.png" alt="" class="icon-check-red"><span class="c-red"> <!--{ convertCouponArrData[.key_].useEndDate }--></span>
<!--{ / }-->
 
 
</li>
<!--{ / }-->
</ul>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
</div>
</div>
<button type="button" class="bn_cls v2 ly_btn_close" ><span class="sp">{=__('닫기')}</span></button>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}'
 
};
$.ajax({
method: "POST",
 
변경 후layer_coupon_down.html
<a href="#" class="bn_logout" id="btnAllCouponDown">{=__('전체다운로드')}</a>
</div>
<div class="cp_bx">
<!--{ ? goodsCouponArrData }-->
<p class="coupon_section_tit">{=__('상품쿠폰')}</p>
<ul>
<!--{ @ goodsCouponArrData }-->
<li>
<!--{ convertCouponArrData[.key_].couponApplyDuplicateType }-->
 
<!--{ ? .chkMemberCoupon == 'YES' }-->
<br/> <img src="../img/icon/icon-check-black.png" alt="" class="icon-check-red"><span class="c-red"> <!--{ convertCouponArrData[.key_].useEndDate }--></span>
<!--{ / }-->
 
 
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
<!--{ ? orderCouponArrData }-->
<p class="coupon_section_tit">{=__('주문쿠폰')}</p>
<ul>
<!--{ @ orderCouponArrData }-->
<li>
<label for="cp1">
<span class="tit">
 
{ .couponNm }
<strong>{ convertCouponArrData[.key_].couponBenefit }</strong> { convertCouponArrData[.key_].couponKindType }<br/>
<span>
<!--{ ? convertCouponArrData[.key_].couponMaxBenefit }-->
{ convertCouponArrData[.key_].couponMaxBenefit }<br/>
<!--{ / }-->
<!--{ ? convertCouponArrData[.key_].couponMinOrderPrice }-->
{ convertCouponArrData[.key_].couponMinOrderPrice }<br/>
<!--{ / }-->
<!--{ convertCouponArrData[.key_].couponApplyDuplicateType }-->
 
<!--{ ? .chkMemberCoupon == 'YES' }-->
<br/> <img src="../img/icon/icon-check-black.png" alt="" class="icon-check-red"><span class="c-red"> <!--{ convertCouponArrData[.key_].useEndDate }--></span>
<!--{ / }-->
 
 
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<br/> {=__('해당 회원등급이 아닙니다.')}
<!--{ / }-->
 
</span>
</span>
<!--{ ? .chkMemberCoupon == 'NO_MEMBER_GROUP' }-->
<span class="sale disabled">
{=__('발급불가')}
<!--{ : .chkMemberCoupon == 'DUPLICATE_COUPON' }-->
<span class="sale complete">
{=__('발급완료')}
<!--{ : .chkMemberCoupon == 'YES' }-->
<span class="sale ">
<a class="btn-coupon-down" data-coupon-no="{ .couponNo }"><em>{=__('다운로드')}</em></a>
<!--{ / }-->
 
</span>
</label>
</li>
<!--{ / }-->
</ul>
<!--{ / }-->
</div>
</div>
<button type="button" class="bn_cls v2 ly_btn_close" ><span class="sp">{=__('닫기')}</span></button>
mode: 'coupon_down',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
mode: 'coupon_down_all',
goodsNo: '{goodsNo}',
scmNo: '{scmNo}',
brandCd: '{brandCd}',
couponToken: '{=couponToken}'
};
$.ajax({
method: "POST",
 
data/skin/mobile/story_g/img/icon/icon-check-black.png
변경 전icon-check-black.png
Binary files /dev/null and b/user/data/skin/mobile/story_g/img/icon/icon-check-black.png differ
 
변경 후icon-check-black.png
Binary files /dev/null and b/user/data/skin/mobile/story_g/img/icon/icon-check-black.png differ
 
14세 이상 연령 동의 항목 추가
Admin/member/_member_view.php+4
변경 전_member_view.php
</div></td>
</tr>
<?php endif; ?>
 
 
 
 
<!-- 이메일 -->
<tr>
<th><div data-tooltip-seq="011">이메일</div></th>
 
변경 후_member_view.php
</div></td>
</tr>
<?php endif; ?>
<tr>
<th><div data-tooltip-seq="041">만 14세 이상 가입동의</div></th>
<td><div><?= $data['under14ConsentText'] ?? '-' ?></div></td>
</tr>
<!-- 이메일 -->
<tr>
<th><div data-tooltip-seq="011">이메일</div></th>
 
Component/Member/Util/MemberUtil.php+21
변경 전MemberUtil.php
return $items;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 회원 약관 동의 표시용 행 데이터 생성. 약관 행 설정 배열을 받아 뷰에서 그대로 출력할 표시용 배열을 반환한다 (HTML 미생성 — 마크업은 뷰가 담당).
* 약관이 늘어도 호출부에서 배열 항목만 추가하면 되고 메서드 시그니처는 불변.
 
변경 후MemberUtil.php
return $items;
}
 
/**
* 회원정보 '만 14세 이상 가입동의' 표시값. 현재 가입연령제한·동의항목 사용 설정과 무관하게 회원 동의값으로 노출하고, 승인제 상점은 승인일·비승인제는 가입일을 동의일로 본다.
*
* @param array $memberData 회원 데이터 (under14ConsentFl 'y'/'n', approvalDt 가입승인일, entryDt 가입일)
* @param array $joinPolicy 회원가입 정책 (appUseFl 가입승인 'y'/'n'/'company')
* @return string '동의 {일시}' / '동의'(일시 없음) / '-'(미노출)
*/
public static function buildUnder14ConsentText(array $memberData, array $joinPolicy): string
{
if (($memberData['under14ConsentFl'] ?? 'n') !== 'y') {
return '-';
}
$useApprovalDt = in_array($joinPolicy['appUseFl'] ?? 'n', ['y', 'company'], true);
$consentDt = $useApprovalDt ? ($memberData['approvalDt'] ?? '') : ($memberData['entryDt'] ?? '');
if (empty($consentDt) || $consentDt === '0000-00-00 00:00:00') {
return '동의';
}
 
return '동의 ' . $consentDt;
}
 
/**
* 회원 약관 동의 표시용 행 데이터 생성. 약관 행 설정 배열을 받아 뷰에서 그대로 출력할 표시용 배열을 반환한다 (HTML 미생성 — 마크업은 뷰가 담당).
* 약관이 늘어도 호출부에서 배열 항목만 추가하면 되고 메서드 시그니처는 불변.
 
Controller/Admin/Member/MemberModifyController.php+1
변경 전MemberModifyController.php
$memberData = gd_array_merge($memberData, $historyService->getLastReceiveAgreementByMember($memNo));
// 마케팅 목적 개인정보 수집 및 이용 동의/거부일 (없으면 '-')
$memberData['privateMktAgreementDt'] = $memberData['lastReceiveAgreementDt']['privateMkt'] ?? '-';
 
$memberHistory = $historyService->getMemberHistory($memNo, $request->get()->get('page', 1), $request->get()->get('pageNum', 10));
$memberData['recommendCnt'] = $memberService->getRecommendCount($memberData['memId']);
if ($memberData['rncheck'] == 'realname') {
 
변경 후MemberModifyController.php
$memberData = gd_array_merge($memberData, $historyService->getLastReceiveAgreementByMember($memNo));
// 마케팅 목적 개인정보 수집 및 이용 동의/거부일 (없으면 '-')
$memberData['privateMktAgreementDt'] = $memberData['lastReceiveAgreementDt']['privateMkt'] ?? '-';
$memberData['under14ConsentText'] = \Component\Member\Util\MemberUtil::buildUnder14ConsentText($memberData, gd_policy('member.join'));
$memberHistory = $historyService->getMemberHistory($memNo, $request->get()->get('page', 1), $request->get()->get('pageNum', 10));
$memberData['recommendCnt'] = $memberService->getRecommendCount($memberData['memId']);
if ($memberData['rncheck'] == 'realname') {
 
Controller/Admin/Member/MemberPsController.php+13-12
변경 전MemberPsController.php
throw new LayerException(__('재가입 불가 기간은 1~365일 사이에서 설정해야 합니다.'));
}
 
if ($policy->saveMemberJoin($request->post()->all())) {
if ($joinPolicyService->isRejoinEnabled($requestAllParams['rejoinFl']) === false) {
$this->json([
'messages' => [
__('탈퇴/재가입 설정 사용안함 시 탈퇴회원의 재가입이 허용됩니다.'),
__('저장이 완료되었습니다.')
]
]);
} else {
$this->json(__('저장이 완료되었습니다.'));
}
} else {
throw new Exception(__('처리중에 오류가 발생하여 실패되었습니다.'));
}
 
 
 
 
 
 
 
 
 
 
 
 
} catch (Exception $e) {
if ($request->isAjax()) {
throw $e;
 
변경 후MemberPsController.php
throw new LayerException(__('재가입 불가 기간은 1~365일 사이에서 설정해야 합니다.'));
}
 
if (!$policy->saveMemberJoin($request->post()->all())) {
 
 
 
 
 
 
 
 
 
 
 
throw new Exception(__('처리중에 오류가 발생하여 실패되었습니다.'));
}
$kakaoSyncSettingService = \App::getInstance(KakaoSyncSettingService::class);
$kakaoSyncSettingService->syncSettingWhenSaveMemberJoinPolicy();
if (!$joinPolicyService->isRejoinEnabled($requestAllParams['rejoinFl'])) {
$this->json([
'messages' => [
__('탈퇴/재가입 설정 사용안함 시 탈퇴회원의 재가입이 허용됩니다.'),
__('저장이 완료되었습니다.')
]
]);
} else {
$this->json(__('저장이 완료되었습니다.'));
}
} catch (Exception $e) {
if ($request->isAjax()) {
throw $e;
 
Controller/Admin/Member/MemberRegisterController.php+1
변경 전MemberRegisterController.php
$member = $vo->toArray();
$member['cellPhoneCountryCode'] = 'kr';
$member['phoneCountryCode'] = 'kr';
 
 
// 사업자 등록증 조회
$companyCertification = \App::getInstance(\Component\Member\Company\CompanyCertification::class);
 
변경 후MemberRegisterController.php
$member = $vo->toArray();
$member['cellPhoneCountryCode'] = 'kr';
$member['phoneCountryCode'] = 'kr';
$member['under14ConsentText'] = MemberUtil::buildUnder14ConsentText($member, gd_policy('member.join'));
 
// 사업자 등록증 조회
$companyCertification = \App::getInstance(\Component\Member\Company\CompanyCertification::class);
 
Controller/Admin/Share/MemberCrmDetailController.php+1
변경 전MemberCrmDetailController.php
];
// 마케팅 목적 개인정보 수집 및 이용 동의/거부일 (없으면 '-')
$memberData['privateMktAgreementDt'] = $memberData['lastReceiveAgreementDt']['privateMkt'] ?? '-';
 
 
// 하이픈 제거
$memberData['fax'] = str_replace("-", "", $memberData['fax']);
 
변경 후MemberCrmDetailController.php
];
// 마케팅 목적 개인정보 수집 및 이용 동의/거부일 (없으면 '-')
$memberData['privateMktAgreementDt'] = $memberData['lastReceiveAgreementDt']['privateMkt'] ?? '-';
$memberData['under14ConsentText'] = MemberUtil::buildUnder14ConsentText($memberData, gd_policy('member.join'));
 
// 하이픈 제거
$memberData['fax'] = str_replace("-", "", $memberData['fax']);
 
디자인에디터 페이지 SEO 태그 설정 기능
Component/DesignEditor/DesignEditorAssetRepositoryStub.php-132
변경 전DesignEditorAssetRepositoryStub.php
<?php
 
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Component\DesignEditor;
 
/**
* 디자인 에디터 에셋 저장소 Stub (테스트용)
*
* 단위 테스트를 위한 인메모리 구현체
* 실제 DB 스키마(es_designEditorAsset, es_designEditorFolder)와 동일한 필드 구조 사용
*
* @author NHN godo
*/
class DesignEditorAssetRepositoryStub implements DesignEditorAssetRepositoryInterface
{
/** @var array 임시 에셋 데이터 */
protected array $assets = [];
 
public function __construct()
{
$this->initializeStubData();
}
 
/**
* 임시 에셋 데이터 초기화
*/
protected function initializeStubData(): void
{
$this->assets = [
[
'assetSno' => 1,
'folderSno' => 1,
'skinSno' => 1, // JOIN 결과로 포함
'folderSlug' => 'custom', // JOIN 결과로 포함
'assetCode' => 'custom--test_page',
'assetTitle' => '테스트 페이지',
'assetType' => 'html',
'assetIcon' => null,
'assetContents' => json_encode([
'version' => '1.0.0',
'page' => [
'id' => 'custom-test-page',
'title' => '테스트 페이지',
'seo' => [
'title' => '테스트 페이지 SEO 타이틀',
'description' => '테스트 페이지 설명입니다.',
'keywords' => '테스트, 페이지',
],
'sections' => [],
],
]),
'assetPath' => 'custom/test_page.html',
'pageUrl' => 'custom/test_page.php',
'deleteFl' => 'n',
'regDt' => date('Y-m-d H:i:s'),
'modDt' => null,
],
[
'assetSno' => 2,
'folderSno' => 1,
'skinSno' => 1,
'folderSlug' => 'custom',
'assetCode' => 'custom--event_page',
'assetTitle' => '이벤트 페이지',
'assetType' => 'html',
'assetIcon' => null,
'assetContents' => null,
'assetPath' => 'custom/event_page.html',
'pageUrl' => 'custom/event_page.php',
'deleteFl' => 'n',
'regDt' => date('Y-m-d H:i:s'),
'modDt' => null,
],
];
}
 
/**
* 커스텀 에셋 추가 (테스트용)
*
* @param array $asset
*/
public function addAsset(array $asset): void
{
$this->assets[] = $asset;
}
 
/**
* {@inheritdoc}
*/
public function findByPageUrl(int $skinSno, string $pageUrl): ?array
{
foreach ($this->assets as $asset) {
if ($asset['skinSno'] === $skinSno
&& $asset['pageUrl'] === $pageUrl
&& $asset['deleteFl'] === 'n') {
return $asset;
}
}
return null;
}
 
/**
* {@inheritdoc}
*/
public function findByAssetCode(int $skinSno, string $assetCode): ?array
{
foreach ($this->assets as $asset) {
if ($asset['skinSno'] === $skinSno
&& $asset['assetCode'] === $assetCode
&& $asset['deleteFl'] === 'n') {
return $asset;
}
}
return null;
}
 
/**
* {@inheritdoc}
*/
public function findAllBySkin(int $skinSno): array
{
return array_filter($this->assets, function ($asset) use ($skinSno) {
return $asset['skinSno'] === $skinSno && $asset['deleteFl'] === 'n';
});
}
}
 
변경 후DesignEditorAssetRepositoryStub.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Component/DesignEditor/DesignEditorRouter.php+24
변경 전DesignEditorRouter.php
return $this->repository->findByPageUrl($skinSno, $pageUrl);
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* URI 정규화
*
 
변경 후DesignEditorRouter.php
return $this->repository->findByPageUrl($skinSno, $pageUrl);
}
 
/**
* PC(front) 스킨 기준으로 pageUrl 에셋 매칭
*
* 요청 디바이스와 무관하게 운영중인 PC 스킨에서 조회한다.
* 디자인 에디터 에셋은 반응형 스킨(skinDevice=front)에만 생성되므로,
* 관리자 화면에서 페이지 존재 여부를 확인할 때 이 경로를 사용한다.
*
* 스킨 활성 여부(isDesignEditorSkinEnabled)는 확인하지 않는다.
* 적응형 스킨도 es_designEditorSkin 에 등록되어 있어 판별력이 없고,
* 실제 판별은 에셋 조회가 담당한다.
*
* @param string $pageUrl 페이지 URL (예: custom/my_page.php)
* @return array|null 에셋 정보 또는 null (매칭 실패)
*/
public function matchFrontPageUrl(string $pageUrl): ?array
{
$skinSno = $this->skinResolver->getFrontSkinSno();
if (is_null($skinSno)) {
return null;
}
 
return $this->repository->findByPageUrl($skinSno, $this->normalizeUri($pageUrl));
}
 
/**
* URI 정규화
*
 
Component/DesignEditor/DesignEditorSkinRepositoryInterface.php+30
변경 전DesignEditorSkinRepositoryInterface.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
변경 후DesignEditorSkinRepositoryInterface.php
<?php
 
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Component\DesignEditor;
 
/**
* 디자인 에디터 스킨 저장소 인터페이스
*
* 스킨 리졸버가 사용하는 스킨 조회 인터페이스
* 실제 DB 구현체와 테스트용 Stub 구현체를 교체할 수 있도록 인터페이스로 분리
*
* @author NHN godo
*/
interface DesignEditorSkinRepositoryInterface
{
/**
* 스킨 코드·스킨 디바이스로 스킨 정보 조회
*
* @param string $skinCode 스킨 코드 (design.skin 정책의 frontLive / mobileLive)
* @param string $skinDevice 스킨 디바이스 (front | mobile)
* @return array 스킨 정보 배열 (skinSno·skinCode·skinType·skinDevice·skinName 등), 없으면 빈 배열
*/
public function findBySkinCodeAndSkinDevice(string $skinCode, string $skinDevice): array;
}
 
Component/DesignEditor/DesignEditorSkinResolver.php+45-9
변경 전DesignEditorSkinResolver.php
 
namespace Bundle\Component\DesignEditor;
 
use Origin\Model\Design\DesignEditorSkin;
 
/**
* 디자인 에디터 스킨 리졸버
*
/** @var array|null 캐시된 디자인 에디터 스킨 정보 */
protected ?array $designEditorSkin = null;
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 디자인 에디터 스킨 사용 여부 확인
*
return $this->doGetCurrentSkinName();
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 디자인 에디터 스킨 사용 여부 확인 (구현)
*
// 디바이스 타입 결정
$skinDevice = \Request::isMobile() ? 'mobile' : 'front';
 
// es_designEditorSkin 테이블에서 해당 스킨 조회
$skin = DesignEditorSkin::query()
->where('skinCode', $skinCode)
->where('skinDevice', $skinDevice)
->first();
 
if ($skin) {
$this->designEditorSkin = $skin->toArray();
}
 
return $this->designEditorSkin;
 
return $this->skinConfig;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후DesignEditorSkinResolver.php
 
namespace Bundle\Component\DesignEditor;
 
 
 
/**
* 디자인 에디터 스킨 리졸버
*
/** @var array|null 캐시된 디자인 에디터 스킨 정보 */
protected ?array $designEditorSkin = null;
 
/** @var DesignEditorSkinRepositoryInterface 스킨 저장소 */
protected DesignEditorSkinRepositoryInterface $skinRepository;
 
/**
* @param DesignEditorSkinRepositoryInterface|null $skinRepository 스킨 저장소. 미주입(인자 없는 new - 튜닝 호환) 시 컨테이너에서 가져온다
*/
public function __construct(?DesignEditorSkinRepositoryInterface $skinRepository = null)
{
$this->skinRepository = $skinRepository ?? \App::getInstance(DesignEditorSkinRepositoryInterface::class);
}
 
/**
* 디자인 에디터 스킨 사용 여부 확인
*
return $this->doGetCurrentSkinName();
}
 
/**
* PC(front) 디자인 에디터 스킨 번호 조회
*
* 요청 디바이스와 무관하게 운영중인 PC 스킨을 기준으로 조회한다.
* 관리자 화면처럼 프론트 요청 문맥이 아닌 곳에서 사용한다.
*
* @return int|null 스킨 일련번호 또는 null
*/
public function getFrontSkinSno(): ?int
{
return $this->doGetFrontSkinSno();
}
 
/**
* 디자인 에디터 스킨 사용 여부 확인 (구현)
*
// 디바이스 타입 결정
$skinDevice = \Request::isMobile() ? 'mobile' : 'front';
 
// es_designEditorSkin 테이블에서 해당 스킨 조회 - 스킨 번호가 있는 행만 유효한 스킨으로 본다
$skin = $this->skinRepository->findBySkinCodeAndSkinDevice($skinCode, $skinDevice);
 
 
 
 
if (!empty($skin['skinSno'])) {
$this->designEditorSkin = $skin;
}
 
return $this->designEditorSkin;
 
return $this->skinConfig;
}
 
/**
* PC(front) 스킨 번호 조회 (구현)
*
* @return int|null
*/
protected function doGetFrontSkinSno(): ?int
{
$skinCode = $this->getSkinConfig()['frontLive'] ?? null;
if (empty($skinCode)) {
return null;
}
 
$skin = $this->skinRepository->findBySkinCodeAndSkinDevice($skinCode, 'front');
 
return $skin['skinSno'] ?? null;
}
}
 
Component/DesignEditor/DesignEditorSkinResolverStub.php-84
변경 전DesignEditorSkinResolverStub.php
<?php
 
/*
* Copyright (C) 2026 NHN COMMERCE. - All Rights Reserved
*
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
* is strictly prohibited.
*/
 
namespace Bundle\Component\DesignEditor;
 
/**
* 디자인 에디터 스킨 리졸버 Stub (테스트용)
*
* 단위 테스트를 위한 설정 가능한 구현체
*/
class DesignEditorSkinResolverStub extends DesignEditorSkinResolver
{
/**
* @param bool $enabled 디자인 에디터 스킨 활성화 여부
* @param int|null $skinSno 스킨 번호
* @param string|null $skinName 스킨명
*/
public function __construct(
protected bool $enabled = true,
protected ?int $skinSno = 1,
protected ?string $skinName = 'test_skin'
) {
}
 
/**
* 활성화 여부 설정
*
* @param bool $enabled
*/
public function setEnabled(bool $enabled): void
{
$this->enabled = $enabled;
}
 
/**
* 스킨 번호 설정
*
* @param int|null $skinSno
*/
public function setSkinSno(?int $skinSno): void
{
$this->skinSno = $skinSno;
}
 
/**
* 스킨명 설정
*
* @param string|null $skinName
*/
public function setSkinName(?string $skinName): void
{
$this->skinName = $skinName;
}
 
/**
* {@inheritdoc}
*/
protected function doIsDesignEditorSkinEnabled(): bool
{
return $this->enabled;
}
 
/**
* {@inheritdoc}
*/
protected function doGetCurrentSkinSno(): ?int
{
return $this->skinSno;
}
 
/**
* {@inheritdoc}
*/
protected function doGetCurrentSkinName(): ?string
{
return $this->skinName;
}
}
 
변경 후DesignEditorSkinResolverStub.php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Component/Policy/SeoTag.php+38-1
변경 전SeoTag.php
namespace Bundle\Component\Policy;
use App;
 
use Component\Database\DBTableField;
use Encryptor;
use Framework\Utility\StringUtils;
$realPath = gd_implode(DIRECTORY_SEPARATOR, [SYSSRCPATH, 'Bundle', 'Controller', $viewDirectory, ucfirst($_tmp['dirname']), (gd_implode("", array_map("ucfirst", explode("_", $_tmp['filename'])))) . 'Controller.php']);
}
}
if (!is_file($realPath)) {
return ['result' => false, 'code' => 'NOT FILE'];
}
if($etcPageFl === true){
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* saveSeoTag
 
변경 후SeoTag.php
namespace Bundle\Component\Policy;
use App;
use Bundle\Component\DesignEditor\DesignEditorRouter;
use Component\Database\DBTableField;
use Encryptor;
use Framework\Utility\StringUtils;
$realPath = gd_implode(DIRECTORY_SEPARATOR, [SYSSRCPATH, 'Bundle', 'Controller', $viewDirectory, ucfirst($_tmp['dirname']), (gd_implode("", array_map("ucfirst", explode("_", $_tmp['filename'])))) . 'Controller.php']);
}
}
if (!is_file($realPath) && !$this->existsDesignEditorPage($arrData['path'], $arrData['deviceFl'])) {
return ['result' => false, 'code' => 'NOT FILE'];
}
if($etcPageFl === true){
}
}
/**
* 디자인 에디터로 생성된 페이지 존재 여부
*
* 디자인 에디터 페이지는 컨트롤러 파일 없이 에셋 레코드로만 존재하므로
* 파일 기반 검증으로는 통과할 수 없다. 에셋은 반응형 스킨(skinDevice=front)에만
* 생성되므로 PC 쇼핑몰 등록에 한해 조회한다.
*
* 쿼리스트링이 붙은 경로는 제외한다. 기타 페이지 SEO 출력 조회는 REQUEST_URI
* 원본과 비교하므로 기존에도 지원하지 않던 형태이며, 이번 변경으로 열리지 않게 한다.
*
* 조회 중 예외는 종전 판정(NOT FILE)으로 되돌린다. 이 분기는 is_file() 실패 뒤에만
* 실행되므로, 여기서 예외가 새면 기존에 "존재하지 않습니다" 였던 모든 입력이
* "처리 중 오류" 로 바뀐다. 런타임 라우팅(Application::dispatchDesignEditorPage)과 같은 방침이다.
*
* @param string $path 입력 페이지 경로 (예: promotion/test01.php)
* @param string $deviceFl 디바이스 구분 (p: PC 쇼핑몰, m: 모바일 쇼핑몰)
*
* @return bool
*/
protected function existsDesignEditorPage(string $path, string $deviceFl): bool
{
if ($deviceFl !== 'p' || str_contains($path, '?')) {
return false;
}
try {
// 컨테이너 등록 키(ContainerDefinitionsOrigin)가 FQCN 이라 Bundle\ 접두사로 참조한다 - Application::dispatchDesignEditorPage 와 동일
$router = \App::getInstance(DesignEditorRouter::class);
return $router->matchFrontPageUrl($path) !== null;
} catch (\Throwable $e) {
Logger::channel('designEditor')->warning(__METHOD__ . ' : 디자인 에디터 에셋 조회 실패', ['exception' => $e, 'path' => $path]);
return false;
}
}
/**
* saveSeoTag
 
쿠폰 웹훅 기능 추가
Component/Coupon/CouponAdmin.php+61-1
변경 전CouponAdmin.php
use Origin\Service\Coupon\CopyCouponService;
use Origin\Service\Coupon\UpdateCouponService;
use Origin\Service\Member\ManagerLoginService;
 
use Request;
use Session;
use UserFilePath;
$result = $this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
if ($result > 0) {
echo json_encode(['result' => 'ok', 'msg' => __('쿠폰 발급상태가 변경되었습니다.')]);
 
} else {
echo json_encode(['result' => 'fail', 'msg' => __('다시 시도해 주세요.')]);
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 쿠폰 삭제하기
*
$couponField = ['imageStorage', 'couponImage'];
$deletedCouponData = $this->getCouponInfo($couponNo, gd_implode(',', $couponField));
 
 
 
 
$arrBind = [
'i',
$couponNo,
];
// --- 삭제
$this->db->set_delete_db(DB_COUPON, 'couponNo = ?', $arrBind);
 
 
 
 
// 이미지 삭제
if (gd_isset($deletedCouponData['imageStorage']) && $deletedCouponData['imageStorage'] == 'obs') {
 
throw new \Exception(__('쿠폰 등록에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
 
break;
}
case 'modify' : {
throw new \Exception(__('쿠폰 수정에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
 
 
// 이미지를 새롭게 등록하는 경우, 기존 OBS 이미지 삭제
if ($isImageUploaded && gd_isset($couponDataBeforeUpdate['imageStorage']) && gd_isset($couponDataBeforeUpdate['couponImage'])) {
$this->deleteCouponImage($couponDataBeforeUpdate['imageStorage'], $couponDataBeforeUpdate['couponImage']);
 
throw new \Exception(__('오프라인 쿠폰 등록에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
 
break;
}
case 'modify' : {
throw new \Exception(__('오프라인 쿠폰 수정에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
 
 
// 이미지를 새롭게 등록하는 경우, 기존 OBS 이미지 삭제
if ($isImageUploaded && gd_isset($couponDataBeforeUpdate['imageStorage']) && gd_isset($couponDataBeforeUpdate['couponImage'])) {
$this->deleteCouponImage($couponDataBeforeUpdate['imageStorage'], $couponDataBeforeUpdate['couponImage']);
 
변경 후CouponAdmin.php
use Origin\Service\Coupon\CopyCouponService;
use Origin\Service\Coupon\UpdateCouponService;
use Origin\Service\Member\ManagerLoginService;
use Origin\Service\WebHook\PromotionWebHookService;
use Request;
use Session;
use UserFilePath;
$result = $this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
if ($result > 0) {
echo json_encode(['result' => 'ok', 'msg' => __('쿠폰 발급상태가 변경되었습니다.')]);
$this->sendCouponSavedWebHook($arrData['couponNo'], 'UPDATED');
} else {
echo json_encode(['result' => 'fail', 'msg' => __('다시 시도해 주세요.')]);
}
}
 
/**
* 쿠폰 등록/수정 웹훅 발송
*
* @param int|string $couponNo 쿠폰번호
* @param string $actionType 'CREATED' | 'UPDATED'
*
* @return void
*/
protected function sendCouponSavedWebHook($couponNo, string $actionType)
{
try {
$couponData = $this->getCouponInfo($couponNo, 'couponNm, couponKind, couponSaveType, couponType, couponUseType');
$promotionWebHookService = \App::getInstance(PromotionWebHookService::class);
$promotionWebHookService->sendCouponUpdatedWebHook((int)$couponNo, $actionType, $couponData);
} catch (\Throwable $e) {
\App::getInstance('logger')->channel('coupon')->warning(__METHOD__ . ' 쿠폰 등록/수정 웹훅 발송 실패', [
'couponNo' => $couponNo,
'actionType' => $actionType,
'exception' => $e,
]);
}
}
 
/**
* 쿠폰 삭제 웹훅 발송
*
* @param int|string $couponNo 쿠폰번호
* @param array $deletedCouponData 삭제 전 조회한 쿠폰 정보
*
* @return void
*/
protected function sendCouponDeletedWebHook($couponNo, $deletedCouponData)
{
try {
$promotionWebHookService = \App::getInstance(PromotionWebHookService::class);
$promotionWebHookService->sendCouponUpdatedWebHook((int)$couponNo, 'DELETED', $deletedCouponData);
} catch (\Throwable $e) {
\App::getInstance('logger')->channel('coupon')->warning(__METHOD__ . ' 쿠폰 삭제 웹훅 발송 실패', [
'couponNo' => $couponNo,
'exception' => $e,
]);
}
}
 
/**
* 쿠폰 삭제하기
*
$couponField = ['imageStorage', 'couponImage'];
$deletedCouponData = $this->getCouponInfo($couponNo, gd_implode(',', $couponField));
 
// 삭제 후에는 행이 없어 웹훅 발송용 정보를 삭제 전에 조회
$couponWebHookData = $this->getCouponInfo($couponNo, 'couponNm, couponKind, couponSaveType, couponType, couponUseType');
 
$arrBind = [
'i',
$couponNo,
];
// --- 삭제
$deletedCount = $this->db->set_delete_db(DB_COUPON, 'couponNo = ?', $arrBind);
if ($deletedCount > 0) {
$this->sendCouponDeletedWebHook($couponNo, $couponWebHookData);
}
 
// 이미지 삭제
if (gd_isset($deletedCouponData['imageStorage']) && $deletedCouponData['imageStorage'] == 'obs') {
 
throw new \Exception(__('쿠폰 등록에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
$this->sendCouponSavedWebHook($couponNo, 'CREATED');
break;
}
case 'modify' : {
throw new \Exception(__('쿠폰 수정에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
$this->sendCouponSavedWebHook($couponNo, 'UPDATED');
 
// 이미지를 새롭게 등록하는 경우, 기존 OBS 이미지 삭제
if ($isImageUploaded && gd_isset($couponDataBeforeUpdate['imageStorage']) && gd_isset($couponDataBeforeUpdate['couponImage'])) {
$this->deleteCouponImage($couponDataBeforeUpdate['imageStorage'], $couponDataBeforeUpdate['couponImage']);
 
throw new \Exception(__('오프라인 쿠폰 등록에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
$this->sendCouponSavedWebHook($couponNo, 'CREATED');
break;
}
case 'modify' : {
throw new \Exception(__('오프라인 쿠폰 수정에 실패하였습니다. 고객센터에 문의해주세요.'));
}
 
$this->sendCouponSavedWebHook($couponNo, 'UPDATED');
 
// 이미지를 새롭게 등록하는 경우, 기존 OBS 이미지 삭제
if ($isImageUploaded && gd_isset($couponDataBeforeUpdate['imageStorage']) && gd_isset($couponDataBeforeUpdate['couponImage'])) {
$this->deleteCouponImage($couponDataBeforeUpdate['imageStorage'], $couponDataBeforeUpdate['couponImage']);
 
Component/Coupon/Coupon.php+36
변경 전Coupon.php
use Globals;
use Request;
use Session;
 
 
/**
* Coupon Class
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['couponNo']);
$this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
$logger->info(sprintf(__('쿠폰 사용가능 상태 변경 couponNo [%1$s] date [%2$s] '), $arrData['couponNo'], $now) . Request::getReferer());
 
}
if(!$return && $couponType != 'f') {
$arrData['couponType'] = 'f';
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['couponNo']);
$this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
$logger->info(sprintf(__('쿠폰 발급종료 상태 변경 [%1$s] couponNo [%2$s] date [%3$s] '), $msg, $arrData['couponNo'], $now) . Request::getReferer());
 
if ($couponIssuedQuantityCheck) {
$return = true;
}
return $return;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public function checkCouponTypeArr($couponNoArr)
{
if(!is_array($couponNoArr)) $couponNoArr = explode(INT_DIVISION, $couponNoArr);
 
변경 후Coupon.php
use Globals;
use Request;
use Session;
use Origin\Service\WebHook\PromotionWebHookService;
 
/**
* Coupon Class
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['couponNo']);
$this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
$logger->info(sprintf(__('쿠폰 사용가능 상태 변경 couponNo [%1$s] date [%2$s] '), $arrData['couponNo'], $now) . Request::getReferer());
$this->sendCouponStatusChangedWebHook($couponNo, $coupon, $arrData['couponType']);
}
if(!$return && $couponType != 'f') {
$arrData['couponType'] = 'f';
$this->db->bind_param_push($arrBind['bind'], 'i', $arrData['couponNo']);
$this->db->set_update_db(DB_COUPON, $arrBind['param'], 'couponNo = ?', $arrBind['bind'], false);
$logger->info(sprintf(__('쿠폰 발급종료 상태 변경 [%1$s] couponNo [%2$s] date [%3$s] '), $msg, $arrData['couponNo'], $now) . Request::getReferer());
$this->sendCouponStatusChangedWebHook($couponNo, $coupon, $arrData['couponType']);
if ($couponIssuedQuantityCheck) {
$return = true;
}
return $return;
}
 
/**
* 발급상태 자동 전환 웹훅 발송
*
* checkCouponType() 은 현재 상태와 무관하게 UPDATE 를 재실행하므로
* 실제로 값이 바뀌는 경우에만 발송해 프론트 요청마다 중복 발행되는 것을 막는다.
* 웹훅 필드는 checkCouponType() 의 공용 조회에 섞지 않고 상태가 바뀐 때만 따로 조회하며,
* 웹훅 실패가 발급 가능 여부 판정을 막지 않도록 예외는 로그만 남긴다.
*
* @param int|string $couponNo 쿠폰번호
* @param array|mixed $coupon 변경 전 쿠폰 정보 (발급상태 비교용)
* @param string|mixed $changedCouponType 변경된 발급상태
*
* @return void
*/
protected function sendCouponStatusChangedWebHook($couponNo, $coupon, $changedCouponType)
{
try {
$beforeCouponType = is_array($coupon) ? ($coupon['couponType'] ?? '') : '';
if ($beforeCouponType === (string)$changedCouponType) {
return;
}
 
$couponData = $this->getCouponInfo($couponNo, 'couponNm, couponKind, couponSaveType, couponType, couponUseType');
$promotionWebHookService = \App::getInstance(PromotionWebHookService::class);
$promotionWebHookService->sendCouponUpdatedWebHook((int)$couponNo, 'UPDATED', $couponData);
} catch (\Throwable $e) {
\App::getInstance('logger')->channel('coupon')->warning(__METHOD__ . ' 쿠폰 발급상태 변경 웹훅 발송 실패', [
'couponNo' => $couponNo,
'exception' => $e,
]);
}
}
 
public function checkCouponTypeArr($couponNoArr)
{
if(!is_array($couponNoArr)) $couponNoArr = explode(INT_DIVISION, $couponNoArr);
 
SMS 발송 세팅 현황 안내 추가
Admin/crm/message_config.php+21
변경 전message_config.php
</span>
</header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!-- 기본 설정 섹션 -->
<section class="ncua-card default-setting">
<header class="ncua-card__header">
 
변경 후message_config.php
</span>
</header>
 
<!-- SMS 발송 세팅 현황 -->
<div class="ncua-accordion ncua-accordion--blue sms-send-setting-status">
<div class="ncua-accordion__summary-style">안내사항</div>
<ul class="ncua-accordion__content">
<li class="ncua-notice-info">
원활한 SMS 발송을 위해서는 아래 항목이 모두 <span class="sms-send-setting-completed">완료</span>로 표기되어야 합니다.
<ul class="sms-send-setting-items">
<?php foreach ($smsSendSettingItems as $smsSendSettingItem): ?>
<li class="ncua-notice-info">
<?= gd_htmlspecialchars($smsSendSettingItem['name']) ?> :
<span class="<?= gd_htmlspecialchars($smsSendSettingItem['statusClass'] ?? '') ?>"><?= gd_htmlspecialchars($smsSendSettingItem['statusText']) ?></span>
</li>
<?php endforeach; ?>
</ul>
</li>
<?php if (!empty($smsSendSettingGuideUrl)): ?>
<li class="ncua-notice-info">상세 항목별 설정 방법은 <a class="sms-send-setting-guide-link" href="<?= gd_htmlspecialchars($smsSendSettingGuideUrl) ?>" target="_blank" rel="noopener noreferrer">SMS 발송 설정 가이드</a> 를 확인해주세요.</li>
<?php endif; ?>
</ul>
</div>
 
<!-- 기본 설정 섹션 -->
<section class="ncua-card default-setting">
<header class="ncua-card__header">
 
카카오 회원 가입 팝업 처리 개선
Component/Member/PageAccess.php+15
변경 전PageAccess.php
'guest' => '0',
'intro' => 'x',
],
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
'kakao/kakaoo_login.php' => [
'member' => '0',
'guest' => '0',
 
변경 후PageAccess.php
'guest' => '0',
'intro' => 'x',
],
'member/kakao_sync.php' => [
'member' => 'x',
'guest' => '0',
'intro' => 'x',
],
'member/kakao_connect.php' => [
'member' => 'x',
'guest' => '0',
'intro' => 'x',
],
'member/kakao_connect_ps.php' => [
'member' => 'x',
'guest' => '0',
'intro' => 'x',
],
'kakao/kakaoo_login.php' => [
'member' => '0',
'guest' => '0',
 
최신 iOS Safari 환경 네이버페이(주문형) 이동 안정성 개선
Controller/Front/Goods/NaverPayController.php+11-12
변경 전NaverPayController.php
?>
<html>
<body>
<form name="frm" method="get" action="<?= $redirectUrl ?>">
</form>
</body>
<?php echo $fbScript; ?>
<script>
<?php if(\Request::isMobileDevice()){ ?>
<?php if($naverPayConfig['mobileButtonTarget'] == 'new') {?>
document.frm.target = "_blank";
<?php }
else {?>
document.frm.target = "_top";
<?php }?>
document.frm.submit();
<?php }else{ ?>
document.frm.submit();
 
<?php } ?>
window.onpageshow = function(event) {
/**
 
변경 후NaverPayController.php
?>
<html>
<body>
 
 
</body>
<?php echo $fbScript; ?>
<script>
/**
* iOS Safari ITP 대응: 폼 자동 submit 대신 location.replace로 최상위 이동
* (최상위 내비게이션은 ITP 차단 대상이 아니라 이동 끊김이 줄어듬)
* 페이스북 픽셀($fbScript)은 측정 유지를 위해 이동 직전에 먼저 출력
*/
<?php if (\Request::isMobileDevice() && $naverPayConfig['mobileButtonTarget'] === 'new') { ?>
window.open("<?= $redirectUrl ?>"); // 모바일 '새 창' 설정
<?php } elseif (\Request::isMobileDevice()) { ?>
window.top.location.replace("<?= $redirectUrl ?>"); // 모바일 _top 이동
<?php } else { ?>
location.replace("<?= $redirectUrl ?>"); // PC 현재 창 이동
<?php } ?>
window.onpageshow = function(event) {
/**
 
예치금·마일리지 포함 주문 환불 처리 안정성 개선
Admin/order/refund_do_new.php+6-3
변경 전refund_do_new.php
if ($(this).val() == '') {
$(this).val(0);
}
if ($.isNumeric($(this).val())) {
refundPriceSum += parseInt($(this).val());
lessEachRefund = lessRefundPrice + parseInt($(this).val());
 
 
 
} else {
$(this).focus();
}
 
변경 후refund_do_new.php
if ($(this).val() == '') {
$(this).val(0);
}
// 콤마만 제거한 값으로 합계 검증 - 콤마 포함 값은 $.isNumeric 을 우회해 그대로 전송된다.
// (소수점은 제거하지 않는다 - decimal "75900.00" 의 소수점을 지우면 ×100)
var refundPriceVal = String($(this).val()).replace(/,/g, '');
if ($.isNumeric(refundPriceVal)) {
refundPriceSum += parseInt(refundPriceVal);
lessEachRefund = lessRefundPrice + parseInt(refundPriceVal);
} else {
$(this).focus();
}
 
Component/Order/ReOrderCalculation.php+68
변경 전ReOrderCalculation.php
{
use CommonLogStorageTrait; // 공용 로그 저장
 
 
 
 
/**
* @var null|object 디비 접속
*/
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public function setRefundCompleteOrderGoodsNew($getData, $autoProcess = false, $userHandleFl = false)
{
$goods = \App::load(\Component\Goods\Goods::class);
$orderAdmin = App::load(\Component\Order\OrderAdmin::class);
 
 
 
 
 
 
 
 
 
// 환불수단에 맞지않는 금액값이 넘어오면 리턴처리
if ($getData['info']['refundMethod'] == '현금환불') {
if ($getData['info']['completePgPrice'] > 0 || $getData['info']['completeDepositPrice'] > 0 || $getData['info']['completeMileagePrice'] > 0) {
}
$checkDivisionVal = 0;
do {
 
foreach ($arrayData as $k => $v) {
if ($checkKey != '') {
if (isset($v['orderDeliverySno']) && $checkKey != '' && $checkKey != $v['orderDeliverySno']) {
}
}
}
 
 
 
 
 
 
 
 
 
 
 
 
} while ($checkDivisionVal == 0);
}
 
$checkDivisionVal = 1;
 
do {
 
 
 
// free supply vat 순으로 체크하면서 오버되면 재분배
if ($orgTotalData['realTaxFreePrice'] < $tempTaxFreeSum) { // 환불 업데이트 free 합계값이 오버인경우
// 오버값
$checkDivisionVal = 0;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
} while ($checkDivisionVal == 1);
 
return [$updateDeliveryData, $refundDeliveryData];
 
변경 후ReOrderCalculation.php
{
use CommonLogStorageTrait; // 공용 로그 저장
 
// 환불 방법 설정의 결제수단별 완료 환불 금액 입력 키 (산술 처리 전 정규화 대상)
const REFUND_COMPLETE_PRICE_KEYS = ['completeCashPrice', 'completePgPrice', 'completeDepositPrice', 'completeMileagePrice'];
 
/**
* @var null|object 디비 접속
*/
}
}
 
/**
* 환불 금액을 산술 처리 가능한 정수로 정규화한다.
*
* 콤마만 제거하고 소수점은 보존한다. 비숫자를 일괄 제거하면 decimal(12,2) 값
* "75900.00" 의 소수점까지 지워져 ×100 이 된다.
*
* @param string|int|float $completePrice 환불 금액 입력값
* @return int
* @throws Exception 숫자가 아니거나 음수인 경우
*/
protected function normalizeRefundCompletePrice(string|int|float $completePrice): int
{
$normalizedPrice = trim(str_replace(',', '', (string)$completePrice));
 
if ($normalizedPrice === '') {
return 0;
}
if (!is_numeric($normalizedPrice)) {
throw new Exception(__('환불 금액을 숫자로 확인할 수 없습니다. 다시 확인해주세요.'));
}
if ((float)$normalizedPrice < 0) {
throw new Exception(__('환불 금액은 0 보다 작을 수 없습니다. 다시 확인해주세요.'));
}
 
// 소수부 절사 ("75900.00" → 75900)
return (int)(float)$normalizedPrice;
}
 
public function setRefundCompleteOrderGoodsNew($getData, $autoProcess = false, $userHandleFl = false)
{
$goods = \App::load(\Component\Goods\Goods::class);
$orderAdmin = App::load(\Component\Order\OrderAdmin::class);
 
// 콤마가 섞인 값("347,180")은 형변환 시 347 로 잘려 안분 합계가 어긋나 무한루프를 유발한다.
foreach (self::REFUND_COMPLETE_PRICE_KEYS as $completePriceKey) {
if (!isset($getData['info'][$completePriceKey])) {
continue;
}
$getData['info'][$completePriceKey] = $this->normalizeRefundCompletePrice($getData['info'][$completePriceKey]);
}
 
// 환불수단에 맞지않는 금액값이 넘어오면 리턴처리
if ($getData['info']['refundMethod'] == '현금환불') {
if ($getData['info']['completePgPrice'] > 0 || $getData['info']['completeDepositPrice'] > 0 || $getData['info']['completeMileagePrice'] > 0) {
}
$checkDivisionVal = 0;
do {
$beforeDivisionVal = $tempDivisionVal;
foreach ($arrayData as $k => $v) {
if ($checkKey != '') {
if (isset($v['orderDeliverySno']) && $checkKey != '' && $checkKey != $v['orderDeliverySno']) {
}
}
}
// 무한루프 방지: 1회 순회 동안 차액이 줄지 않으면 더 안분할 항목이 없으므로 종료한다.
if ($checkDivisionVal === 0 && $tempDivisionVal === $beforeDivisionVal) {
// 안분이 덜 된 상태로 환불이 진행되므로 error 로 남긴다. (추적용으로 대상 키를 함께 기록)
\Logger::channel('order')->error(__METHOD__ . '[' . __LINE__ . '], 안분 차액을 맞출 수 없어 루프 종료(무한루프 방지): ', [
'strText' => $strText,
'remainDivisionVal' => $tempDivisionVal,
'divisionSum' => $divisionSum,
'orgPrice' => $orgPrice,
'divisionKeys' => array_keys($arrayData),
]);
break;
}
} while ($checkDivisionVal == 0);
}
 
$checkDivisionVal = 1;
 
do {
// 무한루프 방지용 직전 순회 tax 합계 (변화가 없으면 재분배 불가로 보고 종료)
$beforeDivisionSignature = $tempTaxSupplySum . STR_DIVISION . $tempTaxVatSum . STR_DIVISION . $tempTaxFreeSum;
 
// free supply vat 순으로 체크하면서 오버되면 재분배
if ($orgTotalData['realTaxFreePrice'] < $tempTaxFreeSum) { // 환불 업데이트 free 합계값이 오버인경우
// 오버값
$checkDivisionVal = 0;
}
 
// 재분배 후에도 tax 합계가 동일하면 진행이 없는 상태이므로 종료
if ($checkDivisionVal === 1 && $beforeDivisionSignature === ($tempTaxSupplySum . STR_DIVISION . $tempTaxVatSum . STR_DIVISION . $tempTaxFreeSum)) {
// 배송비 tax 안분이 덜 된 상태로 환불이 진행되므로 error 로 남긴다. (추적용으로 대상 키를 함께 기록)
\Logger::channel('order')->error(__METHOD__ . '[' . __LINE__ . '], 배송비 tax 안분이 수렴하지 않아 루프 종료(무한루프 방지): ', [
'orgTotalData' => $orgTotalData,
'tempTaxSupplySum' => $tempTaxSupplySum,
'tempTaxVatSum' => $tempTaxVatSum,
'tempTaxFreeSum' => $tempTaxFreeSum,
'deliveryKeys' => array_keys($refundDeliveryData),
]);
break;
}
 
} while ($checkDivisionVal == 1);
 
return [$updateDeliveryData, $refundDeliveryData];
 
배송지 수정 기능 보안 강화
Admin/order/inc_order_view.php+8-8
변경 전inc_order_view.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
</td>
<?php } ?>
<?php } else {?>
<?php if($rowAll === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php if($data['receiverZonecode']){ echo "[".$data['receiverZonecode']."]"; } ?>
<?php if($data['receiverZipcode']){ echo "(".$data['receiverZipcode'].")"; } ?>
<br />
<?php if($data['receiverAddress']){ echo $data['receiverAddress']; } ?>
<?php if($data['receiverAddressSub']){ echo " ".$data['receiverAddressSub']; } ?>
</td>
<?php } ?>
<?php } ?>
 
변경 후inc_order_view.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
</td>
<?php } ?>
<?php } else {?>
<?php if($rowAll === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php if($data['receiverZonecode']){ echo "[".gd_htmlspecialchars($data['receiverZonecode'])."]"; } ?>
<?php if($data['receiverZipcode']){ echo "(".gd_htmlspecialchars($data['receiverZipcode']).")"; } ?>
<br />
<?php if($data['receiverAddress']){ echo gd_htmlspecialchars($data['receiverAddress']); } ?>
<?php if($data['receiverAddressSub']){ echo " ".gd_htmlspecialchars($data['receiverAddressSub']); } ?>
</td>
<?php } ?>
<?php } ?>
 
Admin/order/layer_regular_order_shipping_address.php+5-5
변경 전layer_regular_order_shipping_address.php
<?php foreach ($shippingInfo as $info) : ?>
<tr>
<td><input type="radio" name="shippingSno" value="<?=$info['sno']?>"></td>
<td><?php if ($info['defaultFl'] == 'y'): ?> (기본배송지) <br><?php endif;?><?=$info['shippingTitle']?></td>
<td><?=$info['shippingName']?></td>
<?php /* 주소 구성요소가 각각 '-' 로 치환돼 하이픈이 반복되므로 칸 전체를 단일 '-' 로 표시 */ ?>
<?php if (!empty($shippingMaskLabel)) { ?>
<td><?= $maskChar ?></td>
<?php } else { ?>
<td><?=$info['shippingZonecode']?> <?=$info['shippingAddress']?> <?=$info['shippingAddressSub']?></td>
<?php } ?>
<td> 전화번호 : <?=$info['shippingPhone']?> <br/> 휴대폰 : <?=$info['shippingCellPhone']?></td>
<td><?=$info['shippingMessage']?></td>
</tr>
<?php endforeach; ?>
</tbody>
 
변경 후layer_regular_order_shipping_address.php
<?php foreach ($shippingInfo as $info) : ?>
<tr>
<td><input type="radio" name="shippingSno" value="<?=$info['sno']?>"></td>
<td><?php if ($info['defaultFl'] == 'y'): ?> (기본배송지) <br><?php endif;?><?=gd_htmlspecialchars($info['shippingTitle'])?></td>
<td><?=gd_htmlspecialchars($info['shippingName'])?></td>
<?php /* 주소 구성요소가 각각 '-' 로 치환돼 하이픈이 반복되므로 칸 전체를 단일 '-' 로 표시 */ ?>
<?php if (!empty($shippingMaskLabel)) { ?>
<td><?= $maskChar ?></td>
<?php } else { ?>
<td><?=gd_htmlspecialchars($info['shippingZonecode'])?> <?=gd_htmlspecialchars($info['shippingAddress'])?> <?=gd_htmlspecialchars($info['shippingAddressSub'])?></td>
<?php } ?>
<td> 전화번호 : <?=gd_htmlspecialchars($info['shippingPhone'])?> <br/> 휴대폰 : <?=gd_htmlspecialchars($info['shippingCellPhone'])?></td>
<td><?=gd_htmlspecialchars($info['shippingMessage'])?></td>
</tr>
<?php endforeach; ?>
</tbody>
 
Admin/order/layout_order_goods_list.php+16-16
변경 전layout_order_goods_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_html_cut($val['orderMemo'], 30, '..') : '-'?>
</td>
<?php } ?>
<?php } else {?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_html_cut($val['multiShippingOrderMemo'], 30, '..');
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_html_cut($val['orderMemo'], 30, '..');
}
else {
echo '-';
<?php } // 주문번호 ?>
 
<?php if($gridKey === 'orderName' && $rowChk === 0){ // 주문자 ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>" data-cell-phone="<?= $memberMasking->masking('order','tel',$val['smsCellPhone']); ?>">
<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ // 수령자 ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= $memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']) ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo $memberMasking->masking('order', 'name', $val['multiShippingReceiverName']);
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo $memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']);
}
?>
</td>
<td <?= $orderMultiShippingRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
<?php } ?>
</td>
<?php } ?>
<td <?= $orderGoodsRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
<?php } ?>
</td>
<?php } ?>
 
변경 후layout_order_goods_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..')) : '-'?>
</td>
<?php } ?>
<?php } else {?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['multiShippingOrderMemo'], 30, '..'));
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..'));
}
else {
echo '-';
<?php } // 주문번호 ?>
 
<?php if($gridKey === 'orderName' && $rowChk === 0){ // 주문자 ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>" data-cell-phone="<?= gd_htmlspecialchars($memberMasking->masking('order','tel',$val['smsCellPhone'])); ?>">
<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ // 수령자 ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= gd_htmlspecialchars($memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName'])) ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo gd_htmlspecialchars($memberMasking->masking('order', 'name', $val['multiShippingReceiverName']));
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo gd_htmlspecialchars($memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']));
}
?>
</td>
<td <?= $orderMultiShippingRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
<?php } ?>
</td>
<?php } ?>
<td <?= $orderGoodsRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
<?php } ?>
</td>
<?php } ?>
 
Admin/order/layout_order_goods_simple_list.php+10-10
변경 전layout_order_goods_simple_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_html_cut($val['orderMemo'], 30, '..') : '-'?>
</td>
<?php } ?>
<?php } else {?>
<td <?= $orderGoodsRowSpan ?>>
<?php
if($val['orderMemo']){
echo gd_html_cut($val['orderMemo'], 30, '..');
if($val['multiShippingFl'] === 'y'){
echo ' 외' . ((int)$val['totalOrderInfoCount']-1) . '건';
}
<?php } // 주문번호 ?>
 
<?php if($gridKey === 'orderName' && $rowChk === 0){ // 주문자 ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>" data-cell-phone="<?= $memberMasking->masking('order','tel',$val['smsCellPhone']); ?>">
<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ // 수령자 ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= $memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']) ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php
echo $memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']);
if($val['totalOrderInfoCount'] > 1){
echo ' 외 ' . ((int)$val['totalOrderInfoCount']-1) . '명';
}
<td <?= $orderGoodsRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
<?php } ?>
</td>
<?php } //수령자 주소?>
 
변경 후layout_order_goods_simple_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..')) : '-'?>
</td>
<?php } ?>
<?php } else {?>
<td <?= $orderGoodsRowSpan ?>>
<?php
if($val['orderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..'));
if($val['multiShippingFl'] === 'y'){
echo ' 외' . ((int)$val['totalOrderInfoCount']-1) . '건';
}
<?php } // 주문번호 ?>
 
<?php if($gridKey === 'orderName' && $rowChk === 0){ // 주문자 ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>" data-cell-phone="<?= gd_htmlspecialchars($memberMasking->masking('order','tel',$val['smsCellPhone'])); ?>">
<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ // 수령자 ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= gd_htmlspecialchars($memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName'])) ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php
echo gd_htmlspecialchars($memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']));
if($val['totalOrderInfoCount'] > 1){
echo ' 외 ' . ((int)$val['totalOrderInfoCount']-1) . '명';
}
<td <?= $orderGoodsRowSpan ?>>
<?php /* 배송지: 탈퇴/휴면 회원 주문이면 단일 '-' 표시(개인정보 은닉), 아니면 기존 조합 표시 */ ?>
<?php if (!empty($data[$orderNo]['orderMaskLabel'])) { echo $data[$orderNo]['maskChar']; } else { ?>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
<?php } ?>
</td>
<?php } //수령자 주소?>
 
Admin/order/layout_order_list.php+10-10
변경 전layout_order_list.php
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_html_cut($val['multiShippingOrderMemo'], 30, '..');
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_html_cut($val['orderMemo'], 30, '..');
}
else {
echo '-';
<?php } //주문번호 ?>
 
<?php if($gridKey === 'orderName'){ //주문자 ?>
<td class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>" data-cell-phone="<?= $memberMasking->masking('order','tel',$val['smsCellPhone']); ?>">
<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo $memberMasking->masking('order', 'name', $val['multiShippingReceiverName']);
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo $memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']);
}
?>
</td>
 
<?php if($gridKey === 'receiverAddress'){ //수령자 주소 ?>
<td>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
</td>
<?php } //수령자 주소 ?>
 
 
변경 후layout_order_list.php
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['multiShippingOrderMemo'], 30, '..'));
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..'));
}
else {
echo '-';
<?php } //주문번호 ?>
 
<?php if($gridKey === 'orderName'){ //주문자 ?>
<td class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>" data-cell-phone="<?= gd_htmlspecialchars($memberMasking->masking('order','tel',$val['smsCellPhone'])); ?>">
<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])); ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo gd_htmlspecialchars($memberMasking->masking('order', 'name', $val['multiShippingReceiverName']));
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo gd_htmlspecialchars($memberMasking->masking('order', 'name', $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']));
}
?>
</td>
 
<?php if($gridKey === 'receiverAddress'){ //수령자 주소 ?>
<td>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
</td>
<?php } //수령자 주소 ?>
 
 
Admin/order/logistics_layout_order_goods_list.php+16-16
변경 전logistics_layout_order_goods_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_html_cut($val['orderMemo'], 30, '..') : '-'?>
</td>
<?php } ?>
<?php } else {?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_html_cut($val['multiShippingOrderMemo'], 30, '..');
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_html_cut($val['orderMemo'], 30, '..');
}
else {
echo '-';
</td>
<?php } ?>
<?php if($gridKey === 'orderName' && $rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']) ?>" data-cell-phone="<?= $memberMasking->masking('order','tel',$val['smsCellPhone']); ?>">
<?= $memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName']) ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName'] ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo $val['multiShippingReceiverName'];
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo $data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName'];
}
?>
</td>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
</td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".$val['receiverZonecode']."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".$val['receiverZipcode'].")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo $val['receiverAddress']; } ?>
<?php if($val['receiverAddressSub']){ echo " ".$val['receiverAddressSub']; } ?>
</td>
<?php } ?>
<?php } ?>
 
변경 후logistics_layout_order_goods_list.php
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?=($val['orderMemo']) ? gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..')) : '-'?>
</td>
<?php } ?>
<?php } else {?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingOrderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['multiShippingOrderMemo'], 30, '..'));
if(((int)$val['multiShippingOrderMemoCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingOrderMemoCount']-1) . '건';
}
}
else {
if($val['orderMemo']){
echo gd_htmlspecialchars(gd_html_cut($val['orderMemo'], 30, '..'));
}
else {
echo '-';
</td>
<?php } ?>
<?php if($gridKey === 'orderName' && $rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan; ?> class="js-member-info" data-member-no="<?= $val['memNo'] ?>" data-member-name="<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])) ?>" data-cell-phone="<?= gd_htmlspecialchars($memberMasking->masking('order','tel',$val['smsCellPhone'])); ?>">
<?= gd_htmlspecialchars($memberMasking->masking('order','name',$data[$orderNo]['withdrawnMembersPersonalData']['orderName'] ?: $val['orderName'])) ?>
<p class="mgb0">
<?php if (!$val['memNo']) { ?>
<?php if (!$val['memNoCheck']) { ?>
<?php if($gridKey === 'receiverName'){ ?>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>><?= gd_htmlspecialchars($data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']) ?></td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<?php
if($val['multiShippingFl'] === 'y'){
if($val['multiShippingReceiverName']){
echo gd_htmlspecialchars($val['multiShippingReceiverName']);
if(((int)$val['multiShippingReceiverNameCount']-1) > 0){
echo ' 외 ' . ((int)$val['multiShippingReceiverNameCount']-1) . '명';
}
}
}
else {
echo gd_htmlspecialchars($data[$orderNo]['withdrawnMembersPersonalData']['receiverName'] ?: $val['receiverName']);
}
?>
</td>
<?php if($useMultiShippingKey === true){ ?>
<?php if($rowMultiShipping === 0){ ?>
<td <?= $orderMultiShippingRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
</td>
<?php } ?>
<?php } else {?>
<?php if($rowChk === 0){ ?>
<td <?= $orderGoodsRowSpan ?>>
<?php if($val['receiverZonecode']){ echo "[".gd_htmlspecialchars($val['receiverZonecode'])."]"; } ?>
<?php if($val['receiverZipcode']){ echo "(".gd_htmlspecialchars($val['receiverZipcode']).")"; } ?>
<br />
<?php if($val['receiverAddress']){ echo gd_htmlspecialchars($val['receiverAddress']); } ?>
<?php if($val['receiverAddressSub']){ echo " ".gd_htmlspecialchars($val['receiverAddressSub']); } ?>
</td>
<?php } ?>
<?php } ?>
 
Admin/order/popup_order_packet.php+3-3
변경 전popup_order_packet.php
}
?>
</td>
<td class="center"><?= isset($withdrawnMaskLabels[$data['orderNo']]) ? $maskChar : $data['receiverName'] ?></td>
<td class="center">
<?php if (isset($withdrawnMaskLabels[$data['orderNo']])) { ?>
<div><?= $maskChar ?></div>
<?php if (isset($withdrawnMaskLabels[$data['orderNo']])) { ?>
<?= $maskChar ?>
<?php } else if (empty($data['isDefaultMall']) === true) { ?>
(<?=$data['receiverZonecode']?>) <?=$data['receiverAddressSub']?> <?=$data['receiverState']?> <?=$data['receiverCity']?> <?=$data['receiverAddress']?> <?=$data['receiverCountry']?>
<?php } else { ?>
(<?=$data['receiverZonecode']?>) <?=$data['receiverAddress']?> <?=$data['receiverAddressSub']?>
<?php } ?>
</td>
<td class="center">
 
변경 후popup_order_packet.php
}
?>
</td>
<td class="center"><?= isset($withdrawnMaskLabels[$data['orderNo']]) ? $maskChar : gd_htmlspecialchars($data['receiverName']) ?></td>
<td class="center">
<?php if (isset($withdrawnMaskLabels[$data['orderNo']])) { ?>
<div><?= $maskChar ?></div>
<?php if (isset($withdrawnMaskLabels[$data['orderNo']])) { ?>
<?= $maskChar ?>
<?php } else if (empty($data['isDefaultMall']) === true) { ?>
(<?=gd_htmlspecialchars($data['receiverZonecode'])?>) <?=gd_htmlspecialchars($data['receiverAddressSub'])?> <?=gd_htmlspecialchars($data['receiverState'])?> <?=gd_htmlspecialchars($data['receiverCity'])?> <?=gd_htmlspecialchars($data['receiverAddress'])?> <?=gd_htmlspecialchars($data['receiverCountry'])?>
<?php } else { ?>
(<?=gd_htmlspecialchars($data['receiverZonecode'])?>) <?=gd_htmlspecialchars($data['receiverAddress'])?> <?=gd_htmlspecialchars($data['receiverAddressSub'])?>
<?php } ?>
</td>
<td class="center">
 
Admin/order/regular_order_view.php+4-4
변경 전regular_order_view.php
<th>휴대폰번호</th>
<td class="text-left">
<span><?= $applierInfo['applierCellPhone'] ?></span>
<?php /* member_sms 인자는 SMS 발송 기능용 → 분리 원본 키 사용 */ ?>
<a class="btn btn-sm btn-gray" onclick="member_sms('<?=gd_isset($applierInfo['memNo'])?>','<?= urlencode($applierInfo['applierName']); ?>','<?= isset($applierInfo['applierCellPhoneSeparationOrigin']) ? $applierInfo['applierCellPhoneSeparationOrigin'] : $applierInfo['applierCellPhone']; ?>', '<?=$applierInfo['smsFl']?>')">SMS 보내기</a>
</td>
</tr>
<tr>
<th>휴대폰번호</th>
<td class="text-left">
<span><?= $shippingInfo['shippingCellPhone'] ?></span>
<?php /* member_sms 인자는 SMS 발송 기능용 → 분리 원본 키 사용 */ ?>
<a class="btn btn-sm btn-gray" onclick="member_sms('<?=gd_isset($shippingInfo['memNo'])?>','<?= urlencode(isset($shippingInfo['shippingNameSeparationOrigin']) ? $shippingInfo['shippingNameSeparationOrigin'] : $shippingInfo['shippingName']); ?>','<?= isset($shippingInfo['shippingCellPhoneSeparationOrigin']) ? $shippingInfo['shippingCellPhoneSeparationOrigin'] : $shippingInfo['shippingCellPhone']; ?>', '<?=$shippingInfo['smsFl']?>')">SMS 보내기</a>
</td>
</tr>
<tr>
 
변경 후regular_order_view.php
<th>휴대폰번호</th>
<td class="text-left">
<span><?= $applierInfo['applierCellPhone'] ?></span>
<?php /* member_sms 인자는 SMS 발송 기능용 → 분리 원본(이름은 이스케이프 전 값) 사용 */ ?>
<a class="btn btn-sm btn-gray" onclick="member_sms('<?=gd_isset($applierInfo['memNo'])?>','<?= urlencode($applierNameRaw); ?>','<?= isset($applierInfo['applierCellPhoneSeparationOrigin']) ? $applierInfo['applierCellPhoneSeparationOrigin'] : $applierInfo['applierCellPhone']; ?>', '<?=$applierInfo['smsFl']?>')">SMS 보내기</a>
</td>
</tr>
<tr>
<th>휴대폰번호</th>
<td class="text-left">
<span><?= $shippingInfo['shippingCellPhone'] ?></span>
<?php /* member_sms 인자는 SMS 발송 기능용 → 분리 원본(이름은 이스케이프 전 값) 사용 */ ?>
<a class="btn btn-sm btn-gray" onclick="member_sms('<?=gd_isset($shippingInfo['memNo'])?>','<?= urlencode($shippingNameRaw); ?>','<?= isset($shippingInfo['shippingCellPhoneSeparationOrigin']) ? $shippingInfo['shippingCellPhoneSeparationOrigin'] : $shippingInfo['shippingCellPhone']; ?>', '<?=$shippingInfo['smsFl']?>')">SMS 보내기</a>
</td>
</tr>
<tr>
 
Component/Design/ReplaceCode.php+60-59
변경 전ReplaceCode.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\SkinUtils;
 
use Globals;
use Logger;
 
Logger::info(__METHOD__);
$this->replaceSrc['{rc_memNm}'] = [
'desc' => __('회원명'),
'val' => $replaceData['memNm'],
];
$this->replaceSrc['{rc_orderDt}'] = [
'desc' => __('주문일시'),
];
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => $replaceData['orderNm'],
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
];
$this->replaceSrc['{rc_receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => $replaceData['receiverNm'],
];
$this->replaceSrc['{rc_receiverZipcode}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => $replaceData['receiverZipcode'],
];
$this->replaceSrc['{rc_receiverZonecode}'] = [
'desc' => __('우편번호'),
'val' => $replaceData['receiverZonecode'],
];
$this->replaceSrc['{rc_receiverAddress}'] = [
'desc' => __('주소'),
'val' => $replaceData['receiverAddress'],
];
$this->replaceSrc['{rc_receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => $replaceData['receiverAddressSub'],
];
$this->replaceSrc['{rc_receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => $replaceData['receiverPhone'],
];
$this->replaceSrc['{rc_receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => $replaceData['receiverCellPhone'],
];
$this->replaceSrc['{rc_receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => $replaceData['receiverMemo'],
];
$this->replaceSrc['{rc_receiverPhonePrefix}'] = [
'desc' => __('전화번호 국가번호'),
'val' => $replaceData['receiverPhonePrefix'],
];
$this->replaceSrc['{rc_receiverCellPhonePrefix}'] = [
'desc' => __('휴대폰번호 국가번호'),
'val' => $replaceData['receiverCellPhonePrefix'],
];
$this->replaceSrc['{rc_receiverPhonePrefixCode}'] = [
'desc' => __('전화번호 국가코드'),
'val' => $replaceData['receiverPhonePrefixCode'],
];
$this->replaceSrc['{rc_receiverCellPhonePrefixCode}'] = [
'desc' => __('휴대폰번호 국가코드'),
'val' => $replaceData['receiverCellPhonePrefixCode'],
];
$this->replaceSrc['{rc_receiverCountry}'] = [
'desc' => __('수취인 국가'),
'val' => $replaceData['receiverCountry'],
];
$this->replaceSrc['{rc_receiverCity}'] = [
'desc' => __('수취인 도시'),
'val' => $replaceData['receiverCity'],
];
$this->replaceSrc['{rc_receiverState}'] = [
'desc' => __('수취인 주소'),
'val' => $replaceData['receiverState'],
];
$this->replaceSrc['{rc_totalDeliveryInsuranceFee}'] = [
'desc' => __('EMS 해외 총 보험료'),
];
$this->replaceSrc['{rc_receiverNmAdd}'] = [
'desc' => __('받는 사람'),
'val' => $replaceData['receiverNmAdd'],
];
$this->replaceSrc['{rc_receiverZipcodeAdd}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => $replaceData['receiverZipcodeAdd'],
];
$this->replaceSrc['{rc_receiverZonecodeAdd}'] = [
'desc' => __('우편번호'),
'val' => $replaceData['receiverZonecodeAdd'],
];
$this->replaceSrc['{rc_receiverAddressAdd}'] = [
'desc' => __('주소'),
'val' => $replaceData['receiverAddressAdd'],
];
$this->replaceSrc['{rc_receiverAddressSubAdd}'] = [
'desc' => __('상세주소'),
'val' => $replaceData['receiverAddressSubAdd'],
];
$this->replaceSrc['{rc_receiverPhoneAdd}'] = [
'desc' => __('전화번호'),
'val' => $replaceData['receiverPhoneAdd'],
];
$this->replaceSrc['{rc_receiverCellPhoneAdd}'] = [
'desc' => __('휴대폰번호'),
'val' => $replaceData['receiverCellPhoneAdd'],
];
$this->replaceSrc['{rc_orderMemoAdd}'] = [
'desc' => __('배송메시지'),
'val' => $replaceData['orderMemoAdd'],
];
$this->replaceSrc['{rc_expirationDate}'] = [
'desc' => __('입금만료일(yyyy-mm-dd hh:mm)'),
];
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => $replaceData['orderNm'],
];
$this->replaceSrc['{rc_bankSender}'] = [
'desc' => __('입금자명'),
'val' => $replaceData['bankSender'],
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
Logger::info(__METHOD__);
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => $replaceData['orderNm'],
];
$this->replaceSrc['{rc_bankSender}'] = [
'desc' => __('입금자명'),
'val' => $replaceData['bankSender'],
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
];
$this->replaceSrc['{rc_receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => $replaceData['receiverNm'],
];
$this->replaceSrc['{rc_receiverZipcode}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => $replaceData['receiverZipcode'],
];
$this->replaceSrc['{rc_receiverZonecode}'] = [
'desc' => __('우편번호'),
'val' => $replaceData['receiverZonecode'],
];
$this->replaceSrc['{rc_receiverAddress}'] = [
'desc' => __('주소'),
'val' => $replaceData['receiverAddress'],
];
$this->replaceSrc['{rc_receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => $replaceData['receiverAddressSub'],
];
$this->replaceSrc['{rc_receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => $replaceData['receiverPhone'],
];
$this->replaceSrc['{rc_receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => $replaceData['receiverCellPhone'],
];
$this->replaceSrc['{rc_receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => $replaceData['receiverMemo'],
];
$this->replaceSrc['{rc_receiverPhonePrefix}'] = [
'desc' => __('전화번호 국가번호'),
'val' => $replaceData['receiverPhonePrefix'],
];
$this->replaceSrc['{rc_receiverCellPhonePrefix}'] = [
'desc' => __('휴대폰번호 국가번호'),
'val' => $replaceData['receiverCellPhonePrefix'],
];
$this->replaceSrc['{rc_receiverPhonePrefixCode}'] = [
'desc' => __('전화번호 국가코드'),
'val' => $replaceData['receiverPhonePrefixCode'],
];
$this->replaceSrc['{rc_receiverCellPhonePrefixCode}'] = [
'desc' => __('휴대폰번호 국가코드'),
'val' => $replaceData['receiverCellPhonePrefixCode'],
];
$this->replaceSrc['{rc_receiverCountry}'] = [
'desc' => __('수취인 국가'),
'val' => $replaceData['receiverCountry'],
];
$this->replaceSrc['{rc_receiverCity}'] = [
'desc' => __('수취인 도시'),
'val' => $replaceData['receiverCity'],
];
$this->replaceSrc['{rc_receiverState}'] = [
'desc' => __('수취인 주소'),
'val' => $replaceData['receiverState'],
];
$this->replaceSrc['{rc_receiverNmAdd}'] = [
'desc' => __('받는 사람'),
'val' => $replaceData['receiverNmAdd'],
];
$this->replaceSrc['{rc_receiverZipcodeAdd}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => $replaceData['receiverZipcodeAdd'],
];
$this->replaceSrc['{rc_receiverZonecodeAdd}'] = [
'desc' => __('우편번호'),
'val' => $replaceData['receiverZonecodeAdd'],
];
$this->replaceSrc['{rc_receiverAddressAdd}'] = [
'desc' => __('주소'),
'val' => $replaceData['receiverAddressAdd'],
];
$this->replaceSrc['{rc_receiverAddressSubAdd}'] = [
'desc' => __('상세주소'),
'val' => $replaceData['receiverAddressSubAdd'],
];
$this->replaceSrc['{rc_receiverPhoneAdd}'] = [
'desc' => __('전화번호'),
'val' => $replaceData['receiverPhoneAdd'],
];
$this->replaceSrc['{rc_receiverCellPhoneAdd}'] = [
'desc' => __('휴대폰번호'),
'val' => $replaceData['receiverCellPhoneAdd'],
];
$this->replaceSrc['{rc_orderMemoAdd}'] = [
'desc' => __('배송메시지'),
'val' => $replaceData['orderMemoAdd'],
];
}
 
$this->replaceSrc['{deliveryDueDate}'] = [
'desc' => __('배송예정일'),
'val' => $replaceData['deliveryDueDate'],
];
$this->replaceSrc['{deliveryRound}'] = [
'desc' => __('배송회차'),
'val' => $replaceData['deliveryRound'],
];
$this->replaceSrc['{receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => $replaceData['receiverNm'],
];
$this->replaceSrc['{receiverAddress}'] = [
'desc' => __('주소'),
'val' => $replaceData['receiverAddress'],
];
$this->replaceSrc['{receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => $replaceData['receiverAddressSub'],
];
$this->replaceSrc['{receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => $replaceData['receiverMemo'],
];
$this->replaceSrc['{receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => $replaceData['receiverPhone'],
];
$this->replaceSrc['{receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => $replaceData['receiverCellPhone'],
];
$this->replaceSrc['{serviceView}'] = [
'desc' => __('고객센터 페이지'),
 
변경 후ReplaceCode.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\SkinUtils;
use Framework\Utility\StringUtils;
use Globals;
use Logger;
 
Logger::info(__METHOD__);
$this->replaceSrc['{rc_memNm}'] = [
'desc' => __('회원명'),
'val' => StringUtils::htmlSpecialChars($replaceData['memNm'] ?? ''),
];
$this->replaceSrc['{rc_orderDt}'] = [
'desc' => __('주문일시'),
];
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => StringUtils::htmlSpecialChars($replaceData['orderNm'] ?? ''),
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
];
$this->replaceSrc['{rc_receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverNm'] ?? ''),
];
$this->replaceSrc['{rc_receiverZipcode}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZipcode'] ?? ''),
];
$this->replaceSrc['{rc_receiverZonecode}'] = [
'desc' => __('우편번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZonecode'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddress}'] = [
'desc' => __('주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddress'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressSub'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhone'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhone'] ?? ''),
];
$this->replaceSrc['{rc_receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverMemo'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhonePrefix}'] = [
'desc' => __('전화번호 국가번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhonePrefix'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhonePrefix}'] = [
'desc' => __('휴대폰번호 국가번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhonePrefix'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhonePrefixCode}'] = [
'desc' => __('전화번호 국가코드'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhonePrefixCode'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhonePrefixCode}'] = [
'desc' => __('휴대폰번호 국가코드'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhonePrefixCode'] ?? ''),
];
$this->replaceSrc['{rc_receiverCountry}'] = [
'desc' => __('수취인 국가'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCountry'] ?? ''),
];
$this->replaceSrc['{rc_receiverCity}'] = [
'desc' => __('수취인 도시'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCity'] ?? ''),
];
$this->replaceSrc['{rc_receiverState}'] = [
'desc' => __('수취인 주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverState'] ?? ''),
];
$this->replaceSrc['{rc_totalDeliveryInsuranceFee}'] = [
'desc' => __('EMS 해외 총 보험료'),
];
$this->replaceSrc['{rc_receiverNmAdd}'] = [
'desc' => __('받는 사람'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverNmAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverZipcodeAdd}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZipcodeAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverZonecodeAdd}'] = [
'desc' => __('우편번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZonecodeAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressAdd}'] = [
'desc' => __('주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressSubAdd}'] = [
'desc' => __('상세주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressSubAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhoneAdd}'] = [
'desc' => __('전화번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhoneAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhoneAdd}'] = [
'desc' => __('휴대폰번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhoneAdd'] ?? ''),
];
$this->replaceSrc['{rc_orderMemoAdd}'] = [
'desc' => __('배송메시지'),
'val' => StringUtils::htmlSpecialChars($replaceData['orderMemoAdd'] ?? ''),
];
$this->replaceSrc['{rc_expirationDate}'] = [
'desc' => __('입금만료일(yyyy-mm-dd hh:mm)'),
];
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => StringUtils::htmlSpecialChars($replaceData['orderNm'] ?? ''),
];
$this->replaceSrc['{rc_bankSender}'] = [
'desc' => __('입금자명'),
'val' => StringUtils::htmlSpecialChars($replaceData['bankSender'] ?? ''),
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
Logger::info(__METHOD__);
$this->replaceSrc['{rc_orderNm}'] = [
'desc' => __('주문자명'),
'val' => StringUtils::htmlSpecialChars($replaceData['orderNm'] ?? ''),
];
$this->replaceSrc['{rc_bankSender}'] = [
'desc' => __('입금자명'),
'val' => StringUtils::htmlSpecialChars($replaceData['bankSender'] ?? ''),
];
$this->replaceSrc['{rc_orderNo}'] = [
'desc' => __('주문번호'),
];
$this->replaceSrc['{rc_receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverNm'] ?? ''),
];
$this->replaceSrc['{rc_receiverZipcode}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZipcode'] ?? ''),
];
$this->replaceSrc['{rc_receiverZonecode}'] = [
'desc' => __('우편번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZonecode'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddress}'] = [
'desc' => __('주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddress'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressSub'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhone'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhone'] ?? ''),
];
$this->replaceSrc['{rc_receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverMemo'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhonePrefix}'] = [
'desc' => __('전화번호 국가번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhonePrefix'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhonePrefix}'] = [
'desc' => __('휴대폰번호 국가번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhonePrefix'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhonePrefixCode}'] = [
'desc' => __('전화번호 국가코드'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhonePrefixCode'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhonePrefixCode}'] = [
'desc' => __('휴대폰번호 국가코드'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhonePrefixCode'] ?? ''),
];
$this->replaceSrc['{rc_receiverCountry}'] = [
'desc' => __('수취인 국가'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCountry'] ?? ''),
];
$this->replaceSrc['{rc_receiverCity}'] = [
'desc' => __('수취인 도시'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCity'] ?? ''),
];
$this->replaceSrc['{rc_receiverState}'] = [
'desc' => __('수취인 주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverState'] ?? ''),
];
$this->replaceSrc['{rc_receiverNmAdd}'] = [
'desc' => __('받는 사람'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverNmAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverZipcodeAdd}'] = [
'desc' => __('구 우편번호 (6자리)'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZipcodeAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverZonecodeAdd}'] = [
'desc' => __('우편번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverZonecodeAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressAdd}'] = [
'desc' => __('주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverAddressSubAdd}'] = [
'desc' => __('상세주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressSubAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverPhoneAdd}'] = [
'desc' => __('전화번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhoneAdd'] ?? ''),
];
$this->replaceSrc['{rc_receiverCellPhoneAdd}'] = [
'desc' => __('휴대폰번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhoneAdd'] ?? ''),
];
$this->replaceSrc['{rc_orderMemoAdd}'] = [
'desc' => __('배송메시지'),
'val' => StringUtils::htmlSpecialChars($replaceData['orderMemoAdd'] ?? ''),
];
}
 
$this->replaceSrc['{deliveryDueDate}'] = [
'desc' => __('배송예정일'),
'val' => $replaceData['deliveryDueDate'],
];
$this->replaceSrc['{deliveryRound}'] = [
'desc' => __('배송회차'),
'val' => $replaceData['deliveryRound'],
];
$this->replaceSrc['{receiverNm}'] = [
'desc' => __('받는 사람'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverNm'] ?? ''),
];
$this->replaceSrc['{receiverAddress}'] = [
'desc' => __('주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddress'] ?? ''),
];
$this->replaceSrc['{receiverAddressSub}'] = [
'desc' => __('상세주소'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverAddressSub'] ?? ''),
];
$this->replaceSrc['{receiverMemo}'] = [
'desc' => __('배송메시지'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverMemo'] ?? ''),
];
$this->replaceSrc['{receiverPhone}'] = [
'desc' => __('전화번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverPhone'] ?? ''),
];
$this->replaceSrc['{receiverCellPhone}'] = [
'desc' => __('휴대폰번호'),
'val' => StringUtils::htmlSpecialChars($replaceData['receiverCellPhone'] ?? ''),
];
$this->replaceSrc['{serviceView}'] = [
'desc' => __('고객센터 페이지'),
 
Component/Order/OrderNew.php+88-3
변경 전OrderNew.php
const SETTLE_KIND_LATER = 'pl'; // 기타. 네이버페이에서 "후불결제"로 결제된 경우
const SETTLE_KIND_FINTECH_UNKNOWN = 'fu'; // 페이코/네이버페이에서 상품상세에서 주문시 결제수단을 알수 없는 경우
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* @var array 영수증 신청 상태 (n : 신청 안함, r : 현금 영수증, t : 세금 계산서)
*/
// 주문 추가 필드 정보
$getData['addField'] = $this->getOrderAddFieldView($getData['addField']);
 
// 남기실 내용
$getData['orderMemo'] = nl2br($getData['orderMemo']);
 
// 무통장 입금 은행 정보
$getData['bankAccount'] = explode(STR_DIVISION, $getData['bankAccount']);
 
return $getData;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 배송지 정보 수정
*/
 
변경 후OrderNew.php
const SETTLE_KIND_LATER = 'pl'; // 기타. 네이버페이에서 "후불결제"로 결제된 경우
const SETTLE_KIND_FINTECH_UNKNOWN = 'fu'; // 페이코/네이버페이에서 상품상세에서 주문시 결제수단을 알수 없는 경우
 
/**
* @var array 뷰 출력 전 이스케이프가 필요한 주문자·수령자 정보 필드
*/
const RECEIVER_ESCAPE_FIELDS = [
'orderName',
'orderPhone',
'orderPhonePrefix',
'orderCellPhone',
'orderCellPhonePrefix',
'orderEmail',
'orderZonecode',
'orderZipcode',
'orderAddress',
'orderAddressSub',
'orderCity',
'orderState',
'orderCountry',
'receiverName',
'receiverNm', // 멀티 배송지 정보의 수령자명 키
'receiverPhone',
'receiverPhonePrefix',
'receiverCellPhone',
'receiverCellPhonePrefix',
'receiverZonecode',
'receiverZipcode',
'receiverAddress',
'receiverAddressSub',
'receiverCity',
'receiverState',
'receiverCountry',
'visitName',
'visitPhone',
'visitAddress',
'visitMemo', // 방문수령 메모
];
 
/**
* @var array 영수증 신청 상태 (n : 신청 안함, r : 현금 영수증, t : 세금 계산서)
*/
// 주문 추가 필드 정보
$getData['addField'] = $this->getOrderAddFieldView($getData['addField']);
 
 
 
 
// 무통장 입금 은행 정보
$getData['bankAccount'] = explode(STR_DIVISION, $getData['bankAccount']);
 
return $getData;
}
 
/**
* 수령자 정보 필드를 뷰 출력용으로 이스케이프
*
* 주문 데이터 전체는 goodsImage·gift.imageUrl 처럼 서버에서 생성한 이미지 태그를
* 포함하므로 통째로 이스케이프할 수 없다.
* 남기실 내용은 이스케이프 뒤에 nl2br 을 거쳐야 줄바꿈이 글자로 보이지 않으므로
* 조회 메서드가 아니라 이 메서드가 줄바꿈 변환까지 담당한다.
* 수령자명·연락처를 가공하는 처리(선물하기 마스킹 등)가 있으면 그 뒤에 호출해야 한다.
*
* @param array $data 주문 정보 (필드별 배열인 멀티 배송지 정보도 그대로 사용 가능)
*
* @return array 수령자 정보가 이스케이프된 주문 정보
*/
public function escapeReceiverInfo(array $data): array
{
foreach (static::RECEIVER_ESCAPE_FIELDS as $receiverField) {
if (isset($data[$receiverField])) {
$data[$receiverField] = StringUtils::htmlSpecialChars($data[$receiverField]);
}
}
 
// 마이페이지 주문상세의 방문 수령지 주소는 배송지별 중첩 배열로 출력된다
if (isset($data['visitDeliveryInfo']['address'])) {
$data['visitDeliveryInfo']['address'] = StringUtils::htmlSpecialChars($data['visitDeliveryInfo']['address']);
}
 
// 남기실 내용은 이스케이프 뒤에 줄바꿈을 변환한다
if (isset($data['orderMemo'])) {
$data['orderMemo'] = $this->nl2brRecursive(
StringUtils::htmlSpecialChars($data['orderMemo'])
);
}
 
return $data;
}
 
/**
* 문자열 또는 배열에 nl2br 을 적용
*
* @param array|string|null $value 변환할 값 (멀티 배송지는 필드별 배열로 들어온다)
*
* @return array|string 줄바꿈이 <br /> 로 변환된 값
*/
protected function nl2brRecursive($value)
{
if (is_array($value)) {
return array_map([$this, 'nl2brRecursive'], $value);
}
 
return nl2br($value ?? '');
}
 
/**
* 배송지 정보 수정
*/
 
Component/Order/Order.php+88-3
변경 전Order.php
const SETTLE_KIND_LATER = 'pl'; // 기타. 네이버페이에서 "후불결제"로 결제된 경우
const SETTLE_KIND_FINTECH_UNKNOWN = 'fu'; // 페이코/네이버페이에서 상품상세에서 주문시 결제수단을 알수 없는 경우
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* @var array 영수증 신청 상태 (n : 신청 안함, r : 현금 영수증, t : 세금 계산서)
*/
// 주문 추가 필드 정보
$getData['addField'] = $this->getOrderAddFieldView($getData['addField']);
 
// 남기실 내용
$getData['orderMemo'] = nl2br($getData['orderMemo']);
 
// 무통장 입금 은행 정보
$getData['bankAccount'] = explode(STR_DIVISION, $getData['bankAccount']);
 
return $getData;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 배송지 정보 수정
*/
 
변경 후Order.php
const SETTLE_KIND_LATER = 'pl'; // 기타. 네이버페이에서 "후불결제"로 결제된 경우
const SETTLE_KIND_FINTECH_UNKNOWN = 'fu'; // 페이코/네이버페이에서 상품상세에서 주문시 결제수단을 알수 없는 경우
 
/**
* @var array 뷰 출력 전 이스케이프가 필요한 주문자·수령자 정보 필드
*/
const RECEIVER_ESCAPE_FIELDS = [
'orderName',
'orderPhone',
'orderPhonePrefix',
'orderCellPhone',
'orderCellPhonePrefix',
'orderEmail',
'orderZonecode',
'orderZipcode',
'orderAddress',
'orderAddressSub',
'orderCity',
'orderState',
'orderCountry',
'receiverName',
'receiverNm', // 멀티 배송지 정보의 수령자명 키
'receiverPhone',
'receiverPhonePrefix',
'receiverCellPhone',
'receiverCellPhonePrefix',
'receiverZonecode',
'receiverZipcode',
'receiverAddress',
'receiverAddressSub',
'receiverCity',
'receiverState',
'receiverCountry',
'visitName',
'visitPhone',
'visitAddress',
'visitMemo', // 방문수령 메모
];
 
/**
* @var array 영수증 신청 상태 (n : 신청 안함, r : 현금 영수증, t : 세금 계산서)
*/
// 주문 추가 필드 정보
$getData['addField'] = $this->getOrderAddFieldView($getData['addField']);
 
 
 
 
// 무통장 입금 은행 정보
$getData['bankAccount'] = explode(STR_DIVISION, $getData['bankAccount']);
 
return $getData;
}
 
/**
* 수령자 정보 필드를 뷰 출력용으로 이스케이프
*
* 주문 데이터 전체는 goodsImage·gift.imageUrl 처럼 서버에서 생성한 이미지 태그를
* 포함하므로 통째로 이스케이프할 수 없다.
* 남기실 내용은 이스케이프 뒤에 nl2br 을 거쳐야 줄바꿈이 글자로 보이지 않으므로
* 조회 메서드가 아니라 이 메서드가 줄바꿈 변환까지 담당한다.
* 수령자명·연락처를 가공하는 처리(선물하기 마스킹 등)가 있으면 그 뒤에 호출해야 한다.
*
* @param array $data 주문 정보 (필드별 배열인 멀티 배송지 정보도 그대로 사용 가능)
*
* @return array 수령자 정보가 이스케이프된 주문 정보
*/
public function escapeReceiverInfo(array $data): array
{
foreach (static::RECEIVER_ESCAPE_FIELDS as $receiverField) {
if (isset($data[$receiverField])) {
$data[$receiverField] = StringUtils::htmlSpecialChars($data[$receiverField]);
}
}
 
// 마이페이지 주문상세의 방문 수령지 주소는 배송지별 중첩 배열로 출력된다
if (isset($data['visitDeliveryInfo']['address'])) {
$data['visitDeliveryInfo']['address'] = StringUtils::htmlSpecialChars($data['visitDeliveryInfo']['address']);
}
 
// 남기실 내용은 이스케이프 뒤에 줄바꿈을 변환한다
if (isset($data['orderMemo'])) {
$data['orderMemo'] = $this->nl2brRecursive(
StringUtils::htmlSpecialChars($data['orderMemo'])
);
}
 
return $data;
}
 
/**
* 문자열 또는 배열에 nl2br 을 적용
*
* @param array|string|null $value 변환할 값 (멀티 배송지는 필드별 배열로 들어온다)
*
* @return array|string 줄바꿈이 <br /> 로 변환된 값
*/
protected function nl2brRecursive($value)
{
if (is_array($value)) {
return array_map([$this, 'nl2brRecursive'], $value);
}
 
return nl2br($value ?? '');
}
 
/**
* 배송지 정보 수정
*/
 
Component/RegularDelivery/RegularOrder/RegularOrderShippingAddress.php+1-1
변경 전RegularOrderShippingAddress.php
'shippingName' => htmlspecialchars($address['shippingName'], ENT_QUOTES),
'shippingPhone' => $address['shippingPhone'],
'shippingCellPhone' => $address['shippingCellPhone'],
'shippingZoneCode' => $address['shippingZonecode'],
'shippingAddress' => htmlspecialchars($address['shippingAddress'], ENT_QUOTES),
'shippingAddressSub' => htmlspecialchars($address['shippingAddressSub'], ENT_QUOTES),
'shippingMessage' => htmlspecialchars($address['shippingMessage'], ENT_QUOTES),
 
변경 후RegularOrderShippingAddress.php
'shippingName' => htmlspecialchars($address['shippingName'], ENT_QUOTES),
'shippingPhone' => $address['shippingPhone'],
'shippingCellPhone' => $address['shippingCellPhone'],
'shippingZoneCode' => htmlspecialchars($address['shippingZonecode'], ENT_QUOTES),
'shippingAddress' => htmlspecialchars($address['shippingAddress'], ENT_QUOTES),
'shippingAddressSub' => htmlspecialchars($address['shippingAddressSub'], ENT_QUOTES),
'shippingMessage' => htmlspecialchars($address['shippingMessage'], ENT_QUOTES),
 
Controller/Admin/Order/LayerReceiverInfoController.php+4-1
변경 전LayerReceiverInfoController.php
use Exception;
use Request;
use App;
 
 
/**
* Class LayerReceiverInfoController
// 모듈 설정
$order = App::load(\Component\Order\OrderAdmin::class);
$getData = $order->getOrderView($postValue['orderNo']);
$this->setData('data', $getData);
 
 
// 국가데이터 가져오기
$countriesCode = $order->getUsableCountriesList($getData['mallSno']);
 
변경 후LayerReceiverInfoController.php
use Exception;
use Request;
use App;
use Framework\Utility\StringUtils;
 
/**
* Class LayerReceiverInfoController
// 모듈 설정
$order = App::load(\Component\Order\OrderAdmin::class);
$getData = $order->getOrderView($postValue['orderNo']);
 
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('data', StringUtils::htmlSpecialChars($getData));
// 국가데이터 가져오기
$countriesCode = $order->getUsableCountriesList($getData['mallSno']);
 
Controller/Admin/Order/OrderViewController.php+26-2
변경 전OrderViewController.php
use Origin\Enum\Privacy\Withdrawn\WithdrawnSection;
use Session;
use Request;
 
 
/**
* 주문 상세 페이지
}
}
 
// 네이버페이 메모
if ($naverPayMemo) {
$this->setData('naverPayMemo', $naverPayMemo);
}
 
if($naverpayIndividualCustomUniqueCode) {
// 템플릿 데이터 설정
$this->setData('order', $order);
if ($data['orderChannelFl'] == 'naverpay') {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$this->setData('data', gd_isset($data));
} else {
$this->setData('data', gd_htmlspecialchars(gd_isset($data)));
}
 
 
 
// 주문자명: 탈퇴/휴면=회원유형 라벨, 일반 회원=null
$this->setData('orderMaskLabel', $orderMaskLabel);
// 주소는 우편번호·기본·상세·시도·시군구가 각각 '-' 로 치환돼 한 칸에 모이면 하이픈이 반복된다.
 
변경 후OrderViewController.php
use Origin\Enum\Privacy\Withdrawn\WithdrawnSection;
use Session;
use Request;
use Framework\Utility\StringUtils;
 
/**
* 주문 상세 페이지
}
}
 
// 네이버페이 메모 (네이버페이 주문은 남기실 내용 대신 이 값을 출력한다)
if ($naverPayMemo) {
$this->setData('naverPayMemo', StringUtils::htmlSpecialChars($naverPayMemo));
}
 
if($naverpayIndividualCustomUniqueCode) {
// 템플릿 데이터 설정
$this->setData('order', $order);
if ($data['orderChannelFl'] == 'naverpay') {
// 네이버페이는 goods 항목의 orderStatusStr 등에 서버가 생성한 HTML 이 포함되어 전체 이스케이프가 불가하다
$receiverEscapeFields = $order::RECEIVER_ESCAPE_FIELDS;
foreach ($receiverEscapeFields as $receiverEscapeField) {
if (isset($data[$receiverEscapeField])) {
$data[$receiverEscapeField] = StringUtils::htmlSpecialChars($data[$receiverEscapeField]);
}
}
// 방문 수령지 주소는 배송지별 중첩 배열이라 필드 목록 순회에 걸리지 않는다
if (isset($data['visitDeliveryInfo']['address'])) {
$data['visitDeliveryInfo']['address'] = StringUtils::htmlSpecialChars($data['visitDeliveryInfo']['address']);
}
if (!empty($data['multiShippingList'])) {
foreach ($data['multiShippingList'] as $multiShippingKey => $multiShippingData) {
foreach ($receiverEscapeFields as $receiverEscapeField) {
if (isset($multiShippingData[$receiverEscapeField])) {
$data['multiShippingList'][$multiShippingKey][$receiverEscapeField] = StringUtils::htmlSpecialChars($multiShippingData[$receiverEscapeField]);
}
}
}
}
$this->setData('data', gd_isset($data));
} else {
$this->setData('data', gd_htmlspecialchars(gd_isset($data)));
}
if (!empty($withdrawnMembersPersonalData)) {
$withdrawnMembersPersonalData = StringUtils::htmlSpecialChars($withdrawnMembersPersonalData);
}
// 주문자명: 탈퇴/휴면=회원유형 라벨, 일반 회원=null
$this->setData('orderMaskLabel', $orderMaskLabel);
// 주소는 우편번호·기본·상세·시도·시군구가 각각 '-' 로 치환돼 한 칸에 모이면 하이픈이 반복된다.
 
Controller/Admin/Order/RegularOrderViewController.php+6-2
변경 전RegularOrderViewController.php
// 카드번호는 표시 전용이라(폼·파라미터 미사용) 원본을 남기지 않고 덮는다
$this->setData('cardNo', ColumnMaskingService::DEFAULT_MASK);
}
$this->setData('applierInfo', $applierInfo);
$this->setData('shippingInfo', $shippingInfo);
// 신청자명(1A): 탈퇴/휴면이면 회원 유형 라벨로 표시(뷰: applierMaskLabel ?: 기존식). 일반 회원=null.
$this->setData('applierMaskLabel', $applierMaskLabel);
// 배송지처럼 구성요소가 한 칸에 모이는 항목은 뷰가 칸 전체를 이 값 하나로 대체한다
$this->setData('maskChar', ColumnMaskingService::DEFAULT_MASK);
 
 
 
 
 
// 정기결제 신청서 상담 및 관리자 메모 정보
$consultMemo = $regularOrderAdminDetail->getRegularOrderConsultMemo($applyNo);
$this->setData('consultMemo', $consultMemo);
 
변경 후RegularOrderViewController.php
// 카드번호는 표시 전용이라(폼·파라미터 미사용) 원본을 남기지 않고 덮는다
$this->setData('cardNo', ColumnMaskingService::DEFAULT_MASK);
}
$this->setData('applierInfo', gd_htmlspecialchars($applierInfo));
$this->setData('shippingInfo', gd_htmlspecialchars($shippingInfo));
// 신청자명(1A): 탈퇴/휴면이면 회원 유형 라벨로 표시(뷰: applierMaskLabel ?: 기존식). 일반 회원=null.
$this->setData('applierMaskLabel', $applierMaskLabel);
// 배송지처럼 구성요소가 한 칸에 모이는 항목은 뷰가 칸 전체를 이 값 하나로 대체한다
$this->setData('maskChar', ColumnMaskingService::DEFAULT_MASK);
 
// SMS 발송 팝업은 urlencode 로 전달하며, 수신자명이 회원명과 다르면 비회원으로 분류되므로 이스케이프 전 값을 사용한다
$this->setData('applierNameRaw', $regularOrderShippingInfo['applierInfo']['applierName'] ?? '');
$this->setData('shippingNameRaw', $regularOrderShippingInfo['shippingInfo']['shippingName'] ?? '');
 
// 정기결제 신청서 상담 및 관리자 메모 정보
$consultMemo = $regularOrderAdminDetail->getRegularOrderConsultMemo($applyNo);
$this->setData('consultMemo', $consultMemo);
 
Controller/Admin/Share/LayerShippingAddressController.php+4-1
변경 전LayerShippingAddressController.php
use Exception;
use App;
use Framework\Debug\Exception\AlertCloseException;
 
 
/**
* Class LayerShippingAddress
 
$order = App::load('\\Component\\Order\\Order');
$deliveryAddress = $order->getShippingAddressList($getValue['page'], $getValue['pageNum'], $getValue['memNo']);
$this->setData('deliveryAddress', $deliveryAddress);
 
 
 
// 페이지
$pageObj = \App::load('\\Component\\Page\\Page');
 
변경 후LayerShippingAddressController.php
use Exception;
use App;
use Framework\Debug\Exception\AlertCloseException;
use Framework\Utility\StringUtils;
 
/**
* Class LayerShippingAddress
 
$order = App::load('\\Component\\Order\\Order');
$deliveryAddress = $order->getShippingAddressList($getValue['page'], $getValue['pageNum'], $getValue['memNo']);
 
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('deliveryAddress', StringUtils::htmlSpecialChars($deliveryAddress));
 
// 페이지
$pageObj = \App::load('\\Component\\Page\\Page');
 
Controller/Front/Mypage/OrderViewController.php+14-7
변경 전OrderViewController.php
$multiOrderInfo = null;
if ($orderData['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo(Request::get()->get('orderNo'));
$this->setData('multiOrderInfo', $multiOrderInfo);
$this->setData('isUseMultiShipping', true);
}
 
// 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출)
$orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData);
 
$this->setData('orderData', $orderData);
 
 
 
 
 
 
$orderData = $orderData[0];
// 회원할인에 회원 배송비 할인 추가
$orderData['totalMemberDcPrice'] += $orderData['totalMemberDeliveryDcPrice'];
if ($isPresentOrder) {
$presentOrderAdmin = \App::getInstance(\Component\Present\Order\PresentOrderAdmin::class);
$presentOrderAdmin->maskReceiverInfo($orderData, $multiOrderInfo);
if (!empty($multiOrderInfo)) {
$this->setData('multiOrderInfo', $multiOrderInfo);
}
}
 
 
 
 
 
 
$this->setData('orderInfo', $orderData);
$this->setData('guest', Request::get()->get('guest'));
 
 
변경 후OrderViewController.php
$multiOrderInfo = null;
if ($orderData['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo(Request::get()->get('orderNo'));
 
$this->setData('isUseMultiShipping', true);
}
 
// 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출)
$orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData);
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$escapedOrderData = [];
foreach ($orderData as $orderKey => $orderRow) {
$escapedOrderData[$orderKey] = $order->escapeReceiverInfo($orderRow);
}
 
$this->setData('orderData', $escapedOrderData);
$orderData = $orderData[0];
// 회원할인에 회원 배송비 할인 추가
$orderData['totalMemberDcPrice'] += $orderData['totalMemberDeliveryDcPrice'];
if ($isPresentOrder) {
$presentOrderAdmin = \App::getInstance(\Component\Present\Order\PresentOrderAdmin::class);
$presentOrderAdmin->maskReceiverInfo($orderData, $multiOrderInfo);
 
 
 
 
}
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$orderData = $order->escapeReceiverInfo($orderData);
if (isset($multiOrderInfo)) {
$this->setData('multiOrderInfo', $order->escapeReceiverInfo($multiOrderInfo));
}
 
$this->setData('orderInfo', $orderData);
$this->setData('guest', Request::get()->get('guest'));
 
 
Controller/Front/Mypage/RegularDeliveryShippingAddressChangeController.php+2-1
변경 전RegularDeliveryShippingAddressChangeController.php
$pager = $shippingAddressList['pager'];
 
// 데이터 뷰에 전달
$this->setData('shippingAddressList', $shippingList);
 
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
}
}
 
변경 후RegularDeliveryShippingAddressChangeController.php
$pager = $shippingAddressList['pager'];
 
// 데이터 뷰에 전달
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('shippingAddressList', \Framework\Utility\StringUtils::htmlSpecialChars($shippingList));
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
}
}
 
Controller/Front/Mypage/ShippingController.php+6-2
변경 전ShippingController.php
use Framework\Utility\DateTimeUtils;
use App;
use Request;
 
 
/**
* Class ShippingController
$pageNo = Request::get()->get('page', 1);
 
$shippingList = $order->getShippingAddressList($pageNo, 10);
$this->setData('shippingList', $shippingList);
 
 
 
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
 
 
 
// 데이터 뷰에 전달
$this->setData('regularShippingList', $regularShippingList['list']);
 
$this->setData('totalShippingAddressList', $regularShippingList['pager']->recode['total'] ?? 0);
 
// 정기배송지 페이징 처리
 
변경 후ShippingController.php
use Framework\Utility\DateTimeUtils;
use App;
use Request;
use Framework\Utility\StringUtils;
 
/**
* Class ShippingController
$pageNo = Request::get()->get('page', 1);
 
$shippingList = $order->getShippingAddressList($pageNo, 10);
 
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('shippingList', StringUtils::htmlSpecialChars($shippingList));
 
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
 
 
 
// 데이터 뷰에 전달
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('regularShippingList', StringUtils::htmlSpecialChars($regularShippingList['list']));
$this->setData('totalShippingAddressList', $regularShippingList['pager']->recode['total'] ?? 0);
 
// 정기배송지 페이징 처리
 
Controller/Front/Order/LayerRegularOrderShippingAddressController.php+3-1
변경 전LayerRegularOrderShippingAddressController.php
use Component\Member\Util\MemberUtil;
use Controller\Front\Controller;
use Exception;
 
use Request;
 
class LayerRegularOrderShippingAddressController extends Controller
$shippingList = $shippingInfo['list'];
$pager = $shippingInfo['pager'];
 
$this->setData('deliveryAddress', $shippingList);
 
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
} catch (Exception $e) {
\Logger::channel('regularOrder')->error(__CLASS__ . 'Regular order shipping address 조회 실패', $e->getMessage());
 
변경 후LayerRegularOrderShippingAddressController.php
use Component\Member\Util\MemberUtil;
use Controller\Front\Controller;
use Exception;
use Framework\Utility\StringUtils;
use Request;
 
class LayerRegularOrderShippingAddressController extends Controller
$shippingList = $shippingInfo['list'];
$pager = $shippingInfo['pager'];
 
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('deliveryAddress', StringUtils::htmlSpecialChars($shippingList));
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
} catch (Exception $e) {
\Logger::channel('regularOrder')->error(__CLASS__ . 'Regular order shipping address 조회 실패', $e->getMessage());
 
Controller/Front/Order/LayerRegularOrderShippingAddressRegistController.php+3-1
변경 전LayerRegularOrderShippingAddressRegistController.php
use Controller\Front\Controller;
use Request;
use Component\Member\Util\MemberUtil;
 
 
class LayerRegularOrderShippingAddressRegistController extends Controller
{
// 수정 화면
$regularOrderShippingAddress->hasActiveOrPausedOrdersUsingShippingAddress($shippingAddressSno, $memNo); // 이미 등록된 배송지로 활성화 또는 일시정지된 정기배송이 있는지 확인
$shippingInfo = $regularOrderShippingAddress->getShippingAddressInfo($shippingAddressSno, $memNo);
$this->setData('data', $shippingInfo);
 
$this->setData('mode', 'shipping_modify');
} else {
// 등록 화면
 
변경 후LayerRegularOrderShippingAddressRegistController.php
use Controller\Front\Controller;
use Request;
use Component\Member\Util\MemberUtil;
use Framework\Utility\StringUtils;
 
class LayerRegularOrderShippingAddressRegistController extends Controller
{
// 수정 화면
$regularOrderShippingAddress->hasActiveOrPausedOrdersUsingShippingAddress($shippingAddressSno, $memNo); // 이미 등록된 배송지로 활성화 또는 일시정지된 정기배송이 있는지 확인
$shippingInfo = $regularOrderShippingAddress->getShippingAddressInfo($shippingAddressSno, $memNo);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('data', StringUtils::htmlSpecialChars($shippingInfo));
$this->setData('mode', 'shipping_modify');
} else {
// 등록 화면
 
Controller/Front/Order/LayerShippingAddressController.php+3-1
변경 전LayerShippingAddressController.php
use Session;
use Request;
use Exception;
 
/**
* Class LayerDeliveryAddress
$order = App::load(\Component\Order\Order::class);
$deliveryAddress = $order->getShippingAddressList($page);
$this->setData('deliveryAddress', $deliveryAddress);
 
$this->setData('shippingNo', \Request::get()->get('shippingNo'));
// 페이징 처리
 
변경 후LayerShippingAddressController.php
use Session;
use Request;
use Exception;
use Framework\Utility\StringUtils;
/**
* Class LayerDeliveryAddress
$order = App::load(\Component\Order\Order::class);
$deliveryAddress = $order->getShippingAddressList($page);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('deliveryAddress', StringUtils::htmlSpecialChars($deliveryAddress));
$this->setData('shippingNo', \Request::get()->get('shippingNo'));
// 페이징 처리
 
Controller/Front/Order/LayerShippingAddressRegistController.php+4-1
변경 전LayerShippingAddressRegistController.php
use Exception;
use Framework\Debug\Exception\WarningException;
use Request;
 
/**
* Class LayerDeliveryAddress
if (Request::get()->has('sno') && is_numeric(Request::get()->get('sno'))) {
$data = $order->getShippingAddressData(Request::get()->get('sno'));
$this->setData('data', $data);
 
 
$this->setData('mode', 'shipping_modify');
} else {
if (empty($order->getShippingDefaultFlYn()) === true) {
 
변경 후LayerShippingAddressRegistController.php
use Exception;
use Framework\Debug\Exception\WarningException;
use Request;
use Framework\Utility\StringUtils;
/**
* Class LayerDeliveryAddress
if (Request::get()->has('sno') && is_numeric(Request::get()->get('sno'))) {
$data = $order->getShippingAddressData(Request::get()->get('sno'));
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('data', StringUtils::htmlSpecialChars($data));
$this->setData('mode', 'shipping_modify');
} else {
if (empty($order->getShippingDefaultFlYn()) === true) {
 
Controller/Front/Order/OrderEndController.php+8-1
변경 전OrderEndController.php
 
if ($orderInfo['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo($orderNo);
$this->setData('multiOrderInfo', $multiOrderInfo);
 
 
}
 
// 수취인 배송 국가명
}
 
$this->setData('naverCommonInflowScript', gd_isset($naverCommonInflowScript));
 
 
 
 
 
$this->setData('orderInfo', gd_isset($orderInfo));
 
} catch (\Exception $e) {
 
변경 후OrderEndController.php
 
if ($orderInfo['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo($orderNo);
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$this->setData('multiOrderInfo', $order->escapeReceiverInfo($multiOrderInfo));
}
 
// 수취인 배송 국가명
}
 
$this->setData('naverCommonInflowScript', gd_isset($naverCommonInflowScript));
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
if (!empty($orderInfo)) {
$orderInfo = $order->escapeReceiverInfo($orderInfo);
}
$this->setData('orderInfo', gd_isset($orderInfo));
 
} catch (\Exception $e) {
 
Controller/Front/Order/RegularOrderController.php+3-1
변경 전RegularOrderController.php
use Component\Cart\RegularOrderCart;
use Controller\Front\Controller;
use Framework\Debug\Exception\AlertRedirectException;
 
use Request;
 
class RegularOrderController extends Controller
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
$memNo = \Session::get('member.memNo');
$defaultShippingInfo = $regularOrderShippingAddress->findDefaultShippingInfo($memNo);
$this->setData('shippingInfo', $defaultShippingInfo);
 
 
// pg 정보
$pgConf = gd_policy('autoPg.pgName');
 
변경 후RegularOrderController.php
use Component\Cart\RegularOrderCart;
use Controller\Front\Controller;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Utility\StringUtils;
use Request;
 
class RegularOrderController extends Controller
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
$memNo = \Session::get('member.memNo');
$defaultShippingInfo = $regularOrderShippingAddress->findDefaultShippingInfo($memNo);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('shippingInfo', StringUtils::htmlSpecialChars($defaultShippingInfo));
 
// pg 정보
$pgConf = gd_policy('autoPg.pgName');
 
Controller/Front/Order/RegularOrderEndController.php+3-1
변경 전RegularOrderEndController.php
namespace Bundle\Controller\Front\Order;
 
use Controller\Front\Controller;
 
 
class RegularOrderEndController extends Controller
{
$orderInfo = $regularOrder->getOrderEndInfo($applyGroupNo);
}
 
$this->setData('orderInfo', $orderInfo['orderInfo']);
 
$this->setData('orderGoodsName', $orderInfo['orderGoodsName']);
$this->setData('cardName', $orderInfo['cardName']);
$this->setData('cardNo', $orderInfo['cardNo']);
 
변경 후RegularOrderEndController.php
namespace Bundle\Controller\Front\Order;
 
use Controller\Front\Controller;
use Framework\Utility\StringUtils;
 
class RegularOrderEndController extends Controller
{
$orderInfo = $regularOrder->getOrderEndInfo($applyGroupNo);
}
 
// XSS 방지 - 배송지 뷰 출력용 이스케이프 (신청 정보는 텍스트 컬럼만 조회해 전체 이스케이프 가능)
$this->setData('orderInfo', StringUtils::htmlSpecialChars($orderInfo['orderInfo'] ?? []));
$this->setData('orderGoodsName', $orderInfo['orderGoodsName']);
$this->setData('cardName', $orderInfo['cardName']);
$this->setData('cardNo', $orderInfo['cardNo']);
 
Controller/Mobile/Mypage/LayerShippingAddressRegistController.php+4-1
변경 전LayerShippingAddressRegistController.php
use Component\Member\Util\MemberUtil;
use Exception;
use Request;
 
/**
* 배송지 등록 레이어
if (Request::get()->has('sno') && is_numeric(Request::get()->get('sno'))) {
$data = $order->getShippingAddressData(Request::get()->get('sno'));
$this->setData('data', $data);
 
 
$this->setData('mode', 'shipping_modify');
} else {
if (empty($order->getShippingDefaultFlYn()) === true) {
 
변경 후LayerShippingAddressRegistController.php
use Component\Member\Util\MemberUtil;
use Exception;
use Request;
use Framework\Utility\StringUtils;
/**
* 배송지 등록 레이어
if (Request::get()->has('sno') && is_numeric(Request::get()->get('sno'))) {
$data = $order->getShippingAddressData(Request::get()->get('sno'));
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('data', StringUtils::htmlSpecialChars($data));
$this->setData('mode', 'shipping_modify');
} else {
if (empty($order->getShippingDefaultFlYn()) === true) {
 
Controller/Mobile/Mypage/OrderViewController.php+15-8
변경 전OrderViewController.php
$multiOrderInfo = null;
if ($orderData['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo(Request::get()->get('orderNo'));
$this->setData('multiOrderInfo', $multiOrderInfo);
$this->setData('isUseMultiShipping', true);
}
 
// 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출)
$orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData);
 
$this->setData('orderData', $orderData);
 
 
 
 
 
 
 
$ordersByRegisterDay = [];
foreach ($orderData as $index => $item) {
if (empty($item['regDt']) === false) {
$ordersByRegisterDay[DateTimeUtils::dateFormat('Y-m-d', $item['regDt'])][] = $item;
$ordersByRegisterDay[DateTimeUtils::dateFormat('Y-m-d', $item['regDt'])][$index]['orderGoodsCnt'] = gd_count($item['goods']);
if ($isPresentOrder) {
$presentOrderAdmin = \App::getInstance(\Component\Present\Order\PresentOrderAdmin::class);
$presentOrderAdmin->maskReceiverInfo($orderData, $multiOrderInfo);
if (!empty($multiOrderInfo)) {
$this->setData('multiOrderInfo', $multiOrderInfo);
}
}
 
 
 
 
 
 
$this->setData('orderInfo', $orderData);
$this->setData('guest', Request::get()->get('guest'));
 
 
변경 후OrderViewController.php
$multiOrderInfo = null;
if ($orderData['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo(Request::get()->get('orderNo'));
 
$this->setData('isUseMultiShipping', true);
}
 
// 우체국 반품 수거 반송추적 — 보충 조회 1회로 행에 서버 조립 URL 주입 (송장 원문 미노출)
$orderData = \App::getInstance(ReturnInvoice::class)->assignTraceUrls($orderData);
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$escapedOrderData = [];
foreach ($orderData as $orderKey => $orderRow) {
$escapedOrderData[$orderKey] = $order->escapeReceiverInfo($orderRow);
}
 
$this->setData('orderData', $escapedOrderData);
 
$ordersByRegisterDay = [];
foreach ($escapedOrderData as $index => $item) {
if (empty($item['regDt']) === false) {
$ordersByRegisterDay[DateTimeUtils::dateFormat('Y-m-d', $item['regDt'])][] = $item;
$ordersByRegisterDay[DateTimeUtils::dateFormat('Y-m-d', $item['regDt'])][$index]['orderGoodsCnt'] = gd_count($item['goods']);
if ($isPresentOrder) {
$presentOrderAdmin = \App::getInstance(\Component\Present\Order\PresentOrderAdmin::class);
$presentOrderAdmin->maskReceiverInfo($orderData, $multiOrderInfo);
 
 
 
 
}
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$orderData = $order->escapeReceiverInfo($orderData);
if (isset($multiOrderInfo)) {
$this->setData('multiOrderInfo', $order->escapeReceiverInfo($multiOrderInfo));
}
 
$this->setData('orderInfo', $orderData);
$this->setData('guest', Request::get()->get('guest'));
 
 
Controller/Mobile/Mypage/ShippingController.php+4-1
변경 전ShippingController.php
use Framework\Utility\DateTimeUtils;
use App;
use Request;
 
/**
* Class ShippingController
$pageNo = Request::get()->get('page', 1);
$shippingList = $order->getShippingAddressList($pageNo, 100);
$this->setData('shippingList', $shippingList);
 
 
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
 
변경 후ShippingController.php
use Framework\Utility\DateTimeUtils;
use App;
use Request;
use Framework\Utility\StringUtils;
/**
* Class ShippingController
$pageNo = Request::get()->get('page', 1);
$shippingList = $order->getShippingAddressList($pageNo, 100);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('shippingList', StringUtils::htmlSpecialChars($shippingList));
$regularOrderShippingAddress = \App::getInstance(\Component\RegularDelivery\RegularOrder\RegularOrderShippingAddress::class);
 
Controller/Mobile/Order/LayerRegularOrderShippingAddressController.php+3-1
변경 전LayerRegularOrderShippingAddressController.php
use Component\Member\Util\MemberUtil;
use Controller\Mobile\Controller;
use Exception;
 
use Request;
 
class LayerRegularOrderShippingAddressController extends Controller
$shippingList = $shippingInfo['list'];
$pager = $shippingInfo['pager'];
 
$this->setData('deliveryAddress', $shippingList);
 
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
} catch (Exception $e) {
\Logger::channel('regularOrder')->error(__CLASS__ . 'Regular order shipping address 조회 실패', $e->getMessage());
 
변경 후LayerRegularOrderShippingAddressController.php
use Component\Member\Util\MemberUtil;
use Controller\Mobile\Controller;
use Exception;
use Framework\Utility\StringUtils;
use Request;
 
class LayerRegularOrderShippingAddressController extends Controller
$shippingList = $shippingInfo['list'];
$pager = $shippingInfo['pager'];
 
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('deliveryAddress', StringUtils::htmlSpecialChars($shippingList));
$this->setData('pagination', $pager->getPage('goPageOnDeliveryAddress(\'PAGELINK\');'));
} catch (Exception $e) {
\Logger::channel('regularOrder')->error(__CLASS__ . 'Regular order shipping address 조회 실패', $e->getMessage());
 
Controller/Mobile/Order/LayerRegularOrderShippingAddressRegistController.php+3-1
변경 전LayerRegularOrderShippingAddressRegistController.php
use Controller\Mobile\Controller;
use Request;
use Component\Member\Util\MemberUtil;
 
 
class LayerRegularOrderShippingAddressRegistController extends Controller
{
// 수정 화면
$regularOrderShippingAddress->hasActiveOrPausedOrdersUsingShippingAddress($shippingAddressSno, $memNo); // 이미 등록된 배송지로 활성화 또는 일시정지된 정기배송이 있는지 확인
$shippingInfo = $regularOrderShippingAddress->getShippingAddressInfo($shippingAddressSno, $memNo);
$this->setData('data', $shippingInfo);
 
$this->setData('mode', 'shipping_modify');
} else {
// 등록 화면
 
변경 후LayerRegularOrderShippingAddressRegistController.php
use Controller\Mobile\Controller;
use Request;
use Component\Member\Util\MemberUtil;
use Framework\Utility\StringUtils;
 
class LayerRegularOrderShippingAddressRegistController extends Controller
{
// 수정 화면
$regularOrderShippingAddress->hasActiveOrPausedOrdersUsingShippingAddress($shippingAddressSno, $memNo); // 이미 등록된 배송지로 활성화 또는 일시정지된 정기배송이 있는지 확인
$shippingInfo = $regularOrderShippingAddress->getShippingAddressInfo($shippingAddressSno, $memNo);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('data', StringUtils::htmlSpecialChars($shippingInfo));
$this->setData('mode', 'shipping_modify');
} else {
// 등록 화면
 
Controller/Mobile/Order/OrderController.php+3-1
변경 전OrderController.php
if((int)Session::get('member.memNo') > 0){
// 기본 배송지 정보 가져오기
$defaultShippingAddress = $order->getDefaultShippingAddress();
$this->setData('basiceShippingAddress', $defaultShippingAddress);
 
 
$this->setData('shippingDefaultFl', $order->getShippingDefaultFlYn());
}
 
 
변경 후OrderController.php
if((int)Session::get('member.memNo') > 0){
// 기본 배송지 정보 가져오기
$defaultShippingAddress = $order->getDefaultShippingAddress();
 
// XSS 방지 - 뷰 출력용 이스케이프 (원본은 아래 shippingCountryCode 판별에 재사용)
$this->setData('basiceShippingAddress', StringUtils::htmlSpecialChars($defaultShippingAddress));
$this->setData('shippingDefaultFl', $order->getShippingDefaultFlYn());
}
 
 
Controller/Mobile/Order/OrderEndController.php+8-1
변경 전OrderEndController.php
 
if ($orderInfo['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo($orderNo);
$this->setData('multiOrderInfo', $multiOrderInfo);
 
 
}
 
$dbUrl = \App::load('\\Component\\Marketing\\DBUrl');
}
 
$this->setData('naverCommonInflowScript', gd_isset($naverCommonInflowScript));
 
 
 
 
 
$this->setData('orderInfo', gd_isset($orderInfo));
 
} catch (\Exception $e) {
 
변경 후OrderEndController.php
 
if ($orderInfo['multiShippingFl'] == 'y') {
$multiOrderInfo = $order->getMultiOrderInfo($orderNo);
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
$this->setData('multiOrderInfo', $order->escapeReceiverInfo($multiOrderInfo));
}
 
$dbUrl = \App::load('\\Component\\Marketing\\DBUrl');
}
 
$this->setData('naverCommonInflowScript', gd_isset($naverCommonInflowScript));
 
// XSS 방지 - 수령자 정보 뷰 출력용 이스케이프
if (!empty($orderInfo)) {
$orderInfo = $order->escapeReceiverInfo($orderInfo);
}
$this->setData('orderInfo', gd_isset($orderInfo));
 
} catch (\Exception $e) {
 
Controller/Mobile/Order/OrderPsController.php+6
변경 전OrderPsController.php
use Framework\Debug\Exception\AlertBackException;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
 
use Globals;
use Origin\Service\Order\OrderInfoService;
use Request;
try {
$order = App::load(\Component\Order\Order::class);
$deliveryAddress = $order->getShippingAddressList(1, 999);
 
 
 
 
 
$this->json($deliveryAddress);
} catch (Exception $e) {
$this->json([
 
변경 후OrderPsController.php
use Framework\Debug\Exception\AlertBackException;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Globals;
use Origin\Service\Order\OrderInfoService;
use Request;
try {
$order = App::load(\Component\Order\Order::class);
$deliveryAddress = $order->getShippingAddressList(1, 999);
 
// XSS 방지 - 스킨이 innerHTML로 출력하는 shippingTitle만 이스케이프 (나머지 필드는 .val()로 input에 채워져 이스케이프하면 엔티티가 그대로 노출됨)
foreach ($deliveryAddress as $key => $shippingAddress) {
$deliveryAddress[$key]['shippingTitle'] = StringUtils::htmlSpecialChars($shippingAddress['shippingTitle']);
}
$this->json($deliveryAddress);
} catch (Exception $e) {
$this->json([
 
Controller/Mobile/Order/RegularOrderController.php+4-2
변경 전RegularOrderController.php
use Component\Mall\Mall;
use Component\Cart\RegularOrderCart;
use Framework\Debug\Exception\AlertRedirectException;
 
use Request;
 
class RegularOrderController extends Controller
$defaultShippingInfo = $regularOrderShippingAddress->findDefaultShippingInfo($memNo);
}
$shippingList = $regularOrderShippingAddress->getRegularOrderShippingAddress($memNo, 1, 999);
$this->setData('shippingInfo', $defaultShippingInfo);
$this->setData('shippingList', $shippingList['list']);
 
 
// pg 정보
$pgConf = gd_policy('autoPg.pgName');
 
변경 후RegularOrderController.php
use Component\Mall\Mall;
use Component\Cart\RegularOrderCart;
use Framework\Debug\Exception\AlertRedirectException;
use Framework\Utility\StringUtils;
use Request;
 
class RegularOrderController extends Controller
$defaultShippingInfo = $regularOrderShippingAddress->findDefaultShippingInfo($memNo);
}
$shippingList = $regularOrderShippingAddress->getRegularOrderShippingAddress($memNo, 1, 999);
// XSS 방지 - 뷰(스킨/Asset) 출력용 이스케이프
$this->setData('shippingInfo', StringUtils::htmlSpecialChars($defaultShippingInfo));
$this->setData('shippingList', StringUtils::htmlSpecialChars($shippingList['list']));
 
// pg 정보
$pgConf = gd_policy('autoPg.pgName');
 
data/skin/mobile/moment/order/order.html-54
변경 전order.html
<div id="pgSettlementApplyLayer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
<script id="myShippingList" type="text/html">
<li class="delivery_item">
<div class="inp_rdo">
 
<input type="radio" id="input<%=idx%>" name="shipping"/>
<label for="input<%=idx%>"></label>
 
</div>
<div class="del_body">
<label for="input1">
<div class="del-name">
<span class="shipping_name"><strong><%=shippingName%></strong></span>
<% if (defaultFl == 'y') { %>
<span>({=__('기본배송지')})</span>
<% } %>
</div>
<div class="del-address">
<span class="shipping_zonecode"><%=shippingZonecode%></span>
<% if (shippingZipcode != '') { %>
(
<span class="shipping_zipcode"><%=shippingZipcode%></span>
)
<% } %>
<!--{ ? gGlobal.isFront }-->
<span class="shipping_addresssub"><%=shippingAddressSub%></span>,
<span class="shipping_address"><%=shippingAddress%></span>,
<span class="shipping_state"><%=shippingState%></span>,
<span class="shipping_city"><%=shippingCity%></span>,
<span class="shipping_country"><%=shippingCountry%></span>
<span class="shipping_country_code dn"><%=shippingCountryCode%></span>
<!--{ : }-->
<span class="shipping_address"><%=shippingAddress%></span>
<span class="shipping_addresssub"><%=shippingAddressSub%></span>
<!--{ / }-->
</div>
<div class="del-callnum">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping_cellphone_prefix"><%=shippingCellPhonePrefix%></span>)
<span class="shipping_cellphone_prefixcode dn"><%=shippingCellPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping_cellphone"><%=shippingCellPhone%></span>
 
<span class="dn">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping_phone_prefix"><%=shippingPhonePrefix%></span>)
<span class="shipping_phone_prefixcode dn"><%=shippingPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping_phone"><%=shippingPhone%></span>
</span>
</div>
</label>
</div>
</li>
</script>
<!-- //나의 배송지 관리 레이어 -->
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
변경 후order.html
<div id="pgSettlementApplyLayer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!-- //나의 배송지 관리 레이어 -->
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
data/skin/mobile/mygreek/order/order.html-54
변경 전order.html
<div id="pgSettlementApplyLayer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
<script id="myShippingList" type="text/html">
<li class="delivery_item">
<div class="inp_rdo">
 
<input type="radio" id="input<%=idx%>" name="shipping"/>
<label for="input<%=idx%>"></label>
 
</div>
<div class="del_body">
<label for="input1">
<div class="del-name">
<span class="shipping_name"><strong><%=shippingName%></strong></span>
<% if (defaultFl == 'y') { %>
<span>({=__('기본배송지')})</span>
<% } %>
</div>
<div class="del-address">
<span class="shipping_zonecode"><%=shippingZonecode%></span>
<% if (shippingZipcode != '') { %>
(
<span class="shipping_zipcode"><%=shippingZipcode%></span>
)
<% } %>
<!--{ ? gGlobal.isFront }-->
<span class="shipping_addresssub"><%=shippingAddressSub%></span>,
<span class="shipping_address"><%=shippingAddress%></span>,
<span class="shipping_state"><%=shippingState%></span>,
<span class="shipping_city"><%=shippingCity%></span>,
<span class="shipping_country"><%=shippingCountry%></span>
<span class="shipping_country_code dn"><%=shippingCountryCode%></span>
<!--{ : }-->
<span class="shipping_address"><%=shippingAddress%></span>
<span class="shipping_addresssub"><%=shippingAddressSub%></span>
<!--{ / }-->
</div>
<div class="del-callnum">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping_cellphone_prefix"><%=shippingCellPhonePrefix%></span>)
<span class="shipping_cellphone_prefixcode dn"><%=shippingCellPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping_cellphone"><%=shippingCellPhone%></span>
 
<span class="dn">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping_phone_prefix"><%=shippingPhonePrefix%></span>)
<span class="shipping_phone_prefixcode dn"><%=shippingPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping_phone"><%=shippingPhone%></span>
</span>
</div>
</label>
</div>
</li>
</script>
<!-- //나의 배송지 관리 레이어 -->
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
변경 후order.html
<div id="pgSettlementApplyLayer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!-- //나의 배송지 관리 레이어 -->
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
data/skin/mobile/story_g/order/order.html-54
변경 전order.html
<div id="pgSettlementApplyLayer" class="btm-fix-layer pg-layer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
<script id="myShippingList" type="text/html">
<li class="delivery-item">
<div class="del-head inp_rdo">
 
<input type="radio" id="input<%=idx%>" name="shipping" class="sp">
<label for="input<%=idx%>"></label>
 
</div>
<div class="del-body">
<label for="input1">
<div class="del-name">
<span class="shipping-name"><strong><%=shippingName%></strong></span>
<% if (defaultFl == 'y') { %>
<span class="c-orange">({=__('기본배송지')})</span>
<% } %>
</div>
<div class="del-address">
<span class="shipping-zonecode"><%=shippingZonecode%></span>
<% if (shippingZipcode != '') { %>
(
<span class="shipping-zipcode"><%=shippingZipcode%></span>
)
<% } %>
<!--{ ? gGlobal.isFront }-->
<span class="shipping-addresssub"><%=shippingAddressSub%></span>,
<span class="shipping-address"><%=shippingAddress%></span>,
<span class="shipping-state"><%=shippingState%></span>,
<span class="shipping-city"><%=shippingCity%></span>,
<span class="shipping-country"><%=shippingCountry%></span>
<span class="shipping-country-code dn"><%=shippingCountryCode%></span>
<!--{ : }-->
<span class="shipping-address"><%=shippingAddress%></span>
<span class="shipping-addresssub"><%=shippingAddressSub%></span>
<!--{ / }-->
</div>
<div class="del-callnum">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping-cellphone-prefix"><%=shippingCellPhonePrefix%></span>)
<span class="shipping-cellphone-prefixcode dn"><%=shippingCellPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping-cellphone"><%=shippingCellPhone%></span>
 
<span class="dn">
<!--{ ? gGlobal.isFront }-->
(+<span class="shipping-phone-prefix"><%=shippingPhonePrefix%></span>)
<span class="shipping-phone-prefixcode dn"><%=shippingPhonePrefixCode%></span>
<!--{ / }-->
<span class="shipping-phone"><%=shippingPhone%></span>
</span>
</div>
</label>
</div>
</li>
</script>
 
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
변경 후order.html
<div id="pgSettlementApplyLayer" class="btm-fix-layer pg-layer" data-key="pg-layer"></div>
<!--//PG 결제 적용하기 레이어 -->
<!-- 나의 배송지 관리 레이어 -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
<!--{ ? isUseMultiShipping === true }-->
<script type="text/html" class="template" id="multiShippingRow">
 
디자인에디터 스킨 모바일 환경에서 네이버페이 버튼 표시 개선
Component/Policy/Policy.php+2
변경 전Policy.php
gd_isset($config['useYn'], 'n');
gd_isset($config['testYn'], 'n');
gd_isset($config['mobileButtonTarget'], 'self');
 
 
gd_isset($config['linkStock'], 'n');
gd_isset($config['reviewFl'], 'y');
gd_isset($config['saleFl'], 'n');
 
변경 후Policy.php
gd_isset($config['useYn'], 'n');
gd_isset($config['testYn'], 'n');
gd_isset($config['mobileButtonTarget'], 'self');
gd_isset($config['mobileImgType'], 'MA');
gd_isset($config['mobileImgColor'], '1');
gd_isset($config['linkStock'], 'n');
gd_isset($config['reviewFl'], 'y');
gd_isset($config['saleFl'], 'n');
 
주문 메뉴 개인정보 접속기록 미적재 및 엑셀 다운로드 처리건수 오류 수정
Component/Admin/AdminLogDAO.php+42-7
변경 전AdminLogDAO.php
use Framework\Utility\DateTimeUtils;
use Framework\Utility\StringUtils;
use Origin\Enum\Policy\AccessLogAction;
 
 
use Component\Member\MemberMasking;
use Origin\Service\Member\ManagerEditPolicy;
 
* @return array
*/
public function setDisplayAdminLogList($data){
 
foreach ($data as $lKey => $lVal) {
$data[$lKey]['displayDetailLogFl'] = 'n';
$fileName = end(explode('/', $lVal['baseUri']));
 
 
 
 
 
 
 
 
// 회원정보 조회를 할 경우에만 상세로그 노출
switch ($fileName) {
*/
public function setDisplayAdminLogInfo($data)
{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$fileUri = explode('/', $data['baseUri']);
$fileName = end($fileUri);
$result['searchCnt'] = $data['data']['searchCnt'];
$result['searchData'] = $data['data']['searchData'];
$result['key'] = $data['data']['GET']['key'];
$result['keyword'] = $data['data']['GET']['keyword'];
$result['viewCnt'] = gd_count(explode(',', $data['data']['searchData']));
 
if ($data['data']['searchCnt'] > 1) {
 
 
if ($result['viewCnt'] > 1) {
$searchData = $data['data']['searchData'];
} else {
if (empty($data['data']['POST']['excelDownloadReason']) === false) {
$result['downloadReason'] = '사유 : ' . $data['data']['POST']['excelDownloadReason'];
}
if (empty($data['data']['POST']['totalCount']) === false) {
$result['searchTargetMsg'] .= '<br>처리건수 : ' . number_format($data['data']['POST']['totalCount']) . '건';
 
}
break;
case 'crema_ps.php':
if (empty($searchCondition) == false && $searchConditionMsgView === true) {
$result['searchConditionMsg'] = '처리조건 : ' . $searchCondition;
}
if (empty($searchViewCnt) == false && $searchViewCntView === true) {
$result['searchViewCnt'] = '처리건수 : ' . $searchViewCnt . '건';
}
if ($searchTarget != '') {
 
private function getActionName($pageData)
{
return $this->getValue('action', $pageData, '조회');
}
 
private function getMenuName($menu)
 
변경 후AdminLogDAO.php
use Framework\Utility\DateTimeUtils;
use Framework\Utility\StringUtils;
use Origin\Enum\Policy\AccessLogAction;
use Origin\Enum\Admin\AdminLogAction;
use Framework\Log\PrivacyLog\PrivacyLogResolverRegistry;
use Component\Member\MemberMasking;
use Origin\Service\Member\ManagerEditPolicy;
 
* @return array
*/
public function setDisplayAdminLogList($data){
$registry = App::getInstance(PrivacyLogResolverRegistry::class);
foreach ($data as $lKey => $lVal) {
$data[$lKey]['displayDetailLogFl'] = 'n';
// 개인정보접속기록 엑셀 다운로드 내역처럼 baseUri 없이 조회한 목록도 있다
$baseUri = (string) ($lVal['baseUri'] ?? '');
$logData = is_string($lVal['data'] ?? null) ? (json_decode($lVal['data'], true) ?: []) : [];
if ($registry->findDisplayResolver($baseUri, $logData) !== null) {
$data[$lKey]['displayDetailLogFl'] = 'y';
continue;
}
$fileName = basename($baseUri);
 
// 회원정보 조회를 할 경우에만 상세로그 노출
switch ($fileName) {
*/
public function setDisplayAdminLogInfo($data)
{
$registry = App::getInstance(PrivacyLogResolverRegistry::class);
$resolver = $registry->findDisplayResolver($data['baseUri']);
if ($resolver !== null) {
$result = $resolver->getDisplayData($data['baseUri'], $data['data']);
$result['searchCnt'] = $data['data']['searchCnt'] ?? null;
$result['searchData'] = htmlspecialchars($result['searchData'], ENT_QUOTES, 'UTF-8');
$result['searchConditionMsg'] = '처리조건 : ' . htmlspecialchars($result['keyValue'], ENT_QUOTES, 'UTF-8');
if ($result['keyword'] !== '') {
$result['searchConditionMsg'] .= ' (' . htmlspecialchars($result['keyword'], ENT_QUOTES, 'UTF-8') . ')';
}
$result['searchViewCnt'] = '처리건수 : ' . $result['viewCnt'] . '건';
$contents = [$result['searchConditionMsg'], $result['searchViewCnt']];
if ($result['searchData'] !== '') {
$result['searchTargetMsg'] = '처리대상 : ' . $result['searchData'];
$contents[] = $result['searchTargetMsg'];
}
$result['logContents'] = implode('<br />', $contents);
 
return $result;
}
 
$data['data']['searchData'] = $data['data']['searchData'] ?? '';
$fileUri = explode('/', $data['baseUri']);
$fileName = end($fileUri);
$result['searchCnt'] = $data['data']['searchCnt'];
$result['searchData'] = $data['data']['searchData'];
$result['key'] = $data['data']['GET']['key'];
$result['keyword'] = $data['data']['GET']['keyword'];
$result['viewCnt'] = empty($data['data']['searchData']) ? 0 : count(explode(',', $data['data']['searchData']));
 
if ($result['viewCnt'] === 0) {
$searchData = '';
} elseif ($data['data']['searchCnt'] > 1) {
if ($result['viewCnt'] > 1) {
$searchData = $data['data']['searchData'];
} else {
if (empty($data['data']['POST']['excelDownloadReason']) === false) {
$result['downloadReason'] = '사유 : ' . $data['data']['POST']['excelDownloadReason'];
}
$totalCount = $data['data']['POST']['totalCount'] ?? null;
if ($totalCount !== null && $totalCount !== '') {
$result['searchTargetMsg'] .= '<br>처리건수 : ' . number_format($totalCount) . '건';
}
break;
case 'crema_ps.php':
if (empty($searchCondition) == false && $searchConditionMsgView === true) {
$result['searchConditionMsg'] = '처리조건 : ' . $searchCondition;
}
if ($searchViewCntView) {
$result['searchViewCnt'] = '처리건수 : ' . $searchViewCnt . '건';
}
if ($searchTarget != '') {
 
private function getActionName($pageData)
{
return $this->getValue('action', $pageData, AdminLogAction::VIEW);
}
 
private function getMenuName($menu)
 
Component/Bankda/BankdaGhostDepositor.php+3
변경 전BankdaGhostDepositor.php
/**
* 미확인 입금자 디비 저장
* @param array $arrData 미확인 입금자 배열
 
*/
public function registerGhostDepositor($arrData) {
$insertData['depositPrice'] = isset($arrData['depositPrice']) ? $arrData['depositPrice'] : '';
$arrBind = $this->db->get_binding(DBTableField::tableGhostDepositor(), $insertData, 'insert');
$this->db->set_insert_db(DB_GHOST_DEPOSITOR, $arrBind['param'], $arrBind['bind'], 'y');
 
 
}
/**
 
변경 후BankdaGhostDepositor.php
/**
* 미확인 입금자 디비 저장
* @param array $arrData 미확인 입금자 배열
* @return int|string 등록된 미확인 입금자 번호
*/
public function registerGhostDepositor($arrData) {
$insertData['depositPrice'] = isset($arrData['depositPrice']) ? $arrData['depositPrice'] : '';
$arrBind = $this->db->get_binding(DBTableField::tableGhostDepositor(), $insertData, 'insert');
$this->db->set_insert_db(DB_GHOST_DEPOSITOR, $arrBind['param'], $arrBind['bind'], 'y');
return $this->db->insert_id();
}
/**
 
Component/Excel/ExcelOrderConvert.php+1
변경 전ExcelOrderConvert.php
'fail' => $failCnt,
'functionAuth' => $functionAuthCnt,
'message' => $failMessage,
 
];
}
 
 
변경 후ExcelOrderConvert.php
'fail' => $failCnt,
'functionAuth' => $functionAuthCnt,
'message' => $failMessage,
'groupCd' => $groupCd,
];
}
 
 
Component/Excel/ExcelRequest.php+127-5
변경 전ExcelRequest.php
*/
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
public function makeExcelFile(&$arrData)
{
$excleForm = \App::load('\\Component\\Excel\\ExcelForm');
$this->fileConfig['menu'] = $formData['menu'];
$this->fileConfig['location'] = $formData['location'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
if (!is_dir(UserFilePath::data('excel', $formData['menu'])->getPathName())) {
mkdir(UserFilePath::data('excel', $formData['menu'])->getPathName(), 0707);
}
$regularOrderAdmin = \App::getInstance(\Component\RegularDelivery\RegularOrderAdmin\RegularOrderAdminListExcel::class);
$rows = $regularOrderAdmin->generateExcelForm(unserialize($arrData['whereCondition']), $formData['excelField'], $defaultField);
$getData = $this->getRegularOrderList($rows);
 
 
 
 
} else {
$getData = $this->getOrderList(unserialize($arrData['whereCondition']), $formData['excelField'], $defaultField, $excelFieldName);
}
$tmpGetData = $this->getMemberMemberList(unserialize($arrData['whereCondition']), $formData['excelField'], $excelFieldName);
}
$getData = $tmpGetData['getData'];
$arrData['totalCount'] = $tmpGetData['totalCount'];
} else if ($formData['menu'] == 'promotion' && $formData['location'] == 'coupon_manage') {
$getData = $this->getPromotionCouponManage($arrData, $formData['excelField'], $excelFieldName);
} else if ($formData['menu'] == 'promotion' && $formData['location'] == 'coupon_offline_manage') {
}
 
if ($getData) {
 
if (\is_array($getData)) {
foreach ($getData as $k => $v) {
$this->genarateFile($v);
 
 
}
} else {
if (gd_isset($this->fileConfig['password'])) {
$arrData['filePath'] = "excel" . DS . $formData['menu'];
$arrData['fileName'] = gd_implode(STR_DIVISION, $this->fileConfig['fileName']);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$arrData['state'] = "y";
$arrData['expiryDate'] = date("Y-m-d", strtotime("+7 day"));
 
$setData = $setHedData = [];
$setHedData[] = '<tr>';
foreach ($data as $k => $v) {
 
$tmpData = [];
foreach ($v as $key => $val) {
if (($key % 20 == 0 && $key != '0') || $totalNum - 1 == $key) {
$orderData = $orderAdmin->getOrderListForAdminExcel($whereCondition, $whereCondition['periodFl'], $isUserHandle, $orderType, $excelField, 0, $this->excelPageNum);
 
//튜닝한 업체를 위해 데이터 맞춤
if (empty($orderData['totalCount']) === true && empty($orderData['orderList']) === true) {
$isGenerator = false;
$totalNum = gd_count($orderData);
if ($this->excelPageNum) $orderList = array_chunk($orderData, $this->excelPageNum, true);
$totalNum = gd_count($orderData['totalCount']);
$orderList = $orderData['orderList'];
if (gd_is_provider() && $orderType == 'goods') {
$totalScmInfo = $orderData['totalScmInfo'];
}
}
 
 
 
 
//값이 입력되지 않은 것으로 간주하고 전체 다운로드.
if ($this->excelPageNum == 0) {
$pageNum = 0;
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
$maskLabels = is_array($data) === true ? $excelMasking->resolveOrderLabels($data) : null;
 
 
foreach ($data as $key => $val) {
$progress = ($totalNum == 1) ? 100 : round((100 / ($totalNum - 1)) * ($key + ($i * $this->excelPageNum)));
if ($progress % 20 == 0 || $progress == '100') {
} else {
fwrite($fh, gd_implode(chr(10), $tmpData));
}
 
unset($tmpData);
}
 
fclose($fh);
 
$this->fileConfig['fileName'][] = $fileName . ".xls";
 
}
 
 
 
$totalNum = gd_count($data);
 
 
 
 
if ($this->excelPageNum) $data = array_chunk($data, $this->excelPageNum, true);
else $data = array_chunk($data, $totalNum, true);
 
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
 
foreach ($data as $k => $v) {
 
 
// 분리보관(탈퇴/휴면) 주문 판정을 페이지 단위로 1회만 조회한다
$maskLabels = $excelMasking->resolveOrderLabels($v);
 
$totalNum = gd_count($data);
 
 
 
 
if ($this->excelPageNum) $data = array_chunk($data, $this->excelPageNum, true);
else $data = array_chunk($data, $totalNum, true);
 
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
 
foreach ($data as $k => $v) {
 
 
// 분리보관(탈퇴/휴면) 주문 판정을 페이지 단위로 1회만 조회한다
$maskLabels = $excelMasking->resolveOrderLabels($v);
$orderList = $orderData['orderList'];
}
 
 
 
 
//값이 입력되지 않은 것으로 간주하고 전체 다운로드.
if ($this->excelPageNum == 0) {
$pageNum = 0;
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
$maskLabels = is_array($data) === true ? $excelMasking->resolveOrderLabels($data) : null;
 
 
foreach ($data as $key => $val) {
$progress = ($totalNum - 1) > 0 ? round((100 / ($totalNum - 1)) * ($key + ($i * $this->excelPageNum))) : 100;
if ($progress % 20 == 0 || $progress == '100') {
}
 
fwrite($fh, gd_implode(chr(10), $tmpData));
 
unset($tmpData);
}
 
fclose($fh);
 
$this->fileConfig['fileName'][] = $fileName . ".xls";
 
}
 
return true;
$setData = [];
$setHedData[] = "<tr>";
foreach ($data as $k => $v) {
 
$tmpData = [];
foreach ($v as $key => $val) {
if (($key % 20 == 0 && $key != '0') || $totalNum - 1 == $key) {
$setData[] = gd_array_merge($setHedData, $tmpData);
}
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
}
 
변경 후ExcelRequest.php
*/
}
 
/**
* @param array $fileInfo 엑셀 요청의 menu, fileName, whereCondition, totalCount
* @param int|string $excelKey 선택한 파일 순번
* @return int|string|null 파일별 처리건수 또는 미집계
*/
public function getDownloadTotalCount(array $fileInfo, $excelKey)
{
if (!in_array($fileInfo['menu'] ?? '', ['order', 'orderDraft', 'member'], true)) {
return $fileInfo['totalCount'] ?? null;
}
 
$fileNames = explode(STR_DIVISION, $fileInfo['fileName'] ?? '');
if ((!is_int($excelKey) && !is_string($excelKey))
|| (string) (int) $excelKey !== (string) $excelKey
|| !isset($fileNames[$excelKey])
|| empty($fileNames[$excelKey])) {
return null;
}
 
$whereCondition = [];
if (!empty($fileInfo['whereCondition'])) {
if (!is_string($fileInfo['whereCondition'])) {
return null;
}
$whereCondition = @unserialize($fileInfo['whereCondition'], ['allowed_classes' => false]);
if (!is_array($whereCondition)) {
return null;
}
}
 
if (array_key_exists('excelFileCounts', $whereCondition)) {
$fileCounts = $whereCondition['excelFileCounts'];
if (!is_array($fileCounts) || array_keys($fileCounts) !== $fileNames) {
return null;
}
foreach ($fileCounts as $fileCount) {
if (!is_int($fileCount) || $fileCount < 0) {
return null;
}
}
return $fileCounts[$fileNames[$excelKey]];
}
 
// 파일별 집계가 없는 이전 요청과 튜닝 사본은 기존 건수 표시를 유지한다.
return $fileInfo['totalCount'] ?? null;
}
 
public function makeExcelFile(&$arrData)
{
$excleForm = \App::load('\\Component\\Excel\\ExcelForm');
$this->fileConfig['menu'] = $formData['menu'];
$this->fileConfig['location'] = $formData['location'];
 
// 미집계와 실제 0건을 구분하고 재사용 시 직전 요청 값을 제거한다.
$this->fileConfig['totalCount'] = null;
$this->fileConfig['excelFileCounts'] = [];
$tracksFileCount = in_array($formData['menu'], ['order', 'member'], true) || $arrData['menu'] === 'orderDraft';
if ($tracksFileCount) {
$this->fileConfig['fileName'] = [];
$logWhereCondition = unserialize($arrData['whereCondition'], ['allowed_classes' => false]);
if (is_array($logWhereCondition)) {
// 요청에서 전달되거나 이전 생성 때 저장한 건수는 재집계한다.
unset($logWhereCondition['excelFileCounts']);
$arrData['whereCondition'] = serialize($logWhereCondition);
}
}
 
if (!is_dir(UserFilePath::data('excel', $formData['menu'])->getPathName())) {
mkdir(UserFilePath::data('excel', $formData['menu'])->getPathName(), 0707);
}
$regularOrderAdmin = \App::getInstance(\Component\RegularDelivery\RegularOrderAdmin\RegularOrderAdminListExcel::class);
$rows = $regularOrderAdmin->generateExcelForm(unserialize($arrData['whereCondition']), $formData['excelField'], $defaultField);
$getData = $this->getRegularOrderList($rows);
// 개인정보 접속기록의 '처리건수' 로 남길 실제 산출 건수 (상속 없이 복사된 튜닝 사본에는 신규 메소드가 없어 방어)
$this->fileConfig['totalCount'] = method_exists($regularOrderAdmin, 'getTotalCount')
? $regularOrderAdmin->getTotalCount()
: null;
} else {
$getData = $this->getOrderList(unserialize($arrData['whereCondition']), $formData['excelField'], $defaultField, $excelFieldName);
}
$tmpGetData = $this->getMemberMemberList(unserialize($arrData['whereCondition']), $formData['excelField'], $excelFieldName);
}
$getData = $tmpGetData['getData'];
$this->fileConfig['totalCount'] = $tmpGetData['totalCount'];
} else if ($formData['menu'] == 'promotion' && $formData['location'] == 'coupon_manage') {
$getData = $this->getPromotionCouponManage($arrData, $formData['excelField'], $excelFieldName);
} else if ($formData['menu'] == 'promotion' && $formData['location'] == 'coupon_offline_manage') {
}
 
if ($getData) {
$generatedAllFiles = true;
if (\is_array($getData)) {
foreach ($getData as $k => $v) {
if ($this->genarateFile($v) === false) {
$generatedAllFiles = false;
}
}
} else {
if (gd_isset($this->fileConfig['password'])) {
$arrData['filePath'] = "excel" . DS . $formData['menu'];
$arrData['fileName'] = gd_implode(STR_DIVISION, $this->fileConfig['fileName']);
 
if ($tracksFileCount) {
$fileNames = $this->fileConfig['fileName'];
$fileCounts = $this->fileConfig['excelFileCounts'];
if (empty($fileCounts) && count($fileNames) === 1 && isset($this->fileConfig['totalCount'])) {
$fileCounts = [(int) $this->fileConfig['totalCount']];
}
$logWhereCondition = unserialize($arrData['whereCondition'], ['allowed_classes' => false]);
$logWhereCondition = is_array($logWhereCondition) ? $logWhereCondition : [];
$logWhereCondition['excelFileCounts'] = [];
if ($generatedAllFiles
&& (!is_array($getData) || count($getData) === count($fileNames))
&& !empty($fileNames)
&& count($fileNames) === count(array_unique($fileNames))
&& empty($fileCounts)) {
unset($logWhereCondition['excelFileCounts']);
}
if ($generatedAllFiles
&& (!is_array($getData) || count($getData) === count($fileNames))
&& !empty($fileNames)
&& count($fileNames) === count(array_unique($fileNames))
&& count($fileNames) === count($fileCounts)
&& array_keys($fileCounts) === range(0, count($fileNames) - 1)) {
$logWhereCondition['excelFileCounts'] = array_combine($fileNames, $fileCounts);
}
$arrData['whereCondition'] = serialize($logWhereCondition);
}
 
if (isset($this->fileConfig['totalCount'])) {
// DBTool의 느슨한 null 비교에서 정수 0이 기본값으로 바뀌므로 문자열로 전달한다.
$arrData['totalCount'] = $this->fileConfig['totalCount'] === 0 ? '0' : $this->fileConfig['totalCount'];
}
 
$arrData['state'] = "y";
$arrData['expiryDate'] = date("Y-m-d", strtotime("+7 day"));
 
$setData = $setHedData = [];
$setHedData[] = '<tr>';
foreach ($data as $k => $v) {
$this->fileConfig['excelFileCounts'][] = count($v);
$tmpData = [];
foreach ($v as $key => $val) {
if (($key % 20 == 0 && $key != '0') || $totalNum - 1 == $key) {
$orderData = $orderAdmin->getOrderListForAdminExcel($whereCondition, $whereCondition['periodFl'], $isUserHandle, $orderType, $excelField, 0, $this->excelPageNum);
 
//튜닝한 업체를 위해 데이터 맞춤
if (!isset($orderData['totalCount'], $orderData['orderList'])) {
$isGenerator = false;
$totalNum = gd_count($orderData);
if ($this->excelPageNum) $orderList = array_chunk($orderData, $this->excelPageNum, true);
$totalNum = gd_count($orderData['totalCount']);
$orderList = $orderData['orderList'];
if (gd_is_provider() && $orderType == 'goods') {
$totalScmInfo = $orderData['totalScmInfo'] ?? [];
}
}
 
// 개인정보 접속기록의 '처리건수' 로 남길 실제 산출 건수
$this->fileConfig['totalCount'] = $totalNum;
 
//값이 입력되지 않은 것으로 간주하고 전체 다운로드.
if ($this->excelPageNum == 0) {
$pageNum = 0;
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
$maskLabels = is_array($data) === true ? $excelMasking->resolveOrderLabels($data) : null;
 
$fileCount = 0;
foreach ($data as $key => $val) {
$progress = ($totalNum == 1) ? 100 : round((100 / ($totalNum - 1)) * ($key + ($i * $this->excelPageNum)));
if ($progress % 20 == 0 || $progress == '100') {
} else {
fwrite($fh, gd_implode(chr(10), $tmpData));
}
$fileCount++;
unset($tmpData);
}
 
fclose($fh);
 
$this->fileConfig['fileName'][] = $fileName . ".xls";
$this->fileConfig['excelFileCounts'][] = $fileCount;
}
 
 
 
$totalNum = gd_count($data);
 
// 개인정보 접속기록의 '처리건수' 로 남길 실제 산출 건수
$this->fileConfig['totalCount'] = $totalNum;
 
if ($this->excelPageNum) $data = array_chunk($data, $this->excelPageNum, true);
else $data = array_chunk($data, $totalNum, true);
 
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
 
foreach ($data as $k => $v) {
$this->fileConfig['excelFileCounts'][] = count($v);
 
// 분리보관(탈퇴/휴면) 주문 판정을 페이지 단위로 1회만 조회한다
$maskLabels = $excelMasking->resolveOrderLabels($v);
 
$totalNum = gd_count($data);
 
// 개인정보 접속기록의 '처리건수' 로 남길 실제 산출 건수
$this->fileConfig['totalCount'] = $totalNum;
 
if ($this->excelPageNum) $data = array_chunk($data, $this->excelPageNum, true);
else $data = array_chunk($data, $totalNum, true);
 
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
 
foreach ($data as $k => $v) {
$this->fileConfig['excelFileCounts'][] = count($v);
 
// 분리보관(탈퇴/휴면) 주문 판정을 페이지 단위로 1회만 조회한다
$maskLabels = $excelMasking->resolveOrderLabels($v);
$orderList = $orderData['orderList'];
}
 
// 개인정보 접속기록의 '처리건수' 로 남길 실제 산출 건수
$this->fileConfig['totalCount'] = $totalNum;
 
//값이 입력되지 않은 것으로 간주하고 전체 다운로드.
if ($this->excelPageNum == 0) {
$pageNum = 0;
$excelMasking = \App::getInstance(OrderExcelMaskingService::class);
$maskLabels = is_array($data) === true ? $excelMasking->resolveOrderLabels($data) : null;
 
$fileCount = 0;
foreach ($data as $key => $val) {
$progress = ($totalNum - 1) > 0 ? round((100 / ($totalNum - 1)) * ($key + ($i * $this->excelPageNum))) : 100;
if ($progress % 20 == 0 || $progress == '100') {
}
 
fwrite($fh, gd_implode(chr(10), $tmpData));
$fileCount++;
unset($tmpData);
}
 
fclose($fh);
 
$this->fileConfig['fileName'][] = $fileName . ".xls";
$this->fileConfig['excelFileCounts'][] = $fileCount;
}
 
return true;
$setData = [];
$setHedData[] = "<tr>";
foreach ($data as $k => $v) {
$this->fileConfig['excelFileCounts'][] = count($v);
$tmpData = [];
foreach ($v as $key => $val) {
if (($key % 20 == 0 && $key != '0') || $totalNum - 1 == $key) {
$setData[] = gd_array_merge($setHedData, $tmpData);
}
 
return ['getData' => $setData, 'totalCount' => $totalNum];
}
}
 
Component/Order/OrderAdminNew.php+19
변경 전OrderAdminNew.php
/** @var array 현재 자동취소 주문건 */
protected $targetOrderGoodsNoArr;
protected $targetOrderGoodsNo;
 
 
 
 
public $userHandleText = [
*/
public function saveOrderInvoice($arrData, $searchPeriod = '7')
{
 
parse_str($arrData['whereDetail'], $arrData['whereDetail']);
 
$arrData['whereDetail']['statusMode'] = "p,g";
}
 
$this->db->set_insert_db(DB_ORDER_GODO_POST, 'invoiceNo', array('s', $invoiceNo,), 'y');
 
 
unset($arrUpdate);
unset($arrBind);
*
* @param $arrData
*
 
* @throws Exception
* @internal param string $getValue 파라미터
*/
}
 
$resultData = $godoPost->sendInvoiceInfo($setData);
 
 
if ($resultData) {
 
$this->db->set_update_db(DB_ORDER_GODO_POST, $arrUpdate, 'invoiceNo = ?', $arrBind);
unset($arrUpdate);
unset($arrBind);
 
 
 
//예약완료시 입금상태의 주문건일 경우 상품준비중으로 변경
} else {
throw new \Exception(__('우체국 택배 연동하기가 실패하였습니다.'));
}
 
 
 
 
 
 
 
 
 
 
 
 
}
 
 
 
변경 후OrderAdminNew.php
/** @var array 현재 자동취소 주문건 */
protected $targetOrderGoodsNoArr;
protected $targetOrderGoodsNo;
/** @var array 우체국택배 송장번호를 발급한 주문번호 (saveOrderInvoice) */
protected $issuedInvoiceOrderNos = [];
 
 
public $userHandleText = [
*/
public function saveOrderInvoice($arrData, $searchPeriod = '7')
{
$this->issuedInvoiceOrderNos = [];
parse_str($arrData['whereDetail'], $arrData['whereDetail']);
 
$arrData['whereDetail']['statusMode'] = "p,g";
}
 
$this->db->set_insert_db(DB_ORDER_GODO_POST, 'invoiceNo', array('s', $invoiceNo,), 'y');
$this->issuedInvoiceOrderNos[] = $v['orderNo'];
 
unset($arrUpdate);
unset($arrBind);
*
* @param $arrData
*
* @return array 예약된 주문번호 목록
* @throws Exception
* @internal param string $getValue 파라미터
*/
}
 
$resultData = $godoPost->sendInvoiceInfo($setData);
$reservedOrderNos = [];
 
if ($resultData) {
 
$this->db->set_update_db(DB_ORDER_GODO_POST, $arrUpdate, 'invoiceNo = ?', $arrBind);
unset($arrUpdate);
unset($arrBind);
$reservedOrderNos[] = $updateData[$v]['orderNo'];
 
 
//예약완료시 입금상태의 주문건일 경우 상품준비중으로 변경
} else {
throw new \Exception(__('우체국 택배 연동하기가 실패하였습니다.'));
}
 
return array_values(array_unique($reservedOrderNos));
}
 
/**
* 우체국택배 송장번호를 발급한 주문번호 — 직전 saveOrderInvoice 호출 기준
*
* @return array
*/
public function getIssuedInvoiceOrderNos()
{
return array_values(array_unique($this->issuedInvoiceOrderNos));
}
 
 
 
Component/Order/OrderAdmin.php+19
변경 전OrderAdmin.php
protected $cancelOrderlLimit = 500;
/** @var array 현재 자동취소 주문건 */
protected $targetOrderGoodsNoArr;
 
 
protected $targetOrderGoodsNo;
 
public $userHandleText = [
*/
public function saveOrderInvoice($arrData, $searchPeriod = '7')
{
 
parse_str($arrData['whereDetail'], $arrData['whereDetail']);
 
$arrData['whereDetail']['statusMode'] = "p,g";
}
 
$this->db->set_insert_db(DB_ORDER_GODO_POST, 'invoiceNo', array('s', $invoiceNo,), 'y');
 
 
unset($arrUpdate);
unset($arrBind);
*
* @param $arrData
*
 
* @throws Exception
* @internal param string $getValue 파라미터
*/
}
 
$resultData = $godoPost->sendInvoiceInfo($setData);
 
 
if ($resultData) {
 
$this->db->set_update_db(DB_ORDER_GODO_POST, $arrUpdate, 'invoiceNo = ?', $arrBind);
unset($arrUpdate);
unset($arrBind);
 
 
 
//예약완료시 입금상태의 주문건일 경우 상품준비중으로 변경
} else {
throw new \Exception(__('우체국 택배 연동하기가 실패하였습니다.'));
}
 
 
 
 
 
 
 
 
 
 
 
 
}
 
 
 
변경 후OrderAdmin.php
protected $cancelOrderlLimit = 500;
/** @var array 현재 자동취소 주문건 */
protected $targetOrderGoodsNoArr;
/** @var array 우체국택배 송장번호를 발급한 주문번호 (saveOrderInvoice) */
protected $issuedInvoiceOrderNos = [];
protected $targetOrderGoodsNo;
 
public $userHandleText = [
*/
public function saveOrderInvoice($arrData, $searchPeriod = '7')
{
$this->issuedInvoiceOrderNos = [];
parse_str($arrData['whereDetail'], $arrData['whereDetail']);
 
$arrData['whereDetail']['statusMode'] = "p,g";
}
 
$this->db->set_insert_db(DB_ORDER_GODO_POST, 'invoiceNo', array('s', $invoiceNo,), 'y');
$this->issuedInvoiceOrderNos[] = $v['orderNo'];
 
unset($arrUpdate);
unset($arrBind);
*
* @param $arrData
*
* @return array 예약된 주문번호 목록
* @throws Exception
* @internal param string $getValue 파라미터
*/
}
 
$resultData = $godoPost->sendInvoiceInfo($setData);
$reservedOrderNos = [];
 
if ($resultData) {
 
$this->db->set_update_db(DB_ORDER_GODO_POST, $arrUpdate, 'invoiceNo = ?', $arrBind);
unset($arrUpdate);
unset($arrBind);
$reservedOrderNos[] = $updateData[$v]['orderNo'];
 
 
//예약완료시 입금상태의 주문건일 경우 상품준비중으로 변경
} else {
throw new \Exception(__('우체국 택배 연동하기가 실패하였습니다.'));
}
 
return array_values(array_unique($reservedOrderNos));
}
 
/**
* 우체국택배 송장번호를 발급한 주문번호 — 직전 saveOrderInvoice 호출 기준
*
* @return array
*/
public function getIssuedInvoiceOrderNos()
{
return array_values(array_unique($this->issuedInvoiceOrderNos));
}
 
 
 
Component/RegularDelivery/RegularOrderAdmin/RegularOrderAdminListExcel.php+17
변경 전RegularOrderAdminListExcel.php
* @var Logger
*/
private $logger;
 
 
 
 
 
 
 
/**
* @var OrderExcelMaskingService
}
 
$this->logger->channel('regularOrderAdmin')->info('정기결제 엑셀 폼 데이터 : ', $regularOrderExcelList);
 
 
// 엑셀 헤더 설정
$headers = [];
 
return array_merge($headers, $rows);
}
 
 
 
 
 
 
 
 
 
 
}
 
변경 후RegularOrderAdminListExcel.php
* @var Logger
*/
private $logger;
/**
* 엑셀에 담긴 실제 건수 (개인정보 접속기록의 '처리건수')
*
* @var int|null
*/
protected $totalCount = null;
 
/**
* @var OrderExcelMaskingService
}
 
$this->logger->channel('regularOrderAdmin')->info('정기결제 엑셀 폼 데이터 : ', $regularOrderExcelList);
$this->totalCount = count($regularOrderExcelList);
 
// 엑셀 헤더 설정
$headers = [];
 
return array_merge($headers, $rows);
}
 
/**
* generateExcelForm() 이후에만 값이 있고, 그 전에는 null
*
* @return int|null
*/
public function getTotalCount(): ?int
{
return $this->totalCount;
}
}
 
Controller/Admin/Order/BankdaMatchPsController.php+58
변경 전BankdaMatchPsController.php
case 'accountList':
$bk = new Bankda();
$res = $bk->accountList(Request::get()->toArray());
 
 
 
 
 
echo $res;
exit();
break;
try {
$bk = new Bankda();
$res = $bk->bankMatching(Request::get()->toArray());
 
echo $res;
exit();
} catch (\Exception $e) {
$bk = new Bankda();
$getValue = Request::get()->toArray();
$res = $bk->bankManualOrderList($getValue);
 
if($res) {
echo json_encode($res, JSON_UNESCAPED_UNICODE);
}
throw new HttpException($e->ectMessage, 500);
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후BankdaMatchPsController.php
case 'accountList':
$bk = new Bankda();
$res = $bk->accountList(Request::get()->toArray());
$accountList = json_decode($res, true);
$this->attachPrivacyLogResult(
$this->extractListTargets($accountList, 'bkcode'),
$this->extractListTotalCount($accountList)
);
echo $res;
exit();
break;
try {
$bk = new Bankda();
$res = $bk->bankMatching(Request::get()->toArray());
$this->attachPrivacyLogResult($this->extractMatchedOrderNos($res));
echo $res;
exit();
} catch (\Exception $e) {
$bk = new Bankda();
$getValue = Request::get()->toArray();
$res = $bk->bankManualOrderList($getValue);
$this->attachPrivacyLogResult($this->extractListTargets($res, 'orderNo'), $this->extractListTotalCount($res));
if($res) {
echo json_encode($res, JSON_UNESCAPED_UNICODE);
}
throw new HttpException($e->ectMessage, 500);
}
}
 
/**
* 조회 결과 행의 식별자 — 결과 검증에서 누락을 감지하도록 응답과 식별자의 null 을 보존한다
*
* 통장입금내역은 입금내역 코드(뱅크다 TBLBANK.bkcode), 입금대기 주문은 주문번호를 처리대상으로 남긴다.
* 입금대기 주문은 결과가 없으면 lists 없이 page 만 내려준다
*
* @param mixed $response 조회 응답 (lists: 조회 행 목록, page: 페이징 정보)
* @param string $field 행의 식별자 필드 (bkcode, orderNo)
* @return array|null 응답 형식이 아니면 null
*/
protected function extractListTargets(mixed $response, string $field): ?array
{
if (!is_array($response) || !is_array($response['page'] ?? null)) {
return null;
}
$lists = $response['lists'] ?? [];
 
return is_array($lists)
? array_map(static fn(mixed $row): mixed => is_array($row) ? ($row[$field] ?? null) : null, $lists)
: null;
}
 
/**
* 조회 결과의 검색 건수
*
* @param mixed $response 조회 응답 (page.recode: 검색 건수)
* @return int|null
*/
protected function extractListTotalCount(mixed $response): ?int
{
$totalCount = is_array($response) ? ($response['page']['recode'] ?? null) : null;
 
return is_numeric($totalCount) ? (int) $totalCount : null;
}
 
/**
* 실시간입금확인으로 입금확인된 주문번호 — 화면 결과 레이어와 같이 '^' 뒤를 '|' 로 나눈다
*
* @param mixed $result 매칭 결과 (결과 메시지^주문번호|주문번호)
* @return array|null 입금확인된 주문이 없으면 빈 배열
*/
protected function extractMatchedOrderNos(mixed $result): ?array
{
if (!is_string($result)) {
return null;
}
$orderNos = explode('^', $result, 2)[1] ?? '';
 
return $orderNos === '' ? [] : explode('|', $orderNos);
}
}
 
Controller/Admin/Order/BankdaNoMatchPsController.php+21-1
변경 전BankdaNoMatchPsController.php
// --- 미확인 입금자 등록
case 'insert':
$ghostDepositor->registerGhostDepositor($requestPostParams);
 
 
 
 
$rs['result'] = true;
$this->json($rs);
case 'load':
$res = $ghostDepositor->loadGhostDepositor($requestPostParams);
 
$this->json($res);
break;
case 'download':
$res = $ghostDepositor->loadGhostDepositor($requestPostParams);
 
 
$ghostDepositor->downloadGhostDepositor($res['body']);
default:
exit;
}
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후BankdaNoMatchPsController.php
// --- 미확인 입금자 등록
case 'insert':
$sno = $ghostDepositor->registerGhostDepositor($requestPostParams);
// 등록 번호를 돌려받지 못하면 처리 전 기록만 남긴다
if (!empty($sno)) {
$this->attachPrivacyLogResult([$sno]);
}
$rs['result'] = true;
$this->json($rs);
case 'load':
$res = $ghostDepositor->loadGhostDepositor($requestPostParams);
$this->attachPrivacyLogResult($this->extractPrivacyLogTargets($res['body'] ?? null));
$this->json($res);
break;
case 'download':
$res = $ghostDepositor->loadGhostDepositor($requestPostParams);
// 엑셀 다운로드는 다른 엑셀과 같이 처리건수만 남긴다
$this->attachPrivacyLogResult([], is_array($res['body'] ?? null) ? count($res['body']) : null);
$ghostDepositor->downloadGhostDepositor($res['body']);
default:
exit;
}
/**
* 조회 결과의 입금자 번호 — 결과 검증에서 누락을 감지하도록 본문과 번호의 null 을 보존한다
*
* @param mixed $body 조회 결과 행 목록
* @return array|null
*/
protected function extractPrivacyLogTargets(mixed $body): ?array
{
return is_array($body)
? array_map(static fn(mixed $row): mixed => is_array($row) ? ($row['sno'] ?? null) : null, $body)
: null;
}
}
 
Controller/Admin/Order/CashReceiptPsController.php+11-2
변경 전CashReceiptPsController.php
// --- 현금영수증 개별 발급 요청 저장
case 'cash_receipt_register':
try {
// 개별 발급 요청 저장
$cashReceipt->saveCashReceiptEach($postValue);
 
 
 
 
 
 
 
 
 
 
$this->layer(__('저장이 완료되었습니다.'));
} catch (\Exception $e) {
 
변경 후CashReceiptPsController.php
// --- 현금영수증 개별 발급 요청 저장
case 'cash_receipt_register':
try {
// 개별 발급 요청 저장 — 주문번호 없이 발급하면 저장 시 생성되므로 저장 후 접속기록에 채운다
$saved = false;
try {
$cashReceipt->saveCashReceiptEach($postValue);
$saved = true;
} finally {
$savedOrderNo = $cashReceipt->getSavedOrderNo();
if ($saved || !empty($savedOrderNo)) {
$this->attachPrivacyLogResult([$savedOrderNo]);
}
}
 
$this->layer(__('저장이 완료되었습니다.'));
} catch (\Exception $e) {
 
Controller/Admin/Order/EpostReturnRequestController.php+4-22
변경 전EpostReturnRequestController.php
use Component\Page\Page;
use DTO\Order\EpostReturnSearchDTO;
use Framework\Debug\Exception\AlertBackException;
use Origin\Service\Order\EpostReturnAccessLogService;
use Origin\Service\Privacy\Masking\OrderListMaskingService;
use Request;
 
*/
class EpostReturnRequestController extends \Controller\Admin\Controller
{
private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 수거신청';
private const LOG_PAGE = '우체국택배 수거신청';
private const LOG_ACTION = '수거신청 대상 조회';
 
/**
* {@inheritdoc}
*/
$targetList = \App::getInstance(EpostReturnAdmin::class)->getTargetList($getValue);
// 개인정보 마스킹(주문 리스트): 주문자명→회원유형, 주문자 휴대폰·ID·수령자명·입금자명·입금계좌→'-'
$targetList = \App::getInstance(OrderListMaskingService::class)->maskOrderList($targetList);
$this->writeAccessLog($targetList['data'], $targetList['total']);
 
// [수거 신청] 쓰기 권한 — 화면은 얼럿 판정 재료만 받고 최종 차단은 PS 서버 검증
$naviMenu = $this->getData('naviMenu');
 
$this->getView()->setDefine('epostReturnResult', 'order/epost_return_request/epost_return_request_result.php');
 
// 수거 신청 처리 후 재조회
if (Request::isAjax()) {
 
$this->getView()->setData($this->getData());
echo $this->getView()->render('epostReturnResult');
exit;
/**
* 수거신청 대상 조회 접속기록
*
 
 
* @param array $rows 조회된 대상 행
* @param int $total 검색 건수
*/
protected function writeAccessLog(array $rows, int $total): void
{
$session = \App::getInstance('session');
$orderNos = array_values(array_unique(array_column($rows, 'orderNo')));
 
\App::getInstance(EpostReturnAccessLogService::class)->write(
__(self::LOG_MENU),
__(self::LOG_PAGE),
__(self::LOG_ACTION),
(string)$session->get('manager.sno'),
(string)$session->get('manager.managerId'),
(string)$session->get('manager.scmNo'),
preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')),
(string)Request::getReferer(),
(string)Request::getRemoteAddress(),
['searchData' => gd_implode(', ', $orderNos), 'searchCnt' => $total]
);
}
}
 
변경 후EpostReturnRequestController.php
use Component\Page\Page;
use DTO\Order\EpostReturnSearchDTO;
use Framework\Debug\Exception\AlertBackException;
 
use Origin\Service\Privacy\Masking\OrderListMaskingService;
use Request;
 
*/
class EpostReturnRequestController extends \Controller\Admin\Controller
{
 
 
 
 
/**
* {@inheritdoc}
*/
$targetList = \App::getInstance(EpostReturnAdmin::class)->getTargetList($getValue);
// 개인정보 마스킹(주문 리스트): 주문자명→회원유형, 주문자 휴대폰·ID·수령자명·입금자명·입금계좌→'-'
$targetList = \App::getInstance(OrderListMaskingService::class)->maskOrderList($targetList);
 
 
// [수거 신청] 쓰기 권한 — 화면은 얼럿 판정 재료만 받고 최종 차단은 PS 서버 검증
$naviMenu = $this->getData('naviMenu');
 
$this->getView()->setDefine('epostReturnResult', 'order/epost_return_request/epost_return_request_result.php');
 
// 수거 신청 처리 후 재조회 — exit 로 끝나 AdminLog 인터셉터가 돌지 않으므로 접속기록을 직접 남김
if (Request::isAjax()) {
$this->writePrivacyLog();
$this->getView()->setData($this->getData());
echo $this->getView()->render('epostReturnResult');
exit;
/**
* 수거신청 대상 조회 접속기록
*
* @deprecated
*
* @param array $rows 조회된 대상 행
* @param int $total 검색 건수
*/
protected function writeAccessLog(array $rows, int $total): void
{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
}
 
Controller/Admin/Order/EpostReturnRequestPsController.php+3-30
변경 전EpostReturnRequestPsController.php
use Component\Order\EpostReturnAdmin;
use Origin\DTO\Order\EpostReturnRequestResultDTO;
use Origin\Exception\Order\EpostReturnRequestRejectedException;
use Origin\Service\Order\EpostReturnAccessLogService;
use Request;
use Throwable;
 
*/
class EpostReturnRequestPsController extends \Controller\Admin\Controller
{
private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 수거신청';
private const LOG_PAGE = '우체국택배 수거신청';
private const LOG_ACTION = '우체국 수거 예약 전송(고객 정보 조회·전송 포함)';
 
/**
* {@inheritdoc}
*/
$result = \App::getInstance(EpostReturnAdmin::class)
->applyManualRequest((bool)$writable['check'], !gd_is_provider(), $orderGoodsSnos);
 
// 자격 검증을 통과해 전송 단계까지 갔을 때만 남김 — 거부는 예외로 빠져 여기 오지 않음
$this->writePickupRequestAccessLog($orderGoodsSnos, $result);
 
$this->json([
'code' => 200,
'requestedCount' => count($orderGoodsSnos),
$reason = $e->getReason();
$this->json(['code' => 0, 'reason' => $reason->value, 'message' => __($reason->alertTitle()), 'subMessage' => __($reason->alertSubtitle())]);
} catch (Throwable $e) {
// 전송 도중 실패라도 고객 정보 조회·전송이 이미 일어났을 수 있어 접속기록은 남김(신청 결과가 없으니 대상 목록만)
\App::getInstance('logger')->channel('epost')->error('우체국택배 수거 신청 실패', ['orderGoodsSnos' => $orderGoodsSnos, 'exception' => $e]);
$this->writePickupRequestAccessLog($orderGoodsSnos, null);
$this->json(['code' => 0, 'reason' => 'ERROR', 'message' => __('수거 신청에 실패하였습니다.'), 'subMessage' => __('잠시 후 다시 시도해 주세요.')]);
}
}
 
/**
* 수거 신청 접속기록 — 실행당 1건, 처리 대상은 data 에 담음
 
 
*
* @param array $orderGoodsSnos 신청 대상 주문상품번호
* @param ?EpostReturnRequestResultDTO $result 신청 결과 — 전송 도중 실패로 없으면 null
*/
protected function writePickupRequestAccessLog(array $orderGoodsSnos, ?EpostReturnRequestResultDTO $result): void
{
$session = \App::getInstance('session');
 
\App::getInstance(EpostReturnAccessLogService::class)->write(
__(self::LOG_MENU),
__(self::LOG_PAGE),
__(self::LOG_ACTION),
(string)$session->get('manager.sno'),
(string)$session->get('manager.managerId'),
(string)$session->get('manager.scmNo'),
preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')),
(string)Request::getReferer(),
(string)Request::getRemoteAddress(),
$result !== null ? [
'orderGoodsSnos' => $orderGoodsSnos,
'successCount' => $result->getSuccessCount(),
'failureCount' => $result->getFailureCount(),
'excludedCount' => count($result->getExcludedOrderGoodsSnos()),
] : ['orderGoodsSnos' => $orderGoodsSnos, 'error' => true]
);
}
}
 
변경 후EpostReturnRequestPsController.php
use Component\Order\EpostReturnAdmin;
use Origin\DTO\Order\EpostReturnRequestResultDTO;
use Origin\Exception\Order\EpostReturnRequestRejectedException;
 
use Request;
use Throwable;
 
*/
class EpostReturnRequestPsController extends \Controller\Admin\Controller
{
 
 
 
 
/**
* {@inheritdoc}
*/
$result = \App::getInstance(EpostReturnAdmin::class)
->applyManualRequest((bool)$writable['check'], !gd_is_provider(), $orderGoodsSnos);
 
 
 
 
$this->json([
'code' => 200,
'requestedCount' => count($orderGoodsSnos),
$reason = $e->getReason();
$this->json(['code' => 0, 'reason' => $reason->value, 'message' => __($reason->alertTitle()), 'subMessage' => __($reason->alertSubtitle())]);
} catch (Throwable $e) {
 
\App::getInstance('logger')->channel('epost')->error('우체국택배 수거 신청 실패', ['orderGoodsSnos' => $orderGoodsSnos, 'exception' => $e]);
 
$this->json(['code' => 0, 'reason' => 'ERROR', 'message' => __('수거 신청에 실패하였습니다.'), 'subMessage' => __('잠시 후 다시 시도해 주세요.')]);
}
}
 
/**
* 수거 신청 접속기록
*
* @deprecated
*
* @param array $orderGoodsSnos 신청 대상 주문상품번호
* @param ?EpostReturnRequestResultDTO $result 신청 결과 — 전송 도중 실패로 없으면 null
*/
protected function writePickupRequestAccessLog(array $orderGoodsSnos, ?EpostReturnRequestResultDTO $result): void
{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
}
 
Controller/Admin/Order/OrderPsController.php+12-1
변경 전OrderPsController.php
try {
$excelOrderConvert = App::load('Component\\Excel\\ExcelOrderConvert');
$result = $excelOrderConvert->updateOrderInvoiceExcel(Request::files()->toArray());
 
 
 
 
$this->json($result);
exit;
} catch (Exception $e) {
case 'register_frequency':
try {
$postValue = StringUtils::xssArrayClean(Request::request()->toArray());
$order->registerFrequencyAddress($postValue);
 
 
 
 
 
throw new LayerException();
 
} catch (Exception $e) {
 
// 주문 저장 후 처리
if ($result) {
 
 
// Kafka MQ처리
$mqData = [
'cartInfo' => ['cartSnos' => is_array($cart->cartSno) ? $cart->cartSno : [$cart->cartSno]],
 
변경 후OrderPsController.php
try {
$excelOrderConvert = App::load('Component\\Excel\\ExcelOrderConvert');
$result = $excelOrderConvert->updateOrderInvoiceExcel(Request::files()->toArray());
// 그룹코드를 돌려받지 못하면 처리 전 기록만 남긴다
if (is_array($result) && !empty($result['groupCd'])) {
$this->attachPrivacyLogResult([$result['groupCd']], $result['total'] ?? null);
}
$this->json($result);
exit;
} catch (Exception $e) {
case 'register_frequency':
try {
$postValue = StringUtils::xssArrayClean(Request::request()->toArray());
$result = $order->registerFrequencyAddress($postValue);
// 등록은 새로 생긴 주소 번호, 수정은 요청한 주소 번호를 남기고, 번호를 받지 못하면 처리 전 기록만 남긴다
$frequencySno = empty($postValue['sno']) ? $result : $postValue['sno'];
if (is_numeric($frequencySno)) {
$this->attachPrivacyLogResult([(string) $frequencySno]);
}
throw new LayerException();
 
} catch (Exception $e) {
 
// 주문 저장 후 처리
if ($result) {
$this->attachPrivacyLogResult([$order->orderNo]);
 
// Kafka MQ처리
$mqData = [
'cartInfo' => ['cartSnos' => is_array($cart->cartSno) ? $cart->cartSno : [$cart->cartSno]],
 
Controller/Admin/Order/PostPsController.php+16-24
변경 전PostPsController.php
use Framework\Debug\Exception\LayerException;
use Framework\Debug\Exception\AlertRedirectException;
use Message;
use Origin\Service\Order\EpostReturnAccessLogService;
use Request;
 
/**
*/
class PostPsController extends \Controller\Admin\Controller
{
private const LOG_MENU = '주문/배송>택배연동 서비스>우체국택배 신청/관리';
private const LOG_PAGE = '우체국택배 신청/관리';
private const LOG_ACTION = '배송상태 수동 업데이트';
 
/**
* index
*
}
 
$result = $orderAdmin->saveOrderInvoice($postValue);
 
 
 
 
 
 
 
 
if(gd_count($result) > 0 ) {
throw new LayerException(sprintf(__("필수 정보(수령자명,수령자 핸드폰번호,수령자 주소,수령자 우편번호,주문자명) 가 누락된 주문(%s) 외 우체국 택배 송장번호가 발급되었습니다."), gd_implode(",",$result)),null,null,null,6000);
throw new LayerException(__('우체국택배 서비스 신청 후 이용 가능합니다.'));
}
 
$orderAdmin->reserveOrderInvoice($postValue);
 
 
 
 
throw new LayerException(__("우체국택배로 예약되었습니다."));
break;
// 우체국택배 취소
$updated = (int)\App::load('\\Component\\Godo\\GodoPostServerApi')->saveManualUpdate();
 
// 반품(02) 재동기화 — 연동 신청을 마친 상점에만 조회할 이력이 생김.
// 실패해도 이미 커밋된 발송(01) 갱신의 감사기록·응답은 이어가야 해 여기서 막음(원인은 resync() 자신이 로깅함)
$epostApprovalNo = (string)(gd_policy(EpostReturnAdmin::POLICY_KEY)['compdivcd'] ?? '');
if ($epostApprovalNo !== '') {
try {
}
}
 
$this->writeManualUpdateAccessLog($updated, $epostApprovalNo !== '');
 
$this->json(['code' => 200, 'updatedCount' => $updated]);
 
return;
}
 
/**
* 배송상태 수동 업데이트 접속기록 — 실행당 1건, 처리 대상은 data 에 담음
 
 
*
* @param int $updatedCount 발송(01) 갱신 건수
* @param bool $returnResync 반품(02) 재동기화 실행 여부
*/
protected function writeManualUpdateAccessLog(int $updatedCount, bool $returnResync): void
{
$session = \App::getInstance('session');
 
\App::getInstance(EpostReturnAccessLogService::class)->write(
__(self::LOG_MENU),
__(self::LOG_PAGE),
__(self::LOG_ACTION),
(string)$session->get('manager.sno'),
(string)$session->get('manager.managerId'),
(string)$session->get('manager.scmNo'),
preg_replace('#^/{2,}#', '/', (Request::getRequestUri() ?? '')),
(string)Request::getReferer(),
(string)Request::getRemoteAddress(),
['updatedCount' => $updatedCount, 'returnResync' => $returnResync ? 'y' : 'n']
);
}
}
 
변경 후PostPsController.php
use Framework\Debug\Exception\LayerException;
use Framework\Debug\Exception\AlertRedirectException;
use Message;
 
use Request;
 
/**
*/
class PostPsController extends \Controller\Admin\Controller
{
 
 
 
 
/**
* index
*
}
 
$result = $orderAdmin->saveOrderInvoice($postValue);
// 발급한 주문번호를 돌려받지 못하면 처리 전 기록만 남긴다
$issuedOrderNos = method_exists($orderAdmin, 'getIssuedInvoiceOrderNos')
? $orderAdmin->getIssuedInvoiceOrderNos()
: [];
if (!empty($issuedOrderNos)) {
$this->attachPrivacyLogResult($issuedOrderNos);
}
 
if(gd_count($result) > 0 ) {
throw new LayerException(sprintf(__("필수 정보(수령자명,수령자 핸드폰번호,수령자 주소,수령자 우편번호,주문자명) 가 누락된 주문(%s) 외 우체국 택배 송장번호가 발급되었습니다."), gd_implode(",",$result)),null,null,null,6000);
throw new LayerException(__('우체국택배 서비스 신청 후 이용 가능합니다.'));
}
 
$reservedOrderNos = $orderAdmin->reserveOrderInvoice($postValue);
// 예약한 주문번호를 돌려받지 못하면 처리 전 기록만 남긴다
if (!empty($reservedOrderNos)) {
$this->attachPrivacyLogResult($reservedOrderNos);
}
throw new LayerException(__("우체국택배로 예약되었습니다."));
break;
// 우체국택배 취소
$updated = (int)\App::load('\\Component\\Godo\\GodoPostServerApi')->saveManualUpdate();
 
// 반품(02) 재동기화 — 연동 신청을 마친 상점에만 조회할 이력이 생김.
// 실패해도 이미 커밋된 발송(01) 갱신의 응답은 이어가야 해 여기서 막음(원인은 resync() 자신이 로깅함)
$epostApprovalNo = (string)(gd_policy(EpostReturnAdmin::POLICY_KEY)['compdivcd'] ?? '');
if ($epostApprovalNo !== '') {
try {
}
}
 
 
 
$this->json(['code' => 200, 'updatedCount' => $updated]);
 
return;
}
 
/**
* 배송상태 수동 업데이트 접속기록
*
* @deprecated
*
* @param int $updatedCount 발송(01) 갱신 건수
* @param bool $returnResync 반품(02) 재동기화 실행 여부
*/
protected function writeManualUpdateAccessLog(int $updatedCount, bool $returnResync): void
{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
}
 
Controller/Admin/Share/LayerExcelPsController.php+5-1
변경 전LayerExcelPsController.php
$request->request()->set($fileInfo['menu'] . 'Log', true);
$request->post()->set('fileName', $fileName[$postValue['excelKey']]);
$request->post()->set('downloadFileName', $fileInfo['downloadFileName']);
$request->post()->set('totalCount', $fileInfo['totalCount']);
 
 
 
 
$logAction = \App::load('Component\\Admin\\AdminLogDAO');
$logAction->setAdminLog();
}
 
변경 후LayerExcelPsController.php
$request->request()->set($fileInfo['menu'] . 'Log', true);
$request->post()->set('fileName', $fileName[$postValue['excelKey']]);
$request->post()->set('downloadFileName', $fileInfo['downloadFileName']);
$downloadTotalCount = $fileInfo['totalCount'] ?? null;
if (method_exists($excelRequest, 'getDownloadTotalCount')) {
$downloadTotalCount = $excelRequest->getDownloadTotalCount($fileInfo, $postValue['excelKey']);
}
$request->post()->set('totalCount', $downloadTotalCount);
$logAction = \App::load('Component\\Admin\\AdminLogDAO');
$logAction->setAdminLog();
}
 
공급사 계정 다른상품교환 처리 개선
Admin/order/layout_order_view_status_exchange.php+2-1
변경 전layout_order_view_status_exchange.php
//다른상품 교환
$.each($("input[name*='bundle[statusCheck]']"), function () {
var enurikey = $(this).val().concat($(this).attr("data-goods-no"));
var enuriVal = $(this).closest('tr').find("input[name='enuri[]']").val();
 
$('#exchangeForm').append("<input type='hidden' name='enuri["+enurikey+"]' value='"+enuriVal+"' />");
});
}
 
변경 후layout_order_view_status_exchange.php
//다른상품 교환
$.each($("input[name*='bundle[statusCheck]']"), function () {
var enurikey = $(this).val().concat($(this).attr("data-goods-no"));
var enuriInput = $(this).closest('tr').find("input[name='enuri[]']");
var enuriVal = enuriInput.length > 0 ? enuriInput.val() : '0';
$('#exchangeForm').append("<input type='hidden' name='enuri["+enurikey+"]' value='"+enuriVal+"' />");
});
}
 
Component/Order/ReOrderCalculation.php+7-1
변경 전ReOrderCalculation.php
$deliveryMethodFl = '';
foreach ($dVal as $gKey => $gVal) {
$gVal['orderNo'] = $data['orderNo'];
$gVal['enuri'] = $data['enuri'][$gVal['sno'] . $gVal['goodsNo']];
 
 
 
 
 
 
$gVal['mallSno'] = $orderData['mallSno'];
$gVal['orderCd'] = ++$orderCd;
$gVal['orderStatus'] = $changeStatus;
 
변경 후ReOrderCalculation.php
$deliveryMethodFl = '';
foreach ($dVal as $gKey => $gVal) {
$gVal['orderNo'] = $data['orderNo'];
$gVal['enuri'] = Manager::isProvider() ? 0 : (int)$data['enuri'][$gVal['sno'] . $gVal['goodsNo']];
if ($gVal['enuri'] < 0) {
throw new Exception(__('운영자 추가 할인 실행을 실패하였습니다.[금액이 0보다 작음]'));
}
if ($gVal['enuri'] > $gVal['price']['goodsPriceSubtotal']) {
throw new Exception(__('운영자 추가 할인 실행을 실패하였습니다.[상품결제가보다 금액이 큼]'));
}
$gVal['mallSno'] = $orderData['mallSno'];
$gVal['orderCd'] = ++$orderCd;
$gVal['orderStatus'] = $changeStatus;
 
네이버 판매지수 집계 정확도 개선
Component/Worker/NaverDbUrl.php+1
변경 전NaverDbUrl.php
$data = [];
 
foreach ($getData as $val) {
 
if ($val['regDt'] == $val['handleDt']) {
$goodsPrice = $val['goodsPrice'] - $val['refundPrice'];
$goodsCnt = $val['goodsCnt'] - $val['goodsHandleCnt'];
 
변경 후NaverDbUrl.php
$data = [];
 
foreach ($getData as $val) {
$goodsPrice = $goodsCnt = $orderCnt = 0;
if ($val['regDt'] == $val['handleDt']) {
$goodsPrice = $val['goodsPrice'] - $val['refundPrice'];
$goodsCnt = $val['goodsCnt'] - $val['goodsHandleCnt'];
 
선물하기 개인정보 보안 강화
Component/Cart/Cart.php+182-4
변경 전Cart.php
use Framework\Utility\ArrayUtils;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
 
 
use Origin\Service\Statistics\Analytics\Cart\CartAddHistoryAnalyticsCollectorService;
use Framework\Utility\SkinUtils;
use Framework\Utility\StringUtils;
const CART_GOODS_TYPE_GOODS = 'goods';
const CART_GOODS_TYPE_ADDGOODS = 'addGoods';
 
 
 
/**
* @var null|object 디비 접속
*/
}
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 장바구니 상품 삭제
*
$arrBind['param'] = 'sno IN (' . gd_implode(' , ', $param) . ') AND siteKey = ?';
$this->db->bind_param_push($arrBind['bind'], 's', $this->siteKey);
}
 
 
 
 
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
// 장바구니 갯수 변경 처리
// 장바구니 bind 데이터로 goodsNo 조회 - 갯수 변경 처리를 위해 추출
$changeGoodsNoArray = $this->checkCartSelectGoodsNo(null, $arrBind);
 
 
 
 
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
// 장바구니 갯수 변경 처리
$this->setDeleteDirectCart();
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 장바구니 유지기간 초과 상품 삭제
*/
public function setCartDeletePeriod()
{
if ($this->cartPolicy['periodFl'] === 'y' && $this->cartPolicy['periodDay'] > 0) {
$periodDate = date('Y-m-d H:i:s', strtotime('-' . $this->cartPolicy['periodDay'] . ' day'));
$arrBind['param'] = 'regDt < ?';
$this->db->bind_param_push($arrBind['bind'], 's', $periodDate);
// 장바구니 삭제 전 쿠폰 장바구니사용 상태 되돌리기
$cartCouponData = $this->getCartMemberCoupon($arrBind);
foreach ($cartCouponData as $cartKey => $cartVal) {
// 장바구니 bind 데이터로 goodsNo 조회 - 갯수 변경 처리를 위해 추출
$changeGoodsNoArray = $this->checkCartSelectGoodsNo(null, $arrBind);
 
 
 
 
// 장바구니 삭제
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
 
$strSQL = "SELECT * FROM " . $this->tableName . " WHERE siteKey = ? AND goodsNo = ? AND optionSno = ?" . $strDirectSQL . " ORDER BY directCart DESC, regDt ASC, modDt ASC";
$mergeList = $this->db->query_fetch($strSQL, $arrBind);
 
 
 
 
$tempCnt = 0;
$tempOptionText = '';
$tempAddNo = '';
 
$strSQL = "SELECT * FROM " . $this->tableName . " WHERE memNo = ? AND goodsNo = ? AND optionSno = ? AND optionText = ?" . $strDirectSQL . $isWriteAddSql . " ORDER BY directCart DESC, regDt ASC, modDt ASC";
$mergeList = $this->db->query_fetch($strSQL, $arrBind);
 
 
 
 
$tempCnt = 0;
$tempOptionText = '';
$tempAddNo = '';
 
변경 후Cart.php
use Framework\Utility\ArrayUtils;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
use Origin\Service\Cart\CartDeletePeriodPolicyService;
use Origin\Service\Present\PresentCartPrivacyDestroyService;
use Origin\Service\Statistics\Analytics\Cart\CartAddHistoryAnalyticsCollectorService;
use Framework\Utility\SkinUtils;
use Framework\Utility\StringUtils;
const CART_GOODS_TYPE_GOODS = 'goods';
const CART_GOODS_TYPE_ADDGOODS = 'addGoods';
 
const PRESENT_PRIVACY_DESTROY_LOG_SNO_LIMIT = 50;
 
/**
* @var null|object 디비 접속
*/
}
}
 
/**
* 삭제 조건에 해당하는 장바구니 sno 목록 조회
*
* @param array $arrBind where 조건 배열
*
* @return array 장바구니 sno 목록
*/
protected function getCartSnoListByBind($arrBind)
{
if (is_array($arrBind['param'])) {
$addWhere = implode(' AND ', $arrBind['param']);
} else {
$addWhere = $arrBind['param'];
}
 
$strSQL = 'SELECT sno FROM ' . $this->tableName . ' WHERE ' . $addWhere;
$cartData = $this->db->query_fetch($strSQL, $arrBind['bind'], true);
 
return array_column((array) $cartData, 'sno');
}
 
/**
* 현재 장바구니 테이블이 선물하기 수령자 개인정보 파기 대상인지 판정
*
* 판정 자체는 Origin 정책이 소유한다. 여기서 테이블명을 직접 비교하면 상점 튜닝으로 가드를 바꿔
* 파기 대상을 조용히 넓히거나 좁힐 수 있다.
*
* @return bool 파기 대상 테이블 여부
*/
protected function isPresentCartPrivacyTable()
{
return \App::getInstance(PresentCartPrivacyDestroyService::class)
->isPrivacyLinkedCartTable($this->tableName);
}
 
/**
* 선물하기 수령자 개인정보(es_presentCart) 파기
*
* @param array $arrBind 삭제 대상 where 조건 배열
*
* @return int 파기 건수
*/
protected function destroyPresentCartPrivacy($arrBind)
{
$cartSnoList = [];
try {
// 파기 대상 테이블이 아니면 조회 자체를 하지 않는다
if (!$this->isPresentCartPrivacyTable()) {
return 0;
}
 
$cartSnoList = $this->getCartSnoListByBind($arrBind);
 
return $this->destroyPresentCartPrivacyBySnos($cartSnoList);
} catch (\Throwable $e) {
// 결제 완료 직후 장바구니 비우기가 이 경로를 타므로, 파기 실패로 사용자 동선을 끊지 않는다
$this->logPresentCartPrivacyDestroyFailure($cartSnoList, $e);
return 0;
}
}
 
/**
* 장바구니 sno 목록 기준 선물하기 수령자 개인정보 파기
*
* 삭제가 루프 안에서 건별로 일어나는 경로는 sno 를 먼저 모아 여기로 한 번에 넘겨야 N+1 이 되지 않는다.
*
* @param array $cartSnoList 삭제 대상 장바구니 sno 목록
*
* @return int 파기 건수
*/
protected function destroyPresentCartPrivacyBySnos($cartSnoList)
{
try {
if (empty($cartSnoList) || !$this->isPresentCartPrivacyTable()) {
return 0;
}
 
return \App::getInstance(PresentCartPrivacyDestroyService::class)->destroyByCartSnos($cartSnoList);
} catch (\Throwable $e) {
// 결제 완료 직후 장바구니 비우기가 이 경로를 타므로, 파기 실패로 사용자 동선을 끊지 않는다
$this->logPresentCartPrivacyDestroyFailure($cartSnoList, $e);
return 0;
}
}
 
/**
* 선물하기 수령자 개인정보 파기 실패 기록
*
* 실시간 사용자 경로는 파기 실패를 삼키므로, 이 로그가 잔존 개인정보를 되짚을 유일한 근거다.
* 대상이 대량일 수 있어 전체 목록 대신 건수와 앞부분만 남긴다.
*
* @param array $cartSnoList 파기 대상 장바구니 sno 목록
* @param \Throwable $e 파기 중 발생한 예외
*/
protected function logPresentCartPrivacyDestroyFailure($cartSnoList, $e)
{
$cartSnoList = (array) $cartSnoList;
 
\Logger::channel('cart')->error('선물하기 수령자 개인정보 파기 실패', [
'exception' => $e,
'errorMessage' => $e->getMessage(),
'cartTable' => $this->tableName,
'cartSnoCnt' => count($cartSnoList),
'cartSnos' => array_slice($cartSnoList, 0, self::PRESENT_PRIVACY_DESTROY_LOG_SNO_LIMIT),
]);
}
 
/**
* 장바구니 병합 시 삭제될 sno 목록 추출
*
* 병합 루프는 첫 레코드만 남기고 두 번째부터 삭제한다. 삭제 판정을 루프와 동일하게 맞춰야
* 실제로 지워지지 않을 장바구니의 개인정보까지 파기하지 않는다.
*
* @param array $mergeList 병합 대상 장바구니 목록
*
* @return array 삭제 대상 장바구니 sno 정수 배열
*/
protected function getMergeDeleteCartSnoList($mergeList)
{
$deleteCartSnoList = [];
foreach ((array) $mergeList as $mergeKey => $mergeVal) {
if ($mergeVal['optionText']) {
continue;
}
 
if ($mergeKey > 0) {
$deleteCartSnoList[] = (int) $mergeVal['sno'];
}
}
 
return $deleteCartSnoList;
}
 
/**
* 장바구니 상품 삭제
*
$arrBind['param'] = 'sno IN (' . gd_implode(' , ', $param) . ') AND siteKey = ?';
$this->db->bind_param_push($arrBind['bind'], 's', $this->siteKey);
}
 
// 개인정보를 먼저 파기한 뒤 장바구니 본체를 삭제한다
$this->destroyPresentCartPrivacy($arrBind);
 
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
// 장바구니 갯수 변경 처리
// 장바구니 bind 데이터로 goodsNo 조회 - 갯수 변경 처리를 위해 추출
$changeGoodsNoArray = $this->checkCartSelectGoodsNo(null, $arrBind);
 
// 개인정보를 먼저 파기한 뒤 장바구니 본체를 삭제한다
$this->destroyPresentCartPrivacy($arrBind);
 
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
// 장바구니 갯수 변경 처리
$this->setDeleteDirectCart();
}
 
/**
* 장바구니 유지기간 초과 삭제 조건 조회
*
* 만료 기준 산출은 Origin 정책 서비스가 소유한다. 선행 파기와 실제 삭제가 같은 기준시각을 쓰는 것은
* 정책 서비스의 메모이즈로 보장되므로 여기서 기준시각을 다시 계산하면 안 된다.
*
* @return array 유지기간 정책이 꺼져 있으면 빈 배열, 켜져 있으면 where 조건 배열
*/
protected function getCartDeletePeriodBind()
{
// gd_policy 는 설정이 없으면 배열이 아닌 값을 돌려주므로 정책 서비스에 그대로 넘기지 않는다
$cartPolicy = is_array($this->cartPolicy) ? $this->cartPolicy : null;
 
$expiryDate = \App::getInstance(CartDeletePeriodPolicyService::class)->getExpiryDate($cartPolicy);
if ($expiryDate === null) {
return [];
}
 
$arrBind = [];
$arrBind['param'] = 'regDt < ?';
$this->db->bind_param_push($arrBind['bind'], 's', $expiryDate);
 
return $arrBind;
}
 
/**
* 장바구니 유지기간 초과 상품 삭제
*/
public function setCartDeletePeriod()
{
$arrBind = $this->getCartDeletePeriodBind();
if (!empty($arrBind)) {
 
 
// 장바구니 삭제 전 쿠폰 장바구니사용 상태 되돌리기
$cartCouponData = $this->getCartMemberCoupon($arrBind);
foreach ($cartCouponData as $cartKey => $cartVal) {
// 장바구니 bind 데이터로 goodsNo 조회 - 갯수 변경 처리를 위해 추출
$changeGoodsNoArray = $this->checkCartSelectGoodsNo(null, $arrBind);
 
// 개인정보를 먼저 파기한 뒤 장바구니 본체를 삭제한다
$this->destroyPresentCartPrivacy($arrBind);
 
// 장바구니 삭제
$this->db->set_delete_db($this->tableName, $arrBind['param'], $arrBind['bind']);
 
 
$strSQL = "SELECT * FROM " . $this->tableName . " WHERE siteKey = ? AND goodsNo = ? AND optionSno = ?" . $strDirectSQL . " ORDER BY directCart DESC, regDt ASC, modDt ASC";
$mergeList = $this->db->query_fetch($strSQL, $arrBind);
 
// 병합 루프가 건별로 지우므로, 개인정보는 루프 진입 전에 일괄로 먼저 파기한다
$this->destroyPresentCartPrivacyBySnos($this->getMergeDeleteCartSnoList($mergeList));
 
$tempCnt = 0;
$tempOptionText = '';
$tempAddNo = '';
 
$strSQL = "SELECT * FROM " . $this->tableName . " WHERE memNo = ? AND goodsNo = ? AND optionSno = ? AND optionText = ?" . $strDirectSQL . $isWriteAddSql . " ORDER BY directCart DESC, regDt ASC, modDt ASC";
$mergeList = $this->db->query_fetch($strSQL, $arrBind);
 
// 병합 루프가 건별로 지우므로, 개인정보는 루프 진입 전에 일괄로 먼저 파기한다
$this->destroyPresentCartPrivacyBySnos($this->getMergeDeleteCartSnoList($mergeList));
 
$tempCnt = 0;
$tempOptionText = '';
$tempAddNo = '';
 
Component/Goods/GoodsAdmin.php+21
변경 전GoodsAdmin.php
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Origin\Service\Marketing\Google\GoogleGoodsFeedService;
 
use Origin\Service\WebHook\GoodsWebHookService;
use Globals;
use LogHandler;
$this->deleteGoodsOptionObsImage($goodsNo);
}
 
 
 
 
foreach ($arrGoodsTable as $goodsTableNm) {
if ($goodsTableNm == DB_SEO_TAG) {
$this->db->set_delete_db($goodsTableNm, 'pageCode = ?', $this->arrBind);
$goodsWebHookService->sendGoodsWebHook($goodsNo, 'DELETED');
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
/**
* 상품의 OBS 이미지 제거
* @param $goodsNo
 
변경 후GoodsAdmin.php
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Origin\Service\Marketing\Google\GoogleGoodsFeedService;
use Origin\Service\Present\PresentCartPrivacyDestroyService;
use Origin\Service\WebHook\GoodsWebHookService;
use Globals;
use LogHandler;
$this->deleteGoodsOptionObsImage($goodsNo);
}
 
// 상품 삭제는 장바구니를 통째로 지우므로, 삭제 테이블 루프보다 먼저 개인정보를 파기한다
$this->destroyPresentCartPrivacyByGoodsNo($goodsNo);
 
foreach ($arrGoodsTable as $goodsTableNm) {
if ($goodsTableNm == DB_SEO_TAG) {
$this->db->set_delete_db($goodsTableNm, 'pageCode = ?', $this->arrBind);
$goodsWebHookService->sendGoodsWebHook($goodsNo, 'DELETED');
}
 
/**
* 삭제 상품의 장바구니에 연결된 선물하기 수령자 개인정보 파기
*
* 상품 완전삭제는 es_cart 를 goodsNo 기준으로 통째로 지우므로, 파기하지 않으면 es_presentCart 의
* 수령자 개인정보가 참조할 장바구니 없이 영구히 남는다.
*
* 파기 대상 조회는 Origin 서비스가 소유한다. 여기서 직접 조회하면 상점 튜닝으로 대상이 좁혀질 수 있다.
*
* @param integer $goodsNo 상품번호
*
* @return int 파기 건수
*/
protected function destroyPresentCartPrivacyByGoodsNo($goodsNo)
{
return App::getInstance(PresentCartPrivacyDestroyService::class)->destroyByGoodsNo((int) $goodsNo);
}
 
/**
* 상품의 OBS 이미지 제거
* @param $goodsNo
 
Component/Member/Member.php+5
변경 전Member.php
use Bundle\Component\Captcha\Captcha;
use Bundle\Component\Godo\GodoKakaoServerApi;
use Origin\Service\Captcha\AltchaService;
 
use Bundle\Component\Policy\KakaoLoginPolicy;
use Component\Database\DBTableField;
use Component\Member\Company\CompanyCertification;
'i',
$memNo,
];
 
 
 
 
$this->db->set_delete_db(DB_MEMBER, 'memNo = ?', $arrBind);
$this->db->set_delete_db(DB_MEMBER_MILEAGE, 'memNo = ?', $arrBind);
}
 
변경 후Member.php
use Bundle\Component\Captcha\Captcha;
use Bundle\Component\Godo\GodoKakaoServerApi;
use Origin\Service\Captcha\AltchaService;
use Origin\Service\Member\MemberHackOutPrivacyDestroyService;
use Bundle\Component\Policy\KakaoLoginPolicy;
use Component\Database\DBTableField;
use Component\Member\Company\CompanyCertification;
'i',
$memNo,
];
 
// 개인정보를 먼저 파기한 뒤 회원 본체를 삭제한다
\App::getInstance(MemberHackOutPrivacyDestroyService::class)->destroyByMemNo((int) $memNo);
 
$this->db->set_delete_db(DB_MEMBER, 'memNo = ?', $arrBind);
$this->db->set_delete_db(DB_MEMBER_MILEAGE, 'memNo = ?', $arrBind);
}
 
상품 수정시 정가/매입가/판매가 입력 안정성 개선
Component/Goods/GoodsAdmin.php+10-2
변경 전GoodsAdmin.php
 
const ECT_INVALID_ARG = 'GoodsAdmin.ECT_INVALID_ARG';
 
 
 
 
const TEXT_REQUIRE_VALUE = '%s은(는) 필수 항목 입니다.';
 
const TEXT_USELESS_VALUE = '%s은(는) 사용할 수 없습니다.';
*/
 
//
 
 
$arrData['mileageGroupMemberInfo'] = str_replace('\'', '', json_encode($arrData['mileageGroupMemberInfo'], JSON_UNESCAPED_UNICODE));
$arrData['mileageGroupInfo'] = @gd_implode(INT_DIVISION, $arrData['mileageGroupInfo']);
$arrData['fixedGoodsDiscount'] = @gd_implode(STR_DIVISION, $arrData['fixedGoodsDiscount']);
// 할인값의 천 단위 콤마 제거 (미제거 시 할인가 숫자 연산이 깨짐)
$arrData = NumberUtils::removeCommaByField($arrData, 'goodsDiscount');
 
 
 
$arrData['goodsDiscountGroupMemberInfo'] = NumberUtils::removeCommaByField($arrData['goodsDiscountGroupMemberInfo'], 'goodsDiscount');
$arrData['goodsDiscountGroupMemberInfo'] = str_replace('\'', '', json_encode($arrData['goodsDiscountGroupMemberInfo'], JSON_UNESCAPED_UNICODE));
$arrData['exceptBenefit'] = @gd_implode(STR_DIVISION, $arrData['exceptBenefit']);
 
변경 후GoodsAdmin.php
 
const ECT_INVALID_ARG = 'GoodsAdmin.ECT_INVALID_ARG';
 
/** @var string[] 천 단위 콤마 입력이 허용되어 저장 전 정제가 필요한 금액 필드 */
protected const COMMA_REMOVABLE_AMOUNT_FIELDS = ['goodsPrice', 'fixedPrice', 'costPrice', 'goodsDiscount', 'mileageGoods'];
 
const TEXT_REQUIRE_VALUE = '%s은(는) 필수 항목 입니다.';
 
const TEXT_USELESS_VALUE = '%s은(는) 사용할 수 없습니다.';
*/
 
//
// 회원등급별 마일리지도 회원등급별 할인과 같이 별도 배열이라, 인코딩 전에 정제해야 한다
$arrData['mileageGroupMemberInfo'] = NumberUtils::removeCommaByField($arrData['mileageGroupMemberInfo'], 'mileageGoods');
$arrData['mileageGroupMemberInfo'] = str_replace('\'', '', json_encode($arrData['mileageGroupMemberInfo'], JSON_UNESCAPED_UNICODE));
$arrData['mileageGroupInfo'] = @gd_implode(INT_DIVISION, $arrData['mileageGroupInfo']);
$arrData['fixedGoodsDiscount'] = @gd_implode(STR_DIVISION, $arrData['fixedGoodsDiscount']);
// 금액값의 천 단위 콤마 제거 (미제거 시 DB 저장 과정에서 콤마 앞자리만 남고, 할인가 숫자 연산도 깨짐)
foreach (self::COMMA_REMOVABLE_AMOUNT_FIELDS as $amountField) {
$arrData = NumberUtils::removeCommaByField($arrData, $amountField);
}
// 회원등급별 할인은 별도 배열에 담겨 와 위 루프 대상이 아니다
$arrData['goodsDiscountGroupMemberInfo'] = NumberUtils::removeCommaByField($arrData['goodsDiscountGroupMemberInfo'], 'goodsDiscount');
$arrData['goodsDiscountGroupMemberInfo'] = str_replace('\'', '', json_encode($arrData['goodsDiscountGroupMemberInfo'], JSON_UNESCAPED_UNICODE));
$arrData['exceptBenefit'] = @gd_implode(STR_DIVISION, $arrData['exceptBenefit']);
 
Component/Goods/GoodsBenefit.php+3-1
변경 전GoodsBenefit.php
// 상품할인금액
$goodsDcPrice = $goodsData['goodsPrice'] - gd_number_figure(($goodsData['goodsPrice'] * $discountPercent), $goodsConfigTrunc['unitPrecision'], $goodsConfigTrunc['unitRound']);
} else {
 
 
// 상품금액보다 상품할인금액이 클 경우 상품금액으로 변경
if ($goodsData['goodsDiscount'] > $goodsData['goodsPrice']) $goodsDiscount = $goodsData['goodsPrice'];
// 상품할인금액 (정액인 경우 해당 설정된 금액으로)
$goodsDcPrice = $goodsData['goodsPrice'] - $goodsDiscount;
}
 
변경 후GoodsBenefit.php
// 상품할인금액
$goodsDcPrice = $goodsData['goodsPrice'] - gd_number_figure(($goodsData['goodsPrice'] * $discountPercent), $goodsConfigTrunc['unitPrecision'], $goodsConfigTrunc['unitRound']);
} else {
// 할인금액 초기화 (미초기화 시 null 이라 할인이 반영되지 않음)
$goodsDiscount = NumberUtils::numberFormatToFloat($goodsData['goodsDiscount']) ?: 0;
// 상품금액보다 상품할인금액이 클 경우 상품금액으로 변경
if ($goodsDiscount > $goodsData['goodsPrice']) $goodsDiscount = $goodsData['goodsPrice'];
// 상품할인금액 (정액인 경우 해당 설정된 금액으로)
$goodsDcPrice = $goodsData['goodsPrice'] - $goodsDiscount;
}
 
비즈엠 알림톡 웹링크 버튼 PC 링크 미전송으로 발송 실패(K108)하는 문제 수정
Component/Sms/SmsAuto.php+5
변경 전SmsAuto.php
if ($val['linkType'] === 'WL') {
$val['linkMo'] = str_replace('#{invoiceNo}', $this->replaceArguments['invoiceNo'] ?? '', $val['linkMo']);
$buttonData[$key]['url_mobile'] = $val['linkMo'];
 
 
 
 
 
}
}
if (empty($buttonData)) {
 
변경 후SmsAuto.php
if ($val['linkType'] === 'WL') {
$val['linkMo'] = str_replace('#{invoiceNo}', $this->replaceArguments['invoiceNo'] ?? '', $val['linkMo']);
$buttonData[$key]['url_mobile'] = $val['linkMo'];
// 비즈엠은 템플릿 버튼과 발송 버튼의 완전 일치를 요구하므로, PC 링크 없이 등록된 템플릿(구화면, linkPc='')에는 url_pc 를 보내지 않는다.
if (!empty($val['linkPc'])) {
$val['linkPc'] = str_replace('#{invoiceNo}', $this->replaceArguments['invoiceNo'] ?? '', $val['linkPc']);
$buttonData[$key]['url_pc'] = $val['linkPc'];
}
}
}
if (empty($buttonData)) {
 
상품 엑셀 업로드 시 단위가격 반영 개선
Component/Excel/ExcelGoodsConvert.php+22-1
변경 전ExcelGoodsConvert.php
use Component\Database\DBTableField;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
 
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Framework\Http\Parameter;
$goodsData['goodsNmFl'] = 'e';
}
switch ($dbKey) {
 
 
 
 
 
 
 
 
case 'kcmarkFl':
case 'kcmarkDivFl':
case 'kcmarkNo':
}
 
// 단위 가격 자동 계산 (올림)
$goodsPrice = floatval($goodsData['goodsPrice']);
 
 
 
 
 
 
 
 
 
 
 
 
$calcUnitPrice = ($goodsPrice > 0 && floatval($unitQuantity) > 0)
? ceil(($goodsPrice / floatval($unitQuantity)) * floatval($unitBaseQuantity))
: 0;
 
변경 후ExcelGoodsConvert.php
use Component\Database\DBTableField;
use Framework\Utility\ArrayUtils;
use Framework\Utility\ComponentUtils;
use Framework\Utility\NumberUtils;
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Framework\Http\Parameter;
$goodsData['goodsNmFl'] = 'e';
}
switch ($dbKey) {
case 'goodsPrice':
// 엑셀 셀이 텍스트 서식이면 '10,000' 처럼 콤마가 섞여 들어온다. 콤마를 둔 채 숫자 컬럼에 저장하면
// 앞자리만 남고 잘리므로, 저장과 단위가격 계산이 같은 값을 보도록 여기서 정규화한다
$goodsPrice = NumberUtils::numberFormatToFloat($goodsData[$dbKey]);
if ($goodsPrice !== false) {
$goodsData[$dbKey] = $goodsPrice;
}
break;
case 'kcmarkFl':
case 'kcmarkDivFl':
case 'kcmarkNo':
}
 
// 단위 가격 자동 계산 (올림)
// 판매가는 엑셀 값을 우선 사용하고, 컬럼 누락·권한 제외 등으로 쓸 수 없는 행만 저장이 끝난 DB 값으로 보완한다
$goodsPrice = $this->hasGoodsPriceAuth
? NumberUtils::numberFormatToFloat($goodsData['goodsPrice'] ?? null)
: false;
if ($goodsPrice === false) {
$arrBind = [];
$this->db->bind_param_push($arrBind, 'i', $goodsData['goodsNo']);
$strSQL = 'SELECT goodsPrice FROM ' . DB_GOODS . ' WHERE goodsNo = ?';
$goodsRow = $this->db->query_fetch($strSQL, $arrBind, false);
unset($arrBind);
$goodsPrice = floatval($goodsRow['goodsPrice'] ?? 0);
}
 
$calcUnitPrice = ($goodsPrice > 0 && floatval($unitQuantity) > 0)
? ceil(($goodsPrice / floatval($unitQuantity)) * floatval($unitBaseQuantity))
: 0;
 
엑셀 다운로드 시 자체상품코드 텍스트 서식 적용
Component/Excel/ExcelDataConvert.php+2-2
변경 전ExcelDataConvert.php
$this->excelBody[] = '<td>' . ($key+1) . '</td>' . chr(10);
$this->excelBody[] = '<td class="xl24">' . $resultValue['orderNo'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['orderGroup'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['goodsCd'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['optionCode'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['result'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['message'] . '</td>' . chr(10);
$this->excelBody[] = '</tr>' . chr(10);
 
변경 후ExcelDataConvert.php
$this->excelBody[] = '<td>' . ($key+1) . '</td>' . chr(10);
$this->excelBody[] = '<td class="xl24">' . $resultValue['orderNo'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['orderGroup'] . '</td>' . chr(10);
$this->excelBody[] = '<td class="xl24">' . $resultValue['goodsCd'] . '</td>' . chr(10);
$this->excelBody[] = '<td class="xl24">' . $resultValue['optionCode'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['result'] . '</td>' . chr(10);
$this->excelBody[] = '<td>' . $resultValue['message'] . '</td>' . chr(10);
$this->excelBody[] = '</tr>' . chr(10);
 
Component/Excel/ExcelRequest.php+10-2
변경 전ExcelRequest.php
$conditionType = '';
}
return "<td>" . htmlspecialchars($conditionType, ENT_NOQUOTES) . "</td>";
 
 
 
default:
return "<td>" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
}
$tmpData[] = "<td></td>";
}
 
 
 
 
break;
default :
if ($excelFieldName[$excelValue]['type'] == 'mileage' && $val[$excelValue] != '') {
}
$tmpData[] = "<td>" . $settleKind . "</td>";
break;
// 숫자 처리 - 주문 번호 및 우편번호(5자리) 숫자에 대한 처리
 
case 'orderNo':
case 'apiOrderNo':
case 'apiOrderGoodsNo':
case 'settleKind':
$tmpData[] = "<td>" . $orderDelete->printSettleKind($val[$excelValue]) . "</td>";
break;
// 숫자 처리 - 주문 번호 및 우편번호(5자리) 숫자에 대한 처리
 
case 'orderNo':
case 'apiOrderNo':
case 'apiOrderGoodsNo':
 
변경 후ExcelRequest.php
$conditionType = '';
}
return "<td>" . htmlspecialchars($conditionType, ENT_NOQUOTES) . "</td>";
case 'goodsCd':
return "<td class=\"xl24\">" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
 
default:
return "<td>" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
}
$tmpData[] = "<td></td>";
}
 
break;
case 'goodsCd':
$tmpData[] = "<td class=\"xl24\">" . $val[$excelValue] . "</td>";
break;
default :
if ($excelFieldName[$excelValue]['type'] == 'mileage' && $val[$excelValue] != '') {
}
$tmpData[] = "<td>" . $settleKind . "</td>";
break;
// 숫자 처리 - 주문 번호, 우편번호(5자리), 자체상품코드 등 숫자로 인식되면 안 되는 값
case 'goodsCd':
case 'orderNo':
case 'apiOrderNo':
case 'apiOrderGoodsNo':
case 'settleKind':
$tmpData[] = "<td>" . $orderDelete->printSettleKind($val[$excelValue]) . "</td>";
break;
// 숫자 처리 - 주문 번호, 우편번호(5자리), 자체상품코드 등 숫자로 인식되면 안 되는 값
case 'goodsCd':
case 'orderNo':
case 'apiOrderNo':
case 'apiOrderGoodsNo':
 
Component/Excel/ExcelSample.php+9
변경 전ExcelSample.php
use Framework\Utility\ImageUtils;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Helper\Html;
 
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
 
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 
$this->setHeaderCellStyle($headerLastRow);
$this->setAllCellStyle();
 
 
 
 
 
 
 
 
return $this->spreadsheet;
}
 
 
변경 후ExcelSample.php
use Framework\Utility\ImageUtils;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Helper\Html;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
 
$this->setHeaderCellStyle($headerLastRow);
$this->setAllCellStyle();
 
// 샘플에 새로 입력하는 자체상품코드의 앞자리 0 이 잘리지 않도록 열 서식을 텍스트로 지정한다
$goodsCdIndex = array_search('goodsCd', array_column($excelGoodsField, 'dbKey'), true);
if ($goodsCdIndex !== false) {
$goodsCdColumn = Coordinate::stringFromColumnIndex($goodsCdIndex + 1);
$this->worksheet->getStyle("{$goodsCdColumn}:{$goodsCdColumn}")->getNumberFormat()->setFormatCode(NumberFormat::FORMAT_TEXT);
}
 
return $this->spreadsheet;
}
 
 
정기결제 개인정보 보안 강화
Component/Member/HackOut/HackOutService.php+8
변경 전HackOutService.php
$memberList = $this->hackOutByMemberList($request->post()->get('chk'));
\DB::commit();
} catch (Exception $e) {
 
 
\DB::rollback();
 
 
 
 
 
 
throw $e;
}
 
 
변경 후HackOutService.php
$memberList = $this->hackOutByMemberList($request->post()->get('chk'));
\DB::commit();
} catch (Exception $e) {
// 회원 삭제 과정의 개인정보 파기는 Eloquent(PDO) 로 동작해 이 mysqli 트랜잭션에 참여하지 않는다.
// 롤백해도 이미 파기된 개인정보는 되돌아오지 않으므로, 회원은 남고 개인정보만 사라진 대상을 되짚을 수 있게 남긴다
\DB::rollback();
\Logger::channel('member')->error('관리자 회원 일괄 탈퇴 실패', [
'exception' => $e,
'errorMessage' => $e->getMessage(),
'memNos' => $request->post()->get('chk'),
]);
 
throw $e;
}
 
 
Component/Member/Member.php+1-1
변경 전Member.php
$memNo,
];
 
// 개인정보를 먼저 파기한 뒤 회원 본체를 삭제한다
\App::getInstance(MemberHackOutPrivacyDestroyService::class)->destroyByMemNo((int) $memNo);
 
$this->db->set_delete_db(DB_MEMBER, 'memNo = ?', $arrBind);
 
변경 후Member.php
$memNo,
];
 
// 개인정보를 먼저 파기한 뒤 회원 본체를 삭제한다. 순서가 반대면 memNo 로 잔존 개인정보를 되짚을 수 없다
\App::getInstance(MemberHackOutPrivacyDestroyService::class)->destroyByMemNo((int) $memNo);
 
$this->db->set_delete_db(DB_MEMBER, 'memNo = ?', $arrBind);
 
Component/Member/MemberSleep.php+7
변경 전MemberSleep.php
\DB::commit();
gd_set_policy('member.hackout', ['isMigrations' => 'y']);
} catch (Exception $e) {
 
 
\DB::rollback();
gd_set_policy('member.hackout', ['isMigrations' => 'n']);
 
 
 
 
 
}
}
}
 
변경 후MemberSleep.php
\DB::commit();
gd_set_policy('member.hackout', ['isMigrations' => 'y']);
} catch (Exception $e) {
// 회원 삭제 과정의 개인정보 파기는 Eloquent(PDO) 로 동작해 이 mysqli 트랜잭션에 참여하지 않는다.
// 롤백해도 이미 파기된 개인정보는 되돌아오지 않으므로, 실패를 삼키면 회원은 남고 개인정보만 사라진 상태를 아무도 모르게 된다
\DB::rollback();
gd_set_policy('member.hackout', ['isMigrations' => 'n']);
Logger::channel('memberSleep')->error('휴면회원 탈퇴내역 마이그레이션 실패', [
'exception' => $e,
'errorMessage' => $e->getMessage(),
'memNo' => $item['memNo'] ?? null,
]);
}
}
}
 
모바일 메시지 대체 발송 미리보기 숏링크 치환 개선
Admin/crm/preview_mobile_send/layer_preview_mobile_send_alternative.php+1-1
변경 전layer_preview_mobile_send_alternative.php
<script>
const alternativeSmsPreview = document.querySelector('.alternative-contents-preview');
$(document).ready(() => {
alternativeSmsPreview.textContent = resolveReplaceCodeForPreview(alternativeSmsPreview.textContent);
 
// 제목은 변수 치환 미지원 — 변수는 공란으로 표기
const alternativeTitlePreview = document.querySelector('.alternative-title-preview');
 
변경 후layer_preview_mobile_send_alternative.php
<script>
const alternativeSmsPreview = document.querySelector('.alternative-contents-preview');
$(document).ready(() => {
alternativeSmsPreview.textContent = resolveReplaceCodeForPreview(alternativeSmsPreview.textContent, false);
 
// 제목은 변수 치환 미지원 — 변수는 공란으로 표기
const alternativeTitlePreview = document.querySelector('.alternative-title-preview');
 
마이앱 단독 발송 시 미리보기 인증번호 입력·수신거부회원 안내 생략
Admin/crm/layer_preview_mobile_send.php+45-41
변경 전layer_preview_mobile_send.php
<?php if ($showAdWarningText === 'y'): ?>
<p class="caution-ad-message">(광고)성 메시지에는 080 수신거부 문구가 필수입니다.</p>
<?php endif; ?>
<div class="message-auth-code-container">
<p class="ncua-modal-content-title">
메시지 인증번호
</p>
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
<tr>
<th class="ncua-required"><div>인증번호</div></th>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input-full-width">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="sendPassword" type="text" value="" placeholder="발송 시에 발급된 인증번호를 입력해 주세요." />
 
 
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
 
 
 
 
</div>
<ul>
<li class="ncua-caution-text">무단으로 메시지 포인트가 사용되지 않도록 메시지 인증번호로 인증 후 발송할 수 있습니다.</li>
<li class="ncua-notice-info">메시지 인증번호는 <a class="ncua-btn ncua-btn--xs ncua-btn--text has-underline" href="https://www.nhn-commerce.com/mygodo/myGodo_shopMain.php" target="_blank">[마이페이지 > 쇼핑몰 관리]</a> 에서 확인할 수 있습니다.</li>
</ul>
</div>
</article>
</div>
<div class="modal-dialog__footer">
</div>
<script type="text/javascript">
const previewPayload = <?= $payloadJson ?>;
 
 
$(document).on('shown.bs.modal', '.modal', function() {
const $slider = $('.js-layer-mobile-preview');
});
 
async function sendMessage() {
// 인증번호 입력 검증
const sendPassword = document.querySelector('input[name="sendPassword"]')?.value?.trim() || '';
if (!sendPassword) {
NCDSValidator.highlight(document.querySelector('input[name="sendPassword"]'));
NCDSAlert({
message: '메시지 인증번호를 입력해 주세요.',
iconType: 'error'
});
return;
}
 
const smsPasswordValidate = await validateSendPassword(sendPassword);
if (!smsPasswordValidate.success) {
NCDSAlert({ message: smsPasswordValidate.message, subMessage: smsPasswordValidate.subMessage, iconType: 'error' });
layer_close();
return;
 
}
 
// 레시피 신규 모드(기존 CRM 그룹 미선택)는 발송 시 그룹이 새로 생성되므로 20개 초과 차단
const isOnlyAgreed = previewPayload.isOnlyAgreeSMSMember ?? true;
 
const targetCounts = getExpectTargetCountSeparated()
if (isOnlyAgreed || targetCounts.rejectedCount === 0) {
await postSendMessage();
} else {
$.post('./layer_reject_recipient_member_info.php', targetCounts)
 
변경 후layer_preview_mobile_send.php
<?php if ($showAdWarningText === 'y'): ?>
<p class="caution-ad-message">(광고)성 메시지에는 080 수신거부 문구가 필수입니다.</p>
<?php endif; ?>
<?php if (!$isPushOnly): ?>
<div class="message-auth-code-container">
<p class="ncua-modal-content-title">
메시지 인증번호
</p>
<div class="ncua-table ncua-table--vertical">
<table>
<tbody>
<tr>
<th class="ncua-required"><div>인증번호</div></th>
<td>
<div>
<div class="ncua-input ncua-input--xs ncua-input-full-width">
<div class="ncua-input__content">
<div class="ncua-input__field ncua-input__field--xs">
<input name="sendPassword" type="text" value="" placeholder="발송 시에 발급된 인증번호를 입력해 주세요." />
</div>
</div>
</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<ul>
<li class="ncua-caution-text">무단으로 메시지 포인트가 사용되지 않도록 메시지 인증번호로 인증 후 발송할 수 있습니다.</li>
<li class="ncua-notice-info">메시지 인증번호는 <a class="ncua-btn ncua-btn--xs ncua-btn--text has-underline" href="https://www.nhn-commerce.com/mygodo/myGodo_shopMain.php" target="_blank">[마이페이지 > 쇼핑몰 관리]</a> 에서 확인할 수 있습니다.</li>
</ul>
</div>
<?php endif; ?>
 
 
 
 
</article>
</div>
<div class="modal-dialog__footer">
</div>
<script type="text/javascript">
const previewPayload = <?= $payloadJson ?>;
const isPushOnly = <?= json_encode($isPushOnly) ?>;
 
$(document).on('shown.bs.modal', '.modal', function() {
const $slider = $('.js-layer-mobile-preview');
});
 
async function sendMessage() {
if (!isPushOnly) {
const sendPassword = document.querySelector('input[name="sendPassword"]')?.value?.trim() || '';
if (!sendPassword) {
NCDSValidator.highlight(document.querySelector('input[name="sendPassword"]'));
NCDSAlert({
message: '메시지 인증번호를 입력해 주세요.',
iconType: 'error'
});
return;
}
 
const smsPasswordValidate = await validateSendPassword(sendPassword);
if (!smsPasswordValidate.success) {
NCDSAlert({ message: smsPasswordValidate.message, subMessage: smsPasswordValidate.subMessage, iconType: 'error' });
layer_close();
return;
}
}
 
// 레시피 신규 모드(기존 CRM 그룹 미선택)는 발송 시 그룹이 새로 생성되므로 20개 초과 차단
const isOnlyAgreed = previewPayload.isOnlyAgreeSMSMember ?? true;
 
const targetCounts = getExpectTargetCountSeparated()
if (isPushOnly || isOnlyAgreed || targetCounts.rejectedCount === 0) {
await postSendMessage();
} else {
$.post('./layer_reject_recipient_member_info.php', targetCounts)
 
판매채널/설정 메뉴 안내 링크 변경
Admin/marketing/marketing_info.php-3
변경 전marketing_info.php
<?php } else { ?>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location);?> <small></small></h3>
<div class="btn-group">
<a href="https://marketing.godo.co.kr/board/list.gd" target="_blank" class="btn btn-white" style="background: url(<?=PATH_ADMIN_GD_SHARE?>img/marketing_notice.png) no-repeat 5% 70%; padding-left:35px;">마케팅 공지사항</a>
</div>
</div>
 
<iframe name='introduce' src='/share/iframe_godo_page.php?menu=<?=$menu?>' frameborder='0' marginwidth='0' marginheight='0' width='100%' height='2100'></iframe>
 
변경 후marketing_info.php
<?php } else { ?>
<div class="page-header js-affix">
<h3><?php echo end($naviMenu->location);?> <small></small></h3>
 
 
 
</div>
 
<iframe name='introduce' src='/share/iframe_godo_page.php?menu=<?=$menu?>' frameborder='0' marginwidth='0' marginheight='0' width='100%' height='2100'></iframe>
 
Admin/policy/naver_pay_config_order.php+2-2
변경 전naver_pay_config_order.php
<div class="table-title gd-help-manual">
<span class="mgt10">네이버페이 주문형 설정/관리</span>
<span class="flo-right">
<a href="https://marketing.godo.co.kr/board/view.gd?postNo=230&tab=3" target="_blank" class="btn btn-red-box js-manage-inform">네이버페이 주문형 연동 방법</a>
</span>
<!--*필독* 네이버페이 버전 설정 안내입니다.
네이버페이 버전을 2.1로 업그레이드 하실 수 있습니다.
<label class="radio-inline"><input type="radio" class="form-control" name="useYn" value="y" <?= gd_isset($naverPayData['checked']['useYn']['y']) ?>/>사용함</label>
<label class="radio-inline"><input type="radio" class="form-control" name="useYn" value="n" <?= gd_isset($naverPayData['checked']['useYn']['n']) ?>/>사용안함</label>
<div class="notice-info">네이버페이 주문형을 아직 신청하지 않은 경우 먼저 신청을 진행해주시기 바랍니다. <a href="https://admin.pay.naver.com/join/step1" target="_blank">바로가기></a></div>
<div class="notice-info">시스템 연동 단계일 경우, 공지사항 참고 부탁드립니다. <a href="https://marketing.nhn-commerce.com/board/view.gd?postNo=230" target="_blank">공지사항 바로가기></a> </div>
</td>
</tr>
<tr>
 
변경 후naver_pay_config_order.php
<div class="table-title gd-help-manual">
<span class="mgt10">네이버페이 주문형 설정/관리</span>
<span class="flo-right">
<a href="https://marketing-help.nhn-commerce.com/ad-channel-guide/naverpay/godomall-enamu-service-integration" target="_blank" class="btn btn-red-box js-manage-inform">네이버페이 주문형 연동 방법</a>
</span>
<!--*필독* 네이버페이 버전 설정 안내입니다.
네이버페이 버전을 2.1로 업그레이드 하실 수 있습니다.
<label class="radio-inline"><input type="radio" class="form-control" name="useYn" value="y" <?= gd_isset($naverPayData['checked']['useYn']['y']) ?>/>사용함</label>
<label class="radio-inline"><input type="radio" class="form-control" name="useYn" value="n" <?= gd_isset($naverPayData['checked']['useYn']['n']) ?>/>사용안함</label>
<div class="notice-info">네이버페이 주문형을 아직 신청하지 않은 경우 먼저 신청을 진행해주시기 바랍니다. <a href="https://admin.pay.naver.com/join/step1" target="_blank">바로가기></a></div>
<div class="notice-info">시스템 연동 단계일 경우, 공지사항 참고 부탁드립니다. <a href="https://marketing-help.nhn-commerce.com/ad-channel-guide/naverpay/godomall-enamu-service-integration" target="_blank">공지사항 바로가기></a> </div>
</td>
</tr>
<tr>
 
회원정보 팝업 SMS 발송 안정성 개선
Admin/share/ncds/member_crm_sms.php+1-1
변경 전member_crm_sms.php
}
 
$('.btn-register').click(function () {
member_sms($('#memNo').val());
});
</script>
 
변경 후member_crm_sms.php
}
 
$('.btn-register').click(function () {
member_sms($('input[name=memNo]').val());
});
</script>
 
엑셀 다운로드 성능 개선 및 통계 기록 확대
Component/Excel/ExcelDataConvert.php+3
변경 전ExcelDataConvert.php
* 상품 엑셀 다운
*
* @author artherot
 
 
*/
public function setExcelGoodsDown($setData)
{
// 엑셀 하단
$this->excelFooter;
 
 
}
 
/**
 
변경 후ExcelDataConvert.php
* 상품 엑셀 다운
*
* @author artherot
* @param array $setData 엑셀 다운로드 요청 데이터 (fieldCheck, downType, downRange 등)
* @return bool 다운로드 대상 필드가 없으면 false
*/
public function setExcelGoodsDown($setData)
{
// 엑셀 하단
$this->excelFooter;
 
return true;
}
 
/**
 
Component/Excel/ExcelRequest.php+2-73
변경 전ExcelRequest.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
use Framework\Utility\ProducerUtils;
use Framework\Utility\StringUtils;
use Framework\Utility\NumberUtils;
use Framework\Security\Token;
use Framework\Debug\Exception\MissingRequiredFieldException;
use LogHandler;
use Origin\DTO\Kafka\ShopDataExtractLogDTO;
use Origin\Enum\Excel\ShopDataExtractTarget;
$orderDelete->deleteLapseOrderExcelFormUpdate($arrData['requestSno'], $arrData['sno']);
$this->_logger->channel('orderDelete')->info(__METHOD__ . ' ORDER DELETE EXCEL FORM SNO SAVE, DB_EXCEL_REQUEST INSERT NO : ' . $arrData['requestSno'] . ', EXCEL FORM SNO : ', [$arrData['formSno']]);
 
$this->publishExtractLog($formData, true, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return $arrData['requestSno'];
}
 
unset($arrBind);
 
$this->publishExtractLog($formData, true, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return true;
} else {
$this->publishExtractLog($formData, false, ShopDataExtractLogDTO::EXTRACT_TYPE_EXCEL);
return false;
}
 
 
 
//특정 갯수 이하인 경우 바로 생성
/*
if($arrData[ $arrData['whereFl'].'Count'] <= self::EXCEL_PAGE_NUM ) {
return true;
}
*/
 
}
 
public function makeExcelFile(&$arrData)
return "<td>" . htmlspecialchars($conditionType, ENT_NOQUOTES) . "</td>";
case 'goodsCd':
return "<td class=\"xl24\">" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
 
default:
return "<td>" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
}
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
 
/**
* 상점 데이터 추출 완료 결과를 카프카 토픽으로 발행한다.
*
* @param ?array $formData 엑셀 양식 데이터 (es_excelForm, menu 포함)
* @param bool $isSuccess 추출 성공 여부
* @param string $extractType 추출 유형 (ShopDataExtractLogDTO::EXTRACT_TYPE_* 상수)
* @return void
*/
protected function publishExtractLog(?array $formData, bool $isSuccess, string $extractType): void
{
//10월 정기배포 전까지 발행되지 않도록 임시조치
return;
try {
$godosno = Globals::get('gLicense.godosno');
if (empty($godosno)) {
throw new MissingRequiredFieldException('상점 데이터 추출 로그 발행 실패: 라이선스 상점번호(godosno)가 없습니다.');
}
 
$menu = $formData['menu'] ?? null;
if (empty($menu)) {
throw new MissingRequiredFieldException('상점 데이터 추출 로그 발행 실패: 엑셀 양식 메뉴(menu)가 없습니다.');
}
 
$extractTarget = ShopDataExtractTarget::fromMenu($menu);
// 매핑 미확정 메뉴(orderDraft, plusreview, adminLog)는 발행하지 않고 건너뛴다. (9월 배포 추가작업 예정)
if ($extractTarget === null) {
$this->_logger->channel('excel')->info(
'상점 데이터 추출 로그 발행 생략: 매핑되지 않은 엑셀 양식 메뉴',
[
'godoSno' => (int)$godosno,
'extractType' => $extractType,
'extractTarget' => $extractTarget->value,
'isSuccess' => $isSuccess
]
);
return;
}
 
$producer = new ProducerUtils();
$shopDataExtractLogDto = new ShopDataExtractLogDTO(
[(int)$godosno],
$extractType,
$extractTarget->value,
$isSuccess
);
 
$producer->send(
$producer::TOPIC_SHOP_DATA_EXTRACT_LOG_CREATE,
json_encode($shopDataExtractLogDto->toArray()),
$producer::MODE_RESULT_CALLLBACK
);
} catch (\Throwable $e) {
$this->_logger->channel('excel')->warning(
'상점 데이터 추출 로그 카프카 메시지 발송 실패',
[
'exception' => $e,
'extractType' => $extractType,
'menu' => $formData['menu'] ?? null
]
);
}
}
}
 
변경 후ExcelRequest.php
use Framework\Utility\ComponentUtils;
use Framework\Utility\ArrayUtils;
use Framework\Utility\DateTimeUtils;
 
use Framework\Utility\StringUtils;
use Framework\Utility\NumberUtils;
use Framework\Security\Token;
 
use LogHandler;
use Origin\DTO\Kafka\ShopDataExtractLogDTO;
use Origin\Enum\Excel\ShopDataExtractTarget;
$orderDelete->deleteLapseOrderExcelFormUpdate($arrData['requestSno'], $arrData['sno']);
$this->_logger->channel('orderDelete')->info(__METHOD__ . ' ORDER DELETE EXCEL FORM SNO SAVE, DB_EXCEL_REQUEST INSERT NO : ' . $arrData['requestSno'] . ', EXCEL FORM SNO : ', [$arrData['formSno']]);
 
 
return $arrData['requestSno'];
}
 
unset($arrBind);
 
 
return true;
 
 
 
}
 
return false;
 
//특정 갯수 이하인 경우 바로 생성
/*
if($arrData[ $arrData['whereFl'].'Count'] <= self::EXCEL_PAGE_NUM ) {
return true;
}
*/
 
}
 
public function makeExcelFile(&$arrData)
return "<td>" . htmlspecialchars($conditionType, ENT_NOQUOTES) . "</td>";
case 'goodsCd':
return "<td class=\"xl24\">" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
 
default:
return "<td>" . htmlspecialchars($value, ENT_NOQUOTES) . "</td>";
}
 
return ['getData' => $setData, 'totalCount' => '$totalNum'];
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
Controller/Admin/Goods/ExcelGoodsPsController.php+20-3
변경 전ExcelGoodsPsController.php
// 상품 엑셀 다운로드
case 'excel_down':
$this->streamedDownload('상품다운로드.xls');
$excel->setExcelGoodsDown($postValue);
// 상품 엑셀 다운로드 본사 & 전체 일 떄 로깅 처리
if ($postValue['downType'] === 'all' && $postValue['scmFl'] === 'n') {
Churn::detectExcelDownloadTotal('goods');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
}
exit;
break;
 
변경 후ExcelGoodsPsController.php
// 상품 엑셀 다운로드
case 'excel_down':
$this->streamedDownload('상품다운로드.xls');
 
// 상품 엑셀 다운로드 본사 & 전체 일 떄 로깅 처리
$isTotalDownload = $postValue['downType'] === 'all' && $postValue['scmFl'] === 'n';
try {
$isDownloadSuccess = $excel->setExcelGoodsDown($postValue);
} catch (\Throwable $e) {
// 권한 없음·쿼리 오류 등으로 중단된 경우도 추출 실패로 남긴다
if ($isTotalDownload) {
Churn::detectExcelDownloadFail('goods');
}
throw $e;
}
if ($isTotalDownload) {
// 튜닝 소스가 반환값 없이 오버라이드한 경우(null)를 실패로 오인하지 않도록 명시적 false 만 실패 처리한다.
if ($isDownloadSuccess === false) {
Churn::detectExcelDownloadFail('goods');
} else {
Churn::detectExcelDownloadTotal('goods');
}
}
exit;
break;
 
Controller/Admin/Share/LayerExcelPsController.php+25-3
변경 전LayerExcelPsController.php
$postValue = $request->post()->toArray();
$excelRequest = \App::load('Component\\Excel\\ExcelRequest');
$logger->info(__METHOD__, $postValue);
 
try {
switch ($postValue['mode']) {
case 'excel':
$data = $excelRequest->saveInfoExcelRequest($postValue);
 
 
 
 
 
 
if ($data === true) {
if ($postValue['whereFl'] == 'total' && ($postValue['menu'] == 'member' || $postValue['menu'] == 'goods')) {
Churn::detectExcelDownloadTotal($postValue['menu']);
}
echo "<script> parent.set_excel_list('" . __('다운로드할 엑셀파일 생성이 완료되었습니다. 파일을 선택하여 다운로드하시기 바랍니다.') . "'); </script>";
} else {
echo "<script> parent.hide_process(); </script>";
}
}
} catch (\Exception $e) {
 
 
 
 
 
$logger->info($e->getMessage(), $e->getTrace());
throw new LayerException($e->getMessage());
}
exit();
}
 
 
 
 
 
 
 
 
 
 
 
 
 
}
 
변경 후LayerExcelPsController.php
$postValue = $request->post()->toArray();
$excelRequest = \App::load('Component\\Excel\\ExcelRequest');
$logger->info(__METHOD__, $postValue);
$isExtractLogPublished = false;
try {
switch ($postValue['mode']) {
case 'excel':
$data = $excelRequest->saveInfoExcelRequest($postValue);
// 조회 결과 0건도 추출 처리 자체는 성공이므로 상품 즉시 다운로드 경로와 같은 기준으로 성공 발행한다
if ($this->isTotalExtractTarget($postValue)) {
Churn::detectExcelDownloadTotal($postValue['menu']);
$isExtractLogPublished = true;
}
 
if ($data === true) {
 
 
 
echo "<script> parent.set_excel_list('" . __('다운로드할 엑셀파일 생성이 완료되었습니다. 파일을 선택하여 다운로드하시기 바랍니다.') . "'); </script>";
} else {
echo "<script> parent.hide_process(); </script>";
}
}
} catch (\Exception $e) {
// 권한 없음·DB 오류로 추출이 중단된 경우도 실패로 남긴다. 성공을 이미 발행했으면 중복으로 보내지 않는다
if (!$isExtractLogPublished && ($postValue['mode'] ?? '') === 'excel' && $this->isTotalExtractTarget($postValue)) {
Churn::detectExcelDownloadFail($postValue['menu']);
}
 
$logger->info($e->getMessage(), $e->getTrace());
throw new LayerException($e->getMessage());
}
exit();
}
 
/**
* 전체 범위 추출 로그 발행 대상인지 여부. 성공·실패 양쪽 호출부가 같은 조건을 쓰도록 묶는다.
*
* @param array $postValue whereFl, menu
*
* @return bool
*/
protected function isTotalExtractTarget(array $postValue): bool
{
return ($postValue['whereFl'] ?? '') == 'total'
&& in_array($postValue['menu'] ?? '', ['member', 'goods'], true);
}
}
 
로그인·카카오싱크 보안 강화.
Component/Member/Member.php+1-1
변경 전Member.php
{
$data['PAGE_URL'] = Request::getDomainUrl() . Request::getRequestUri();
$data['POST'] = Request::post()->toArray();
unset($data['POST']['loginPwd']);
$data['GET'] = Request::get()->toArray();
$data['USER_AGENT'] = Request::getUserAgent();
$data['SESSION'] = \Session::get('member');
 
변경 후Member.php
{
$data['PAGE_URL'] = Request::getDomainUrl() . Request::getRequestUri();
$data['POST'] = Request::post()->toArray();
unset($data['POST']['loginPwd'], $data['POST']['secretKey']);
$data['GET'] = Request::get()->toArray();
$data['USER_AGENT'] = Request::getUserAgent();
$data['SESSION'] = \Session::get('member');
 
Component/Member/Util/MemberUtil.php+14-2
변경 전MemberUtil.php
* @param string $encryptedString 암호화된 로그인 정보
* @param string $key 암호화 키
*
* @return String
* @throws Exception
*/
public function jsDecrypt($encryptedString, $key= self::SECRET_KEY)
{
$json = json_decode(base64_decode($encryptedString), true);
 
 
 
 
 
 
 
 
 
 
 
 
 
try {
$salt = hex2bin($json["salt"]);
$iv = hex2bin($json["iv"]);
}
 
$cipherText = base64_decode($json['ciphertext']);
$iterations = (int)abs($json['iterations']);
if ($iterations <= 0) {
$iterations = 999;
}
 
변경 후MemberUtil.php
* @param string $encryptedString 암호화된 로그인 정보
* @param string $key 암호화 키
*
* @return string|null
* @throws Exception
*/
public function jsDecrypt($encryptedString, $key= self::SECRET_KEY)
{
$json = json_decode(base64_decode($encryptedString), true);
 
// 평문 전송 전환기: 암호문 구조가 아닌 입력은 복호화 불가로 간주 (Warning 방지)
if (!is_array($json) || !isset($json['salt'], $json['iv'], $json['ciphertext'])) {
return null;
}
 
// hex2bin() E_WARNING 방지: salt·iv 가 짝수 길이 16진수 문자열이 아니면 복호화 불가로 간주
foreach (['salt', 'iv'] as $hexKey) {
if (!is_string($json[$hexKey]) || !ctype_xdigit($json[$hexKey]) || strlen($json[$hexKey]) % 2 !== 0) {
return null;
}
}
 
try {
$salt = hex2bin($json["salt"]);
$iv = hex2bin($json["iv"]);
}
 
$cipherText = base64_decode($json['ciphertext']);
$iterations = (int)abs($json['iterations'] ?? 0);
if ($iterations <= 0) {
$iterations = 999;
}
 
Controller/Front/Member/LoginPsController.php+6
변경 전LoginPsController.php
$decryptLoginPwd = $memberUtil->jsDecrypt($request->post()->get('loginPwd'), self::SECRET_KEY);
 
if (empty($decryptLoginPwd) === false) {
 
 
$request->post()->set('loginId', trim($decryptLoginId));
$request->post()->set('loginPwd', trim($decryptLoginPwd));
 
 
 
 
}
 
//카카오 계정탈퇴 혹은 연결해제시 카카오에서 연결끊기 콜백 url 받아 카카오 연결 정보 삭제
 
변경 후LoginPsController.php
$decryptLoginPwd = $memberUtil->jsDecrypt($request->post()->get('loginPwd'), self::SECRET_KEY);
 
if (empty($decryptLoginPwd) === false) {
// 클라이언트 암호화 제거 전환기: 암호문을 보내는 구스킨 잔존 상점 실측용
$logger->channel('userLogin')->info('로그인 암호문 수신', ['referer' => $request->getReferer()]);
$request->post()->set('loginId', trim($decryptLoginId));
$request->post()->set('loginPwd', trim($decryptLoginPwd));
} else {
// 평문 전송 스킨: 암호문 경로와 동일하게 loginId/loginPwd trim 정합
$request->post()->set('loginId', trim($request->post()->get('loginId') ?? ''));
$request->post()->set('loginPwd', trim($request->post()->get('loginPwd') ?? ''));
}
 
//카카오 계정탈퇴 혹은 연결해제시 카카오에서 연결끊기 콜백 url 받아 카카오 연결 정보 삭제
 
data/skin/front/moment/member/kakao_connect.html+2-40
변경 전kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/front/moment/member/login.html-62
변경 전login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<div class="member_login_box">
<h3>{=__('회원 로그인')}</h3>
<div class="login_input_sec">
<script type="text/javascript">
var $formLogin;
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$('#btnJoinMember').click(function (e) {
}
}
 
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
 
 
<div class="member_login_box">
<h3>{=__('회원 로그인')}</h3>
<div class="login_input_sec">
<script type="text/javascript">
var $formLogin;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$('#btnJoinMember').click(function (e) {
}
}
 
 
 
 
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
data/skin/front/mygreek/member/kakao_connect.html+2-40
변경 전kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/front/mygreek/member/login.html-62
변경 전login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<div class="member_login_box">
<h3>{=__('회원 로그인')}</h3>
<div class="login_input_sec">
<script type="text/javascript">
var $formLogin;
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$('#btnJoinMember').click(function (e) {
}
}
 
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
 
 
<div class="member_login_box">
<h3>{=__('회원 로그인')}</h3>
<div class="login_input_sec">
<script type="text/javascript">
var $formLogin;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$('#btnJoinMember').click(function (e) {
}
}
 
 
 
 
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
data/skin/front/story_g/member/kakao_connect.html+2-40
변경 전kakao_connect.html
<h2>{=__('회원 정보 확인')}</h2>
<div class="box kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<h2>{=__('회원 정보 확인')}</h2>
<div class="box kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/front/story_g/member/login.html-62
변경 전login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<div class="login">
<div class="input-info">
<div>
<script type="text/javascript">
var $formLogin;
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
function login_fail(url, message) {
$formLogin.find('.caution-msg1').removeClass('dn').text(message);
setTimeout(function () {
}
}
 
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<form id="formLogin" method="post" action="{loginActionUrl}">
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="returnUrl" name="returnUrl" value="{returnUrl}"/>
 
 
<div class="login">
<div class="input-info">
<div>
<script type="text/javascript">
var $formLogin;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function login_fail(url, message) {
$formLogin.find('.caution-msg1').removeClass('dn').text(message);
setTimeout(function () {
}
}
 
 
 
 
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
data/skin/mobile/moment/member/kakao_connect.html+2-40
변경 전kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/mobile/moment/member/login.html-66
변경 전login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<input type="hidden" id="encryptLoginId" name="encryptLoginId"/>
<input type="hidden" id="encryptLoginPwd" name="encryptLoginPwd"/>
<dl>
<dt><label for="loginId">{=__('아이디')}</label></dt>
<dd><input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}"/></dd>
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$formLogin = $('#formLogin');
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
if (isMyapp) {
form.encryptLoginId.value = encryptLoginId;
form.encryptLoginPwd.value = encryptLoginPwd;
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
 
 
 
 
<dl>
<dt><label for="loginId">{=__('아이디')}</label></dt>
<dd><input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}"/></dd>
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$formLogin = $('#formLogin');
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
 
 
 
 
if (isMyapp) {
 
 
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
data/skin/mobile/mygreek/member/kakao_connect.html+2-39
변경 전kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/mobile/mygreek/member/login.html-66
변경 전login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<input type="hidden" id="encryptLoginId" name="encryptLoginId"/>
<input type="hidden" id="encryptLoginPwd" name="encryptLoginPwd"/>
<dl>
<dt><label for="loginId">{=__('아이디')}</label></dt>
<dd><input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}"/></dd>
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$formLogin = $('#formLogin');
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
if (isMyapp) {
form.encryptLoginId.value = encryptLoginId;
form.encryptLoginPwd.value = encryptLoginPwd;
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
 
 
 
 
<dl>
<dt><label for="loginId">{=__('아이디')}</label></dt>
<dd><input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}"/></dd>
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$(document).ready(function () {
var order_no_max_length = $('input[name=orderNo]').data('max-length');
$formLogin = $('#formLogin');
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
 
 
 
 
if (isMyapp) {
 
 
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
data/skin/mobile/story_g/member/kakao_connect.html+2-39
변경 전kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}" />
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
/**
* 로그인 정보 암호화 처리
*/
let Encryption = {
get encryptMethod() {
return "AES-256-CBC";
},
get encryptMethodLength() {
let encryptMethod = this.encryptMethod;
let aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt: function (string, key) {
let iv = CryptoJS.lib.WordArray.random(16);
let salt = CryptoJS.lib.WordArray.random(256);
let iterations = 999;
let encryptMethodLength = this.encryptMethodLength / 4;
let hashKey = CryptoJS.PBKDF2(key, salt, { hasher: CryptoJS.algo.SHA512, keySize: encryptMethodLength / 8, iterations: iterations });
let encrypted = CryptoJS.AES.encrypt(string, hashKey, { mode: CryptoJS.mode.CBC, iv: iv });
let encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
let output = {
ciphertext: encryptedString,
iv: CryptoJS.enc.Hex.stringify(iv),
salt: CryptoJS.enc.Hex.stringify(salt),
iterations: iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
};
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
let secretKey = $("#secretKey").val();
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
let encryptLoginId = Encryption.encrypt(loginId, secretKey);
let encryptLoginPwd = Encryption.encrypt(loginPwd, secretKey);
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: encryptLoginId,
loginPwd: encryptLoginPwd,
token: token
},
success: function (data) {
 
변경 후kakao_connect.html
<!-- //member_tit -->
<div class="member_cont kakao_connect">
<input type="hidden" id="token" name="token" value="{token}" />
 
<div class="kakao_connect_header">
<h3><b>{kakaoEmail}</b>{=__('으로 가입된 회원정보가 있습니다.')}</h3>
</div>
location.href = "../member/login.php";
}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
function connectToKakao() {
let memNo = $("input[name='memNo']:checked").val();
if (!memNo) {
let loginId = $("#loginId").val();
let loginPwd = $("#loginPwd").val();
let token = $("#token").val();
 
 
if (!loginId) {
alert('{=__("아이디를 입력해주세요.")}');
return;
}
 
 
 
 
$("#connectToKakaoBtn").prop("disabled", true);
 
$.ajax({
url: "../member/kakao_connect_ps.php",
data: {
memNo: memNo,
loginId: loginId,
loginPwd: loginPwd,
token: token
},
success: function (data) {
 
data/skin/mobile/story_g/member/login.html-66
변경 전login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
<input type="hidden" id="secretKey" name="secretKey" value="{secretKey}"/>
<input type="hidden" id="encryptFl" name="encryptFl" value="Y"/>
<input type="hidden" id="encryptLoginId" name="encryptLoginId"/>
<input type="hidden" id="encryptLoginPwd" name="encryptLoginPwd"/>
<div class="inp_tx">
<label for="loginId" class="blind">{=__('아이디')}</label>
<input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}">
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
/**
* 로그인 정보 암호화 처리
*/
var Encryption = {
get encryptMethod() {
return 'AES-256-CBC';
},
get encryptMethodLength() {
var encryptMethod = this.encryptMethod;
var aesNumber = encryptMethod.match(/\d+/)[0];
return parseInt(aesNumber);
},
encrypt : function (string, key) {
var iv = CryptoJS.lib.WordArray.random(16);
var salt = CryptoJS.lib.WordArray.random(256);
var iterations = 999;
var encryptMethodLength = (this.encryptMethodLength/4);
var hashKey = CryptoJS.PBKDF2(key, salt, {'hasher': CryptoJS.algo.SHA512, 'keySize': (encryptMethodLength/8), 'iterations': iterations});
var encrypted = CryptoJS.AES.encrypt(string, hashKey, {'mode': CryptoJS.mode.CBC, 'iv': iv});
var encryptedString = CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
 
var output = {
'ciphertext': encryptedString,
'iv': CryptoJS.enc.Hex.stringify(iv),
'salt': CryptoJS.enc.Hex.stringify(salt),
'iterations': iterations
};
 
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(JSON.stringify(output)));
}
}
 
/**
* form serialize 값 변경
*
* @param values 폼명
* @param k 변경할 inputName
* @param v 변경할 inputValue
*/
function changeSerialize(values, k, v) {
var found = false;
for (i = 0; i < values.length && !found; i++) {
if (values[i].name == k) {
values[i].value = v;
found = true;
}
}
 
if (!found) {
values.push({name: k,value: v});
}
return values;
}
 
$(document).ready(function () {
$formLogin = $('#formLogin');
$formLogin.validate({
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
// 로그인 정보 암호화 처리 (보안이슈)
var encryptLoginId = Encryption.encrypt($('#loginId').val(),$('#secretKey').val());
var encryptLoginPwd = Encryption.encrypt($('#loginPwd').val(),$('#secretKey').val());
 
if (isMyapp) {
form.encryptLoginId.value = encryptLoginId;
form.encryptLoginPwd.value = encryptLoginPwd;
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
params = changeSerialize(params, 'loginId', encryptLoginId); // 암호화 된 아이디로 변경
params = changeSerialize(params, 'loginPwd', encryptLoginPwd); // 암호화 된 패스워드로 변경
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
변경 후login.html
<input type="hidden" name="saveFlag" value="{data.saveFlag}"/>
<input type="hidden" id="mode" name="mode" value="{modeLogin}"/>
<input type="hidden" id="isMyapp" name="isMyapp" value="{isMyapp}"/>
 
 
 
 
<div class="inp_tx">
<label for="loginId" class="blind">{=__('아이디')}</label>
<input type="text" id="loginId" name="loginId" value="{data.loginId}" placeholder="{=__('아이디')}">
var $formLogin;
var isMyapp = $('#isMyapp').val();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
$(document).ready(function () {
$formLogin = $('#formLogin');
$formLogin.validate({
required: "{=__('패스워드를 입력해주세요')}"
}
}, submitHandler: function (form) {
 
 
 
 
if (isMyapp) {
 
 
form.target = 'ifrmProcess';
form.submit();
} else {
}
 
var params = $(form).serializeArray();
 
 
$.post('../member/login_ps.php', params).done(function (data) {
var code = data.code;
var message = data.message;
 
보안서버 HTTPS 리다이렉트 처리 개선
Component/SiteLink/SiteLink.php+1-1
변경 전SiteLink.php
\Cookie::del('GD5SESSID');
}
$refreshUrl = $this->getFullUrl('https', $this->_sslDomain, $this->_sslPort, Request::getRequestUri());
http_response_code(Response::HTTP_TEMPORARY_REDIRECT);
header('Location: ' . $refreshUrl);
exit();
} else {
 
변경 후SiteLink.php
\Cookie::del('GD5SESSID');
}
$refreshUrl = $this->getFullUrl('https', $this->_sslDomain, $this->_sslPort, Request::getRequestUri());
http_response_code(Response::HTTP_PERMANENTLY_REDIRECT);
header('Location: ' . $refreshUrl);
exit();
} else {
 
SMS 발송담당자 지정 인원 제한 해제
Admin/policy/_manage_permission_menu_list.php+1-1
변경 전_manage_permission_menu_list.php
?>
</table>
</div>
<input type="hidden" id="messageSenderMeta" data-editor-approver="<?= !empty($isEditorMessageApprover) ? 'y' : 'n'; ?>" data-editor-representative="<?= !empty($isEditorMessageRepresentative) ? 'y' : 'n'; ?>" data-caller-blocked="<?= !empty($isCallerBlocked) ? 'y' : 'n'; ?>" data-caller-uncertified-delegate="<?= !empty($isCallerUncertifiedDelegate) ? 'y' : 'n'; ?>" data-sender-count="<?= (int)(gd_isset($messageSenderCount, 0)); ?>" data-sender-limit="<?= (int)(gd_isset($messageSenderLimit, 0)); ?>"/>
<div class="access-control-reason <?=(!empty($sno) || $changeType == 'init') ? '' : 'display-none'?>" style="clear: both;">
<div class="table-action-dropdown">
<div class="table-action mgt0 mgb15">
 
변경 후_manage_permission_menu_list.php
?>
</table>
</div>
<input type="hidden" id="messageSenderMeta" data-editor-approver="<?= !empty($isEditorMessageApprover) ? 'y' : 'n'; ?>" data-editor-representative="<?= !empty($isEditorMessageRepresentative) ? 'y' : 'n'; ?>" data-caller-blocked="<?= !empty($isCallerBlocked) ? 'y' : 'n'; ?>" data-caller-uncertified-delegate="<?= !empty($isCallerUncertifiedDelegate) ? 'y' : 'n'; ?>"/>
<div class="access-control-reason <?=(!empty($sno) || $changeType == 'init') ? '' : 'display-none'?>" style="clear: both;">
<div class="table-action-dropdown">
<div class="table-action mgt0 mgb15">
 
Admin/policy/manage_permission.php+1-12
변경 전manage_permission.php
var $smsSetBox = $('#menuList input:checkbox[data-value="messageSendSms"]:not(:disabled)');
if ($smsSetBox.length > 0) {
var smsSetGranted = $smsSetBox.is(':checked');
var smsChanged = false, smsNewSenderCount = 0;
$smsCheckedRows.each(function () {
var smsRole = $(this).data('smsRole');
// 잠금 판정은 서버가 행마다 내려준다 — 미등록 최고운영자는 smsRole 이 비어 있어 역할값만으로는 걸러지지 않는다
if (nowSender !== smsSetGranted) {
smsChanged = true;
}
if (smsSetGranted && !nowSender) {
smsNewSenderCount++;
}
});
if (smsChanged) {
var $smsMeta = $('#messageSenderMeta');
dialog_alert('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.', '경고');
return false;
}
if (smsSetGranted) {
var smsSenderCount = parseInt($smsMeta.data('senderCount'), 10) || 0;
var smsSenderLimit = parseInt($smsMeta.data('senderLimit'), 10) || 0;
if (smsSenderCount + smsNewSenderCount > smsSenderLimit) {
dialog_alert('발송담당자는 최대 ' + smsSenderLimit + '명까지 지정 가능합니다.', '경고');
return false;
}
}
if ($.trim($('#accessControlReason').val()) === '') {
dialog_alert('발송담당자 권한 변경 사유를 작성해주세요.', '경고');
return false;
 
변경 후manage_permission.php
var $smsSetBox = $('#menuList input:checkbox[data-value="messageSendSms"]:not(:disabled)');
if ($smsSetBox.length > 0) {
var smsSetGranted = $smsSetBox.is(':checked');
var smsChanged = false;
$smsCheckedRows.each(function () {
var smsRole = $(this).data('smsRole');
// 잠금 판정은 서버가 행마다 내려준다 — 미등록 최고운영자는 smsRole 이 비어 있어 역할값만으로는 걸러지지 않는다
if (nowSender !== smsSetGranted) {
smsChanged = true;
}
 
 
 
});
if (smsChanged) {
var $smsMeta = $('#messageSenderMeta');
dialog_alert('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.', '경고');
return false;
}
 
 
 
 
 
 
 
 
if ($.trim($('#accessControlReason').val()) === '') {
dialog_alert('발송담당자 권한 변경 사유를 작성해주세요.', '경고');
return false;
 
Component/Member/Manager.php-7
변경 전Manager.php
$smsRoleGuard = \App::getInstance(MessageOperatorRoleGuard::class);
$smsGranted = is_array($arrData['functionAuth']) && (($arrData['functionAuth'][MessageOperatorRoleService::SEND_SMS_FUNCTION_AUTH] ?? '') === 'y');
$smsChanged = false;
$smsNewSenderCount = 0;
foreach ($managersData as $smsTarget) {
// 최고운영자(sno=1)/위임자는 messageSendSms 강제(잠금)라 변경 대상 아님
if ($smsRoleService->isSenderPermissionLocked((int)$smsTarget['sno'])) {
if ($smsNowGranted !== $smsGranted) {
$smsChanged = true;
}
if ($smsGranted && $smsNowGranted === false) {
$smsNewSenderCount++; // 신규로 발송담당자가 되는 대상
}
}
if ($smsChanged) {
if ($smsRoleGuard->isDelegateOrHigher() === false) {
if ($smsRoleGuard->isRepresentative() && $smsRoleService->isBlockedRepresentative()) {
throw new Exception(__('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.'));
}
if ($smsGranted && ($smsRoleService->countSenders() + $smsNewSenderCount) > MessageOperatorRoleService::MAX_SENDER_COUNT) {
throw new Exception(__('발송담당자는 최대 %d명까지 지정 가능합니다.', MessageOperatorRoleService::MAX_SENDER_COUNT));
}
if (trim((string)($arrData['accessControlReason'] ?? '')) === '') {
throw new Exception(__('발송담당자 권한 변경 사유를 작성해주세요.'));
}
 
변경 후Manager.php
$smsRoleGuard = \App::getInstance(MessageOperatorRoleGuard::class);
$smsGranted = is_array($arrData['functionAuth']) && (($arrData['functionAuth'][MessageOperatorRoleService::SEND_SMS_FUNCTION_AUTH] ?? '') === 'y');
$smsChanged = false;
 
foreach ($managersData as $smsTarget) {
// 최고운영자(sno=1)/위임자는 messageSendSms 강제(잠금)라 변경 대상 아님
if ($smsRoleService->isSenderPermissionLocked((int)$smsTarget['sno'])) {
if ($smsNowGranted !== $smsGranted) {
$smsChanged = true;
}
 
 
 
}
if ($smsChanged) {
if ($smsRoleGuard->isDelegateOrHigher() === false) {
if ($smsRoleGuard->isRepresentative() && $smsRoleService->isBlockedRepresentative()) {
throw new Exception(__('SMS 발송 메뉴 접근이 제한되었습니다.<br>스팸 또는 부정 사용으로 차단되어 접근할 수 없습니다.<br>자세한 사항은 1:1문의로 문의하시기 바랍니다.'));
}
 
 
 
if (trim((string)($arrData['accessControlReason'] ?? '')) === '') {
throw new Exception(__('발송담당자 권한 변경 사유를 작성해주세요.'));
}
 
Controller/Admin/Policy/ManagePsController.php-2
변경 전ManagePsController.php
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
break;
 
// --- 운영자 권한 설정> 운영자 검색
 
변경 후ManagePsController.php
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
 
 
break;
 
// --- 운영자 권한 설정> 운영자 검색
 
Controller/Admin/Share/LayerManagePermissionController.php-2
변경 전LayerManagePermissionController.php
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
$this->setData('messageSenderCount', $messageOperatorRoleService->countSenders());
$this->setData('messageSenderLimit', MessageOperatorRoleService::MAX_SENDER_COUNT);
$this->setData('scmFl', $scmFl);
$this->setData('scmNo', $scmNo);
$this->setData('viewMemuDepth', $viewMemuDepth);
 
변경 후LayerManagePermissionController.php
$this->setData('isCallerBlocked', $isEditorMessageRepresentative && $messageOperatorRoleService->isBlockedRepresentative());
// 위임자 아님 안내가 우선이라 위임자 이상일 때만 판정한다 — isUncertifiedDelegateActor 는 위임자가 아닌 운영자도 DI 가 없으면 true 다
$this->setData('isCallerUncertifiedDelegate', $messageOperatorRoleGuard->isDelegateOrHigher() && $messageOperatorRoleService->isUncertifiedDelegateActor());
 
 
$this->setData('scmFl', $scmFl);
$this->setData('scmNo', $scmNo);
$this->setData('viewMemuDepth', $viewMemuDepth);